├── chat-buy-react ├── server │ ├── key.js │ ├── config.js │ ├── model.js │ ├── router.js │ ├── user-model.js │ ├── allorders-model.js │ ├── server.js │ ├── jwtMiddleware.js │ ├── chat-model.js │ ├── chat.js │ ├── db.js │ ├── foods.json │ ├── goods.js │ ├── user.js │ ├── order.js │ └── socket.js ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── src │ ├── images │ │ ├── goods.png │ │ ├── order.png │ │ ├── user.png │ │ ├── message.png │ │ ├── user-sel.png │ │ ├── goods-sel.png │ │ ├── order-sel.png │ │ └── message-sel.png │ ├── common │ │ ├── history.js │ │ ├── unit.js │ │ └── axiosMiddleware.js │ ├── container │ │ ├── my.jsx │ │ ├── allOrders.jsx │ │ ├── goods.jsx │ │ ├── login.jsx │ │ ├── register.jsx │ │ ├── myOrder.jsx │ │ ├── message.jsx │ │ ├── chat.jsx │ │ └── dashboard.jsx │ ├── reducers │ │ ├── orders.js │ │ ├── index.js │ │ ├── goods.js │ │ ├── chat.js │ │ └── user.js │ ├── components │ │ ├── message │ │ │ ├── chatListItem.jsx │ │ │ ├── chatList.jsx │ │ │ └── messageList.jsx │ │ ├── navBar │ │ │ └── backNavBar.jsx │ │ ├── goods │ │ │ ├── buy.jsx │ │ │ └── goodsList.jsx │ │ ├── common │ │ │ └── 404.jsx │ │ ├── allOrders │ │ │ └── list.jsx │ │ ├── login │ │ │ └── loginForm.jsx │ │ ├── register │ │ │ └── registerForm.jsx │ │ └── myOrder │ │ │ └── myOrderItem.jsx │ ├── store │ │ └── configureStore.js │ ├── asyncComponent.jsx │ ├── router │ │ └── router.jsx │ ├── actions │ │ ├── type.js │ │ ├── goods.js │ │ ├── order.js │ │ ├── user.js │ │ └── chat.js │ ├── index.js │ ├── styles │ │ └── index.scss │ └── registerServiceWorker.js ├── tsconfig.json ├── .gitignore ├── config │ ├── jest │ │ ├── fileTransform.js │ │ └── cssTransform.js │ ├── polyfills.js │ ├── paths.js │ ├── env.js │ ├── webpackDevServer.config.js │ ├── webpack.config.dev.js │ └── webpack.config.prod.js ├── scripts │ ├── test.js │ ├── start.js │ └── build.js └── package.json ├── README-EN.md ├── README.md └── LICENSE /chat-buy-react/server/key.js: -------------------------------------------------------------------------------- 1 | import config from "./config"; 2 | 3 | module.exports = config.privateKey; 4 | -------------------------------------------------------------------------------- /chat-buy-react/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KieSun/Chat-Buy-React/HEAD/chat-buy-react/public/favicon.ico -------------------------------------------------------------------------------- /chat-buy-react/src/images/goods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KieSun/Chat-Buy-React/HEAD/chat-buy-react/src/images/goods.png -------------------------------------------------------------------------------- /chat-buy-react/src/images/order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KieSun/Chat-Buy-React/HEAD/chat-buy-react/src/images/order.png -------------------------------------------------------------------------------- /chat-buy-react/src/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KieSun/Chat-Buy-React/HEAD/chat-buy-react/src/images/user.png -------------------------------------------------------------------------------- /chat-buy-react/src/images/message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KieSun/Chat-Buy-React/HEAD/chat-buy-react/src/images/message.png -------------------------------------------------------------------------------- /chat-buy-react/src/images/user-sel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KieSun/Chat-Buy-React/HEAD/chat-buy-react/src/images/user-sel.png -------------------------------------------------------------------------------- /chat-buy-react/src/images/goods-sel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KieSun/Chat-Buy-React/HEAD/chat-buy-react/src/images/goods-sel.png -------------------------------------------------------------------------------- /chat-buy-react/src/images/order-sel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KieSun/Chat-Buy-React/HEAD/chat-buy-react/src/images/order-sel.png -------------------------------------------------------------------------------- /chat-buy-react/src/common/history.js: -------------------------------------------------------------------------------- 1 | import createHistory from "history/createBrowserHistory"; 2 | 3 | export default createHistory(); 4 | -------------------------------------------------------------------------------- /chat-buy-react/src/images/message-sel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KieSun/Chat-Buy-React/HEAD/chat-buy-react/src/images/message-sel.png -------------------------------------------------------------------------------- /chat-buy-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "allowJs": true 5 | } 6 | } -------------------------------------------------------------------------------- /chat-buy-react/server/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | DEV_DB_URL: "mongodb://localhost/chat-buy-react", 3 | PRODUCTION_DB_URL: "", 4 | PORT: process.env.PORT || 1717, 5 | privateKey: "chat-buy-react" 6 | } 7 | -------------------------------------------------------------------------------- /chat-buy-react/server/model.js: -------------------------------------------------------------------------------- 1 | 2 | import "./db"; 3 | import user from "./user-model"; 4 | import allOrders from "./allorders-model"; 5 | import chat from "./chat-model"; 6 | 7 | 8 | module.exports = { 9 | user, 10 | allOrders, 11 | chat 12 | }; 13 | -------------------------------------------------------------------------------- /chat-buy-react/server/router.js: -------------------------------------------------------------------------------- 1 | const user = require("./user"); 2 | const goods = require("./goods"); 3 | const order = require("./order"); 4 | const chat = require("./chat"); 5 | 6 | 7 | module.exports = function(app) { 8 | [ 9 | user, 10 | goods, 11 | order, 12 | chat 13 | ].forEach((p) => { 14 | p(app); 15 | }) 16 | } -------------------------------------------------------------------------------- /chat-buy-react/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /chat-buy-react/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /chat-buy-react/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /chat-buy-react/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /chat-buy-react/src/container/my.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { List } from "antd-mobile"; 3 | 4 | const Item = List.Item; 5 | 6 | const My = ({ history }) => ( 7 |
8 | 9 | history.push("/me/orders")}> 10 | 我的订单 11 | 12 | 13 |
14 | ); 15 | 16 | export default My; 17 | -------------------------------------------------------------------------------- /chat-buy-react/src/reducers/orders.js: -------------------------------------------------------------------------------- 1 | import { GET_ALL_ORDERS } from "../actions/type"; 2 | import { Map, List } from "immutable"; 3 | 4 | const initialState = Map({ 5 | allOrders: List([]) 6 | }); 7 | 8 | export default function(state = initialState, action) { 9 | switch (action.type) { 10 | case GET_ALL_ORDERS: 11 | return state.set("allOrders", action.payload); 12 | default: 13 | break; 14 | } 15 | return state; 16 | } 17 | -------------------------------------------------------------------------------- /chat-buy-react/server/user-model.js: -------------------------------------------------------------------------------- 1 | import mongoose , { Schema } from "mongoose"; 2 | 3 | const userSchema = Schema({ 4 | // 用户名 5 | user: { type: String, require: true }, 6 | // 密码 7 | pwd: { type: String, require: true }, 8 | // 类型 9 | type: { type: String, require: true }, 10 | // 所有订单 11 | orders: [{ type: Schema.Types.ObjectId, ref: "allOrders" }] 12 | }); 13 | 14 | const user = mongoose.model("user", userSchema); 15 | export default user; -------------------------------------------------------------------------------- /chat-buy-react/src/components/message/chatListItem.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ChatListItem = ({ messageObj, userId }) => { 4 | const isMine = messageObj.get("from") === userId; 5 | return ( 6 |
7 |
{isMine ? "我" : "对方"}
8 |

{messageObj.get("message")}

9 |
10 | ); 11 | }; 12 | 13 | export default ChatListItem; 14 | -------------------------------------------------------------------------------- /chat-buy-react/src/components/navBar/backNavBar.jsx: -------------------------------------------------------------------------------- 1 | import { NavBar, Icon } from "antd-mobile"; 2 | import React from "react"; 3 | import PropTypes from "prop-types"; 4 | 5 | const BackNavBar = ({ title, backClick, hasIcon = true }) => ( 6 | } 9 | onLeftClick={() => backClick()} 10 | > 11 | {title} 12 | 13 | ); 14 | 15 | BackNavBar.propTypes = { 16 | title: PropTypes.string.isRequired, 17 | backClick: PropTypes.func, 18 | hasIcon: PropTypes.bool 19 | }; 20 | 21 | export default BackNavBar; 22 | -------------------------------------------------------------------------------- /chat-buy-react/src/components/goods/buy.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | const Buy = ({ price, handleBuy }) => { 5 | return ( 6 |
7 |
¥{price}
8 |
0 ? "buy-button" : "no-buy-button"} 10 | onClick={() => price > 0 && handleBuy()} 11 | > 12 | 购买 13 |
14 |
15 | ); 16 | }; 17 | 18 | Buy.propTypes = { 19 | price: PropTypes.number.isRequired, 20 | handleBuy: PropTypes.func.isRequired 21 | }; 22 | 23 | export default Buy; 24 | -------------------------------------------------------------------------------- /chat-buy-react/src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from "redux"; 2 | import thunkMiddleware from "redux-thunk"; 3 | import rootReducer from "../reducers/index"; 4 | 5 | export default function configureStore() { 6 | const store = createStore( 7 | rootReducer, 8 | compose( 9 | applyMiddleware(thunkMiddleware), 10 | window.devToolsExtension ? window.devToolsExtension() : f => f 11 | ) 12 | ); 13 | 14 | if (module.hot) { 15 | module.hot.accept("../reducers", () => { 16 | const nextRootReducer = require("../reducers/index"); 17 | store.replaceReducer(nextRootReducer); 18 | }); 19 | } 20 | 21 | return store; 22 | } 23 | -------------------------------------------------------------------------------- /chat-buy-react/src/asyncComponent.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | export default function asyncComponent(importComponent) { 4 | class AsyncComponent extends Component { 5 | constructor(props) { 6 | super(props); 7 | 8 | this.state = { 9 | component: null 10 | }; 11 | } 12 | 13 | async componentDidMount() { 14 | const { default: component } = await importComponent(); 15 | 16 | this.setState({ 17 | component: component 18 | }); 19 | } 20 | 21 | render() { 22 | const C = this.state.component; 23 | 24 | return C ? : null; 25 | } 26 | } 27 | 28 | return AsyncComponent; 29 | } 30 | -------------------------------------------------------------------------------- /chat-buy-react/server/allorders-model.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | 3 | const allOrdersSchema = Schema({ 4 | // 价格 5 | price: { type: Number, require: true }, 6 | // 购买商品描述 7 | desc: { type: String, require: true }, 8 | // 购买多少商品 9 | count: { type: Number, require: true }, 10 | // 订单状态 0:未接单 1:接单 2:已送达 11 | state: { type: Number, require: true }, 12 | // 下单时间 13 | date: { type: Date, require: true, default: Date.now }, 14 | // 购买者 15 | customer: { type: Schema.Types.ObjectId, ref: "user" }, 16 | // 接单者 17 | deliver: { type: Schema.Types.ObjectId, ref: "user" } 18 | }); 19 | 20 | const allOrders = mongoose.model("allOrders", allOrdersSchema); 21 | export default allOrders; -------------------------------------------------------------------------------- /chat-buy-react/src/components/common/404.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Nav from "../navBar/backNavBar"; 3 | import { Button } from "antd-mobile"; 4 | import { withRouter } from "react-router-dom"; 5 | 6 | @withRouter 7 | class NotFound extends React.Component { 8 | render() { 9 | return ( 10 |
11 |
23 | ); 24 | } 25 | } 26 | 27 | export default NotFound; 28 | -------------------------------------------------------------------------------- /chat-buy-react/server/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | 3 | const bodyParser = require("body-parser"); 4 | const cookieParser = require("cookie-parser"); 5 | const jwt = require("jsonwebtoken"); 6 | const key = require("./key"); 7 | 8 | const app = global.app = express(); 9 | 10 | const server = require("http").createServer(app); 11 | const io = global.io = require("socket.io")(server); 12 | 13 | 14 | app.use(bodyParser.json()); 15 | app.use(bodyParser.urlencoded({ extended: false })); 16 | app.use(cookieParser()); 17 | app.use(require("./jwtMiddleware")()); 18 | 19 | require("./socket")(io); 20 | require('./router')(app) 21 | 22 | server.listen(1717, function() { 23 | console.log("Node app start at port 1717"); 24 | }); 25 | -------------------------------------------------------------------------------- /chat-buy-react/src/router/router.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, Router, Switch } from "react-router-dom"; 3 | import asyncComponent from "../asyncComponent"; 4 | import history from "../common/history"; 5 | 6 | const Login = asyncComponent(() => import("../container/login")); 7 | const Register = asyncComponent(() => import("../container/register")); 8 | const DashBoard = asyncComponent(() => import("../container/dashboard.jsx")); 9 | 10 | const Root = () => ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | 20 | export default Root; 21 | -------------------------------------------------------------------------------- /chat-buy-react/src/actions/type.js: -------------------------------------------------------------------------------- 1 | export const REGISTER = "REGISTER"; 2 | export const LOGIN = "LOGIN"; 3 | export const GET_INFO = "GET_INFO"; 4 | export const LOG_OUT = "LOG_OUT"; 5 | export const GOODS_LIST = "GOODS_LIST"; 6 | export const ADD_TO_CART = "ADD_TO_CART"; 7 | export const BUY_SUCCESS = "BUY_SUCCESS"; 8 | export const GET_MY_ORDERS = "GET_MY_ORDERS"; 9 | export const GET_ALL_ORDERS = "GET_ALL_ORDERS"; 10 | export const GET_ORDER = "GET_ORDER"; 11 | export const GET_ORDER_SUCCESS = "GET_ORDER_SUCCESS"; 12 | export const AFFIRM_ORDER = "AFFIRM_ORDER"; 13 | export const GET_USERNAME = "GET_USERNAME"; 14 | export const GET_MESSAGE = "GET_MESSAGE"; 15 | export const GET_MESSAGE_LIST = "GET_MESSAGE_LIST"; 16 | export const SET_CURRENT_LIST = "SET_CURRENT_LIST"; 17 | export const CLEAN_NO_READ = "CLEAN_NO_READ"; 18 | -------------------------------------------------------------------------------- /chat-buy-react/src/common/unit.js: -------------------------------------------------------------------------------- 1 | export function filterNoReadCount(userId, messageList) { 2 | let noReadCountArray = []; 3 | messageList.map(v => { 4 | // 找到当前用户的 lastId 5 | const sendObj = v.get("bothSide").find(v => v.get("user") === userId); 6 | let readId; 7 | if (sendObj) { 8 | readId = sendObj.get("lastId"); 9 | } 10 | // 把发给我的消息过滤出来 11 | const messages = v.get("messages").filter(v => { 12 | return v.get("to") === userId; 13 | }); 14 | if (readId) { 15 | // 把上次未读消息的 id 索引找到 16 | let index = messages.findIndex(v => readId === v.get("_id")); 17 | noReadCountArray.push(messages.size - index - 1); 18 | } else { 19 | noReadCountArray.push(messages.size); 20 | } 21 | return noReadCountArray 22 | }); 23 | return noReadCountArray; 24 | } 25 | -------------------------------------------------------------------------------- /chat-buy-react/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | // import { combineReducers } from "redux"; 2 | import { combineReducers } from "redux-immutable"; 3 | import UserReducer from "./user"; 4 | import GoodsReducer from "./goods"; 5 | import OrdersReducer from "./orders"; 6 | import ChatReducer from "./chat"; 7 | import { LOG_OUT } from "../actions/type"; 8 | import history from "../common/history"; 9 | 10 | const appReducer = combineReducers({ 11 | user: UserReducer, 12 | goods: GoodsReducer, 13 | order: OrdersReducer, 14 | chat: ChatReducer 15 | }); 16 | 17 | const rootReducer = (state, action) => { 18 | if (action.type === LOG_OUT) { 19 | history.push("/login"); 20 | window.localStorage.clear(); 21 | state = undefined; 22 | } 23 | return appReducer(state, action); 24 | }; 25 | 26 | export default rootReducer; 27 | -------------------------------------------------------------------------------- /chat-buy-react/src/components/allOrders/list.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import MyOrderItem from "../myOrder/myOrderItem"; 3 | import PropTypes from "prop-types"; 4 | import ImmutablePropTypes from "react-immutable-proptypes"; 5 | 6 | const List = ({allOrders, getOrder}) => ( 7 | !allOrders.isEmpty() ? ( 8 |
9 | {allOrders.map(v => ( 10 | 16 | ))} 17 |
18 | ) : null 19 | ) 20 | 21 | List.propTypes = { 22 | allOrders: ImmutablePropTypes.list.isRequired, 23 | getOrder: PropTypes.func.isRequired, 24 | }; 25 | 26 | export default List; 27 | -------------------------------------------------------------------------------- /chat-buy-react/server/jwtMiddleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const key = require("./key"); 3 | 4 | // token 中间件 在请求过来时统一判断 token 是否失效 5 | module.exports = function(options) { 6 | // 备用参数 7 | options = options || {} 8 | 9 | return function(req, res, next) { 10 | if (req.url === "/user/login" || req.url === "/user/register") { 11 | next(); 12 | return; 13 | } 14 | 15 | const token = req.body.token || req.query.token || req.headers["x-access-token"]; 16 | if (token) { 17 | jwt.verify(token, key, function(err, decoded) { 18 | if (err) { 19 | return res.status(401).json({ errorMsg: "token失效" }); 20 | } 21 | req.decoded = decoded; 22 | next(); 23 | }); 24 | } else { 25 | return res.status(401).json({ errorMsg: "token失效" }); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /chat-buy-react/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | const argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI or in coverage mode 22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 23 | argv.push('--watch'); 24 | } 25 | 26 | 27 | jest.run(argv); 28 | -------------------------------------------------------------------------------- /chat-buy-react/server/chat-model.js: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema } from "mongoose"; 2 | 3 | const chatSchema = new Schema({ 4 | messageId: String, 5 | // 聊天双方 6 | bothSide: [ 7 | { 8 | user: { 9 | type: Schema.Types.ObjectId 10 | }, 11 | name: { 12 | type: String 13 | }, 14 | lastId: { 15 | type: String 16 | } 17 | } 18 | ], 19 | messages: [ 20 | { 21 | // 发送方 22 | from: { 23 | type: Schema.Types.ObjectId, 24 | ref: "user" 25 | }, 26 | // 接收方 27 | to: { 28 | type: Schema.Types.ObjectId, 29 | ref: "user" 30 | }, 31 | // 发送的消息 32 | message: String, 33 | // 发送日期 34 | date: { type: Date, default: Date.now } 35 | } 36 | ] 37 | }); 38 | 39 | const chat = mongoose.model("chat", chatSchema); 40 | export default chat; -------------------------------------------------------------------------------- /chat-buy-react/src/container/allOrders.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import List from "../components/allOrders/list"; 3 | import { connect } from "react-redux"; 4 | import { getAllOrders, getOrder } from "../actions/order"; 5 | 6 | @connect(state => ({ order: state.get("order") }), { 7 | getAllOrders, 8 | getOrder 9 | }) 10 | class AllOrders extends React.Component { 11 | componentDidMount() { 12 | this.props.getAllOrders(); 13 | } 14 | render() { 15 | const { order, getOrder } = this.props; 16 | const allOrders = order.get("allOrders"); 17 | return ( 18 |
19 | 23 |
24 | ) 25 | } 26 | } 27 | 28 | export default AllOrders; 29 | -------------------------------------------------------------------------------- /chat-buy-react/config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | 18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. 19 | // We don't polyfill it in the browser--this is user's responsibility. 20 | if (process.env.NODE_ENV === 'test') { 21 | require('raf').polyfill(global); 22 | } 23 | -------------------------------------------------------------------------------- /chat-buy-react/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { Provider } from "react-redux"; 4 | import { AppContainer } from "react-hot-loader"; 5 | import App from "./router/router"; 6 | import registerServiceWorker from "./registerServiceWorker"; 7 | import configureStore from "./store/configureStore"; 8 | import "./common/axiosMiddleware"; 9 | import "fastclick"; 10 | import "./styles/index.scss"; 11 | 12 | const store = configureStore(); 13 | 14 | function render(Component) { 15 | ReactDOM.render( 16 | 17 | 18 | 19 | 20 | , 21 | document.getElementById("root") 22 | ); 23 | } 24 | 25 | render(App); 26 | 27 | registerServiceWorker(); 28 | 29 | if (module.hot) { 30 | module.hot.accept("./router/router", () => { 31 | const NextApp = require("./router/router").default; 32 | render(NextApp); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /chat-buy-react/server/chat.js: -------------------------------------------------------------------------------- 1 | const model = require("./model"); 2 | const Chat = model.chat; 3 | 4 | module.exports = function(app) { 5 | // 获取所有聊天信息 6 | app.post("/chat/getMessageList", function(req, res) { 7 | const { id } = req.decoded; 8 | Chat.find({ 9 | messageId: { $regex: id } 10 | }).exec(function(err, doc) { 11 | if (err) { 12 | return res.status(500).json({ msg: "后端出错" }); 13 | } 14 | return res.status(200).json({ code: 0, data: doc }); 15 | }); 16 | }); 17 | // 清除未读消息 18 | app.post("/chat/cleanNoRead", function(req, res) { 19 | const { id } = req.decoded; 20 | const { messageId, readId } = req.body; 21 | Chat.findOneAndUpdate( 22 | { "bothSide.user": id, messageId }, 23 | { $set: { "bothSide.$.lastId": readId } }, 24 | function(error, result) { 25 | if (error || !result) { 26 | return res.status(500).json({ msg: "后端出错" }); 27 | } 28 | return res.json({ code: 0 }); 29 | } 30 | ); 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /chat-buy-react/src/components/message/chatList.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ChatListItem from "./chatListItem"; 3 | import ImmutablePropTypes from "react-immutable-proptypes"; 4 | import PropTypes from "prop-types"; 5 | 6 | class ChatList extends React.PureComponent { 7 | componentDidUpdate() { 8 | window.scrollTo(0, document.body.scrollHeight); 9 | } 10 | render() { 11 | const { currentChatList, userId } = this.props; 12 | console.log('render') 13 | return ( 14 |
15 |
16 | {!currentChatList.isEmpty() && 17 | currentChatList.map(v => ( 18 | 23 | ))} 24 |
25 |
26 | ); 27 | } 28 | } 29 | 30 | ChatList.propTypes = { 31 | currentChatList: ImmutablePropTypes.list.isRequired, 32 | userId: PropTypes.string.isRequired 33 | }; 34 | 35 | export default ChatList; 36 | -------------------------------------------------------------------------------- /chat-buy-react/src/container/goods.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { getGoodsInfo, addToCart, buy } from "../actions/goods"; 4 | import GoodsList from "../components/goods/goodsList"; 5 | import Buy from "../components/goods/buy"; 6 | 7 | @connect(state => ({ goods: state.get("goods") }), { 8 | getGoodsInfo, 9 | addToCart, 10 | buy 11 | }) 12 | class Goods extends React.Component { 13 | componentDidMount() { 14 | if (this.props.goods.get("goodsList").isEmpty()) { 15 | this.props.getGoodsInfo(); 16 | } 17 | } 18 | render() { 19 | const { goods, addToCart, buy } = this.props; 20 | const goodsList = goods.get("goodsList"); 21 | return goodsList.isEmpty() ? null : ( 22 |
23 | 28 | 29 |
30 | ); 31 | } 32 | } 33 | 34 | export default Goods; 35 | -------------------------------------------------------------------------------- /chat-buy-react/src/container/login.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import LoginForm from "../components/login/loginForm"; 3 | import { connect } from "react-redux"; 4 | import { login } from "../actions/user"; 5 | 6 | @connect(null, { login }) 7 | class Login extends React.Component { 8 | constructor() { 9 | super(); 10 | this.state = { 11 | user: "", 12 | pwd: "" 13 | }; 14 | this.handleTextChange = this.handleTextChange.bind(this); 15 | this.handleLogin = this.handleLogin.bind(this); 16 | this.handlePush = this.handlePush.bind(this); 17 | } 18 | handleTextChange(key, value) { 19 | this.setState({ 20 | [key]: value 21 | }); 22 | } 23 | handleLogin() { 24 | this.props.login(this.state); 25 | } 26 | handlePush() { 27 | this.props.history.push("register"); 28 | } 29 | render() { 30 | return ( 31 |
32 | 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Login; 43 | -------------------------------------------------------------------------------- /chat-buy-react/src/container/register.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import RegisterForm from "../components/register/registerForm"; 3 | import { connect } from "react-redux"; 4 | import { register } from "../actions/user"; 5 | 6 | @connect(null, { register }) 7 | class Register extends React.Component { 8 | constructor() { 9 | super(); 10 | this.state = { 11 | user: "", 12 | pwd: "", 13 | type: "deliver" 14 | }; 15 | this.handleTextChange = this.handleTextChange.bind(this); 16 | this.handleRegister = this.handleRegister.bind(this); 17 | this.radioData = [ 18 | { type: "deliver", text: "送货员" }, 19 | { type: "customer", text: "顾客" } 20 | ]; 21 | } 22 | handleTextChange(key, value) { 23 | this.setState({ 24 | [key]: value 25 | }); 26 | } 27 | handleRegister() { 28 | this.props.register(this.state); 29 | } 30 | render() { 31 | return ( 32 |
33 | 39 |
40 | ); 41 | } 42 | } 43 | 44 | export default Register; 45 | -------------------------------------------------------------------------------- /chat-buy-react/src/components/login/loginForm.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { List, InputItem, WingBlank, WhiteSpace, Button } from "antd-mobile"; 3 | import PropTypes from "prop-types"; 4 | 5 | const LoginForm = ({ login, push, handleTextChange }) => ( 6 |
7 | 8 | 9 | handleTextChange("user", v)}> 10 | 用户名 11 | 12 | 13 | handleTextChange("pwd", v)}>密码 14 | 15 | 16 | 19 | 20 |
21 | 24 | 27 |
28 |
29 |
30 | ); 31 | 32 | LoginForm.propTypes = { 33 | login: PropTypes.func.isRequired, 34 | push: PropTypes.func.isRequired, 35 | handleTextChange: PropTypes.func.isRequired 36 | }; 37 | 38 | export default LoginForm; 39 | -------------------------------------------------------------------------------- /chat-buy-react/server/db.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import config from "./config"; 3 | 4 | // Control the number of connections to the mongonDB 5 | let retryCount = 1; 6 | 7 | // mongoose.Promise = global.Promise; 8 | mongoose.Promise = require("bluebird"); 9 | 10 | const connections = mongoose.connection; 11 | const logger = console; 12 | 13 | connections.on('connecting', () => { 14 | logger.info('connecting to mongonDB...', config.DEV_DB_URL) 15 | }) 16 | connections.on('error', (err) => { 17 | logger.error('Error in connecting mongonDB', config.DEV_DB_URL, err) 18 | }) 19 | connections.on('connected', () => { 20 | logger.info("mongonDB connected", config.DEV_DB_URL) 21 | }) 22 | connections.once('open', function() { 23 | logger.info('MongoDB connection opened!', config.DEV_DB_URL); 24 | }); 25 | connections.on('reconnected', function () { 26 | logger.info('MongoDB reconnected!', config.DEV_DB_URL); 27 | }); 28 | connections.on('disconnected', function() { 29 | logger.info('MongoDB disconnected!', config.DEV_DB_URL); 30 | // auto reconnect 31 | if (retryCount < 100) { 32 | mongoose.connect(config.DEV_DB_URL, { useMongoClient: true }); 33 | retryCount ++; 34 | } 35 | }); 36 | mongoose.connect(config.DEV_DB_URL, { useMongoClient: true }); 37 | -------------------------------------------------------------------------------- /chat-buy-react/server/foods.json: -------------------------------------------------------------------------------- 1 | { 2 | "list": [ 3 | { 4 | "name": "招牌双拼套餐", 5 | "price": 59, 6 | "id": 1266111989 7 | }, 8 | { 9 | "name": "秘制手枪腿饭", 10 | "price": 37, 11 | "id": 1249852143 12 | }, 13 | { 14 | "name": "南洋咖喱鸡肉饭套餐", 15 | "price": 36, 16 | "id": 1250432023 17 | }, 18 | { 19 | "name": "招牌卤肉拼蜜汁鸡腿排", 20 | "price": 40, 21 | "id": 1250491784 22 | }, 23 | { 24 | "name": "照烧鸡腿排拼卤肉", 25 | "price": 60, 26 | "id": 1290660345 27 | }, 28 | { 29 | "name": "新奥尔良烤翅中饭", 30 | "price": 45, 31 | "id": 1250405263 32 | }, 33 | { 34 | "name": "香菇滑鸡饭", 35 | "price": 49, 36 | "id": 1265013352 37 | }, 38 | { 39 | "name": "秘制手枪腿饭", 40 | "price": 42, 41 | "id": 1249852129 42 | }, 43 | { 44 | "name": "矿泉水", 45 | "price": 4, 46 | "id": 1249953392 47 | }, 48 | { 49 | "name": "鲜果橙", 50 | "price": 8, 51 | "id": 1249953999 52 | }, 53 | { 54 | "name": "招牌卤肉饭", 55 | "price": 65, 56 | "id": 1249846564 57 | }, 58 | { 59 | "name": "招牌卤肉拼鱼香肉丝", 60 | "price": 52, 61 | "id": 1250481334 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /chat-buy-react/src/components/register/registerForm.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | List, 4 | InputItem, 5 | WingBlank, 6 | WhiteSpace, 7 | Button, 8 | Radio 9 | } from "antd-mobile"; 10 | import PropTypes from "prop-types"; 11 | 12 | const RegisterForm = ({ register, radioData, handleTextChange, type }) => ( 13 |
14 | 15 | 16 | handleTextChange("user", v)}> 17 | 用户名 18 | 19 | 20 | handleTextChange("pwd", v)}>密码 21 | 22 | {radioData.map(i => ( 23 | handleTextChange("type", i.type)} 27 | > 28 | {i.text} 29 | 30 | ))} 31 | 32 | 33 | 36 | 37 |
38 | ); 39 | 40 | RegisterForm.propTypes = { 41 | register: PropTypes.func.isRequired, 42 | radioData: PropTypes.array.isRequired, 43 | handleTextChange: PropTypes.func.isRequired, 44 | type: PropTypes.string.isRequired 45 | }; 46 | 47 | export default RegisterForm; 48 | -------------------------------------------------------------------------------- /chat-buy-react/src/container/myOrder.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { getMyOrders } from "../actions/user"; 4 | import { affirmOrder } from "../actions/order"; 5 | import MyOrderItem from "../components/myOrder/myOrderItem"; 6 | import NavBar from "../components/navBar/backNavBar"; 7 | 8 | @connect(state => ({ user: state.get("user") }), { getMyOrders, affirmOrder }) 9 | class MyOrder extends React.Component { 10 | constructor() { 11 | super(); 12 | this.handleChat = this.handleChat.bind(this); 13 | } 14 | componentDidMount() { 15 | this.props.getMyOrders(); 16 | } 17 | handleChat(id) { 18 | this.props.history.push(`/chat/${id}`); 19 | } 20 | render() { 21 | const { history, affirmOrder, user } = this.props; 22 | return ( 23 |
24 | 25 |
26 | {user 27 | .get("orders") 28 | .map(v => ( 29 | 37 | ))} 38 |
39 |
40 | ); 41 | } 42 | } 43 | 44 | export default MyOrder; 45 | -------------------------------------------------------------------------------- /chat-buy-react/src/actions/goods.js: -------------------------------------------------------------------------------- 1 | import { GOODS_LIST, ADD_TO_CART, BUY_SUCCESS } from "./type"; 2 | import { Toast } from "antd-mobile"; 3 | import axios from "axios"; 4 | import { fromJS } from "immutable"; 5 | 6 | // 获取商品列表 7 | export function getGoodsInfo() { 8 | return async dispatch => { 9 | try { 10 | const res = await axios.get("/goods/list"); 11 | if (res.status === 200 && res.data.code === 0) { 12 | dispatch({ type: GOODS_LIST, payload: fromJS(res.data.data) }); 13 | } 14 | } catch (error) { 15 | console.log(error) 16 | } 17 | }; 18 | } 19 | 20 | // 添加商品到购物车 21 | export function addToCart({ id, price, count }) { 22 | if (!Number.isInteger(count)) { 23 | count = 0; 24 | } 25 | if (count > 99) { 26 | count = 99; 27 | } 28 | if (count < 0) { 29 | count = 0; 30 | } 31 | 32 | return { type: ADD_TO_CART, payload: { id, price, count } }; 33 | } 34 | 35 | // 购买 36 | export function buy() { 37 | return async (dispatch, state) => { 38 | try { 39 | const res = await axios.post("/goods/buy", { 40 | buyList: state() 41 | .get("goods") 42 | .get("shopCart") 43 | .toJS() 44 | }); 45 | if (res.status === 200 && res.data.code === 0) { 46 | Toast.success("购买成功", 1); 47 | dispatch({ type: BUY_SUCCESS }); 48 | } 49 | } catch (error) { 50 | console.log(error) 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /chat-buy-react/src/container/message.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import MessageList from "../components/message/messageList"; 3 | import { setCurrentChatList } from "../actions/chat"; 4 | import { connect } from "react-redux"; 5 | 6 | @connect(state => ({ chat: state.get("chat") }), { setCurrentChatList }) 7 | class Message extends React.Component { 8 | constructor() { 9 | super(); 10 | this.handlePush = this.handlePush.bind(this); 11 | this.getSideObj = this.getSideObj.bind(this); 12 | } 13 | // 找出聊天对象昵称 14 | getSideObj(userId, bothSide) { 15 | return bothSide.find(v => v.get("user") !== userId); 16 | } 17 | handlePush(value) { 18 | const { chat, setCurrentChatList, history } = this.props; 19 | const userId = chat.get("userId"); 20 | setCurrentChatList(value); 21 | history.push( 22 | `/chat/${this.getSideObj(userId, value.get("bothSide")).get("user")}` 23 | ); 24 | } 25 | render() { 26 | const { chat } = this.props; 27 | const messageList = chat.get("messageList"); 28 | const userId = chat.get("userId"); 29 | const noReadCounts = chat.get("noReadCounts"); 30 | return ( 31 |
32 | 39 |
40 | ); 41 | } 42 | } 43 | 44 | export default Message; 45 | -------------------------------------------------------------------------------- /chat-buy-react/src/components/goods/goodsList.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { List, Stepper } from "antd-mobile"; 3 | import ImmutablePropTypes from "react-immutable-proptypes"; 4 | import PropTypes from "prop-types"; 5 | 6 | const Item = List.Item; 7 | const Brief = Item.Brief; 8 | 9 | function getCount(shopCart, id) { 10 | let obj = shopCart.find(value => { 11 | return id === value.get("id"); 12 | }); 13 | return obj ? obj.get("count") : 0; 14 | } 15 | 16 | const GoodsList = ({ goodsList, addToCart, shopCart }) => { 17 | return ( 18 | 19 | {goodsList.map(v => ( 20 | 31 | addToCart({ id: v.get("id"), price: v.get("price"), count }) 32 | } 33 | /> 34 | } 35 | > 36 | {v.get("name")} ¥{v.get("price")} 37 | 38 | ))} 39 | 40 | ); 41 | }; 42 | 43 | GoodsList.propTypes = { 44 | goodsList: ImmutablePropTypes.list.isRequired, 45 | addToCart: PropTypes.func.isRequired, 46 | shopCart: ImmutablePropTypes.list.isRequired 47 | }; 48 | 49 | export default GoodsList; 50 | -------------------------------------------------------------------------------- /chat-buy-react/server/goods.js: -------------------------------------------------------------------------------- 1 | 2 | const model = require("./model"); 3 | const foods = require("./foods.json"); 4 | const User = model.user; 5 | const AllOrders = model.allOrders; 6 | 7 | 8 | module.exports = function(app) { 9 | // 获取商品列表 10 | app.get("/goods/list", function(req, res) { 11 | return res.json({ code: 0, data: foods.list }); 12 | }); 13 | // 购买商品 14 | app.post("/goods/buy", function(req, res) { 15 | const { id } = req.decoded; 16 | const { buyList } = req.body; 17 | let price = 0; 18 | let count = 0; 19 | let desc = ""; 20 | buyList.forEach(v => { 21 | price += v.price * v.count; 22 | count += v.count; 23 | if (!desc) { 24 | desc = foods.list.find(food => { 25 | return food.id === v.id; 26 | }).name; 27 | } 28 | }); 29 | const order = { 30 | price, 31 | count, 32 | state: 0, 33 | desc, 34 | customer: id 35 | }; 36 | const model = new AllOrders(order); 37 | model.save(function(error, data) { 38 | if (error || !data) { 39 | return res.status(500).json({ msg: "后端出错" }); 40 | } 41 | 42 | User.update( 43 | { _id: id }, 44 | { 45 | $push: { 46 | orders: data._id 47 | } 48 | }, 49 | function(e, user) { 50 | if (e || !user) { 51 | return res.status(500).json({ msg: "后端出错" }); 52 | } 53 | 54 | return res.status(200).json({ code: 0, msg: "购买成功" }); 55 | } 56 | ); 57 | }); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /chat-buy-react/src/components/message/messageList.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { List } from "antd-mobile"; 3 | import PropTypes from "prop-types"; 4 | import ImmutablePropTypes from "react-immutable-proptypes"; 5 | 6 | const Item = List.Item; 7 | const Brief = Item.Brief; 8 | 9 | const MessageList = ({ 10 | messageList, 11 | noReadCounts, 12 | push, 13 | userId, 14 | getSideObj 15 | }) => ( 16 |
17 | {!!messageList.size && ( 18 | 19 | {messageList.map((v, index) => ( 20 | 26 | {noReadCounts.get(index)} 27 | 28 | ) 29 | } 30 | onClick={() => { 31 | push(v); 32 | }} 33 | > 34 | {getSideObj(userId, v.get("bothSide")).get("name")} 35 | 36 | {v 37 | .get("messages") 38 | .get(-1) 39 | .get("message")} 40 | 41 | 42 | ))} 43 | 44 | )} 45 |
46 | ); 47 | 48 | MessageList.propTypes = { 49 | messageList: ImmutablePropTypes.list.isRequired, 50 | noReadCounts: ImmutablePropTypes.list.isRequired, 51 | getSideObj: PropTypes.func.isRequired, 52 | push: PropTypes.func.isRequired, 53 | userId: PropTypes.string.isRequired 54 | }; 55 | 56 | export default MessageList; 57 | -------------------------------------------------------------------------------- /chat-buy-react/src/actions/order.js: -------------------------------------------------------------------------------- 1 | import { GET_ALL_ORDERS, AFFIRM_ORDER, GET_ORDER_SUCCESS } from "./type"; 2 | 3 | import { Toast } from "antd-mobile"; 4 | import axios from "axios"; 5 | import history from "../common/history"; 6 | import { fromJS } from "immutable"; 7 | 8 | export function getAllOrders() { 9 | return async dispatch => { 10 | try { 11 | const res = await axios.get("/order/allOrders"); 12 | if (res.status === 200 && res.data.code === 0) { 13 | dispatch({ type: GET_ALL_ORDERS, payload: fromJS(res.data.data) }); 14 | } 15 | } catch (error) { 16 | console.log(error) 17 | } 18 | }; 19 | } 20 | 21 | export function getOrderSuccess(data) { 22 | return { type: GET_ORDER_SUCCESS, payload: data }; 23 | } 24 | 25 | export function affirmOrderSuccess(id) { 26 | return { type: AFFIRM_ORDER, payload: id }; 27 | } 28 | 29 | export function getOrder(orderId) { 30 | return async dispatch => { 31 | try { 32 | const res = await axios.post("/order/getOrder", { orderId }); 33 | if (res.status === 200 && res.data.code === 0) { 34 | history.push("/me/orders"); 35 | Toast.success("接单成功", 1); 36 | } 37 | } catch (error) { 38 | console.log(error) 39 | } 40 | }; 41 | } 42 | 43 | export function affirmOrder(orderId) { 44 | return async dispatch => { 45 | try { 46 | const res = await axios.post("/order/affirm", { orderId }); 47 | if (res.status === 200 && res.data.code === 0) { 48 | Toast.success("确认订单成功", 1); 49 | dispatch(affirmOrderSuccess(orderId)); 50 | } 51 | } catch (error) { 52 | console.log(error) 53 | } 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /chat-buy-react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /chat-buy-react/src/common/axiosMiddleware.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { Toast } from "antd-mobile"; 3 | import configureStore from "../store/configureStore"; 4 | import { logout } from "../actions/user"; 5 | 6 | const store = configureStore(); 7 | 8 | axios.defaults.timeout = 5000; 9 | // 超时重试次数和间隔 10 | axios.defaults.maxRetryCount = 2; 11 | axios.defaults.delay = 1000; 12 | 13 | axios.interceptors.request.use(config => { 14 | Toast.loading(config.loadText || "正在加载", 0); 15 | if (window.localStorage.getItem("token")) { 16 | config.headers["x-access-token"] = window.localStorage.getItem("token"); 17 | } 18 | return config; 19 | }); 20 | 21 | axios.interceptors.response.use( 22 | response => { 23 | Toast.hide(); 24 | const data = response.data; 25 | if (data.code === 1) { 26 | Toast.fail(data.msg, 1); 27 | } 28 | return response; 29 | }, 30 | error => { 31 | Toast.hide(); 32 | if (error.response) { 33 | switch (error.response.status) { 34 | case 401: 35 | store.dispatch(logout()); 36 | break; 37 | case 500: 38 | Toast.fail(error.response.data.msg || "服务器出错啦", 1); 39 | break; 40 | default: 41 | break; 42 | } 43 | return Promise.reject(error); 44 | } else { 45 | // 请求超时重试机制 46 | Toast.hide(); 47 | const config = error.config; 48 | config.loadText = "请求超时,正在重试"; 49 | console.log(error); 50 | if (!config || !config.maxRetryCount) return Promise.reject(error); 51 | config.retryCount = config.retryCount || 0; 52 | if (config.retryCount >= config.maxRetryCount) { 53 | Toast.fail("还是超时,不请求了", 2); 54 | return Promise.reject(error); 55 | } 56 | config.retryCount += 1; 57 | const request = new Promise(resolve => { 58 | setTimeout(() => resolve(), config.delay || 1); 59 | }); 60 | return request.then(() => { 61 | axios(config); 62 | }); 63 | } 64 | } 65 | ); 66 | -------------------------------------------------------------------------------- /chat-buy-react/src/reducers/goods.js: -------------------------------------------------------------------------------- 1 | import { GOODS_LIST, ADD_TO_CART, BUY_SUCCESS } from "../actions/type"; 2 | import { List, Map } from "immutable"; 3 | 4 | const initialState = Map({ 5 | // 当前商品列表 6 | goodsList: List([]), 7 | // 购物车 8 | shopCart: List([]), 9 | // 总价 10 | totalPrice: 0 11 | }); 12 | 13 | /** 14 | * @param {} shopCart 当前购物车 15 | * @param {} {id 商品 ID 16 | * @param {} price 商品价格 17 | * @param {} count} 商品件数 18 | * @param {} totalPrice 当前总价 19 | */ 20 | function changeShopCart(shopCart, { id, price, count }, totalPrice) { 21 | // 在当前购物车寻找是否已经添加过该商品 22 | const index = shopCart.findIndex(item => item.get("id") === id); 23 | if (index === -1) { 24 | // 判断当前购物车是否找到该商品,没有就 push 商品,并修改总价 25 | if (count === 0) { 26 | return [shopCart, totalPrice]; 27 | } 28 | shopCart = shopCart.push(Map({ id, price, count })); 29 | totalPrice += price * count; 30 | return [shopCart, totalPrice]; 31 | } 32 | shopCart = shopCart.update(index, product => { 33 | // 如果在购物车中找到该商品,修改总价 34 | const currentCount = product.get("count"); 35 | if (currentCount > count) { 36 | totalPrice = totalPrice - (currentCount - count) * price; 37 | } else { 38 | totalPrice = totalPrice + (count - currentCount) * price; 39 | } 40 | return product.set("count", count); 41 | }); 42 | if (count === 0) { 43 | // 判断如果该商品件数等于0,就删除购物中的当前商品 44 | shopCart = shopCart.remove(index); 45 | return [shopCart, totalPrice]; 46 | } else { 47 | return [shopCart, totalPrice]; 48 | } 49 | } 50 | 51 | export default function(state = initialState, action) { 52 | switch (action.type) { 53 | case GOODS_LIST: 54 | return state.set("goodsList", action.payload); 55 | case ADD_TO_CART: 56 | let data = changeShopCart( 57 | state.get("shopCart"), 58 | action.payload, 59 | state.get("totalPrice") 60 | ); 61 | return state.merge({ shopCart: data[0], totalPrice: data[1] }); 62 | case BUY_SUCCESS: 63 | return state.merge({ shopCart: List([]), totalPrice: 0 }); 64 | default: 65 | break; 66 | } 67 | return state; 68 | } 69 | -------------------------------------------------------------------------------- /chat-buy-react/src/reducers/chat.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_USERNAME, 3 | GET_MESSAGE, 4 | GET_MESSAGE_LIST, 5 | SET_CURRENT_LIST, 6 | CLEAN_NO_READ 7 | } from "../actions/type"; 8 | import { Map, List } from "immutable"; 9 | 10 | const initialState = Map({ 11 | // 当前聊天对象昵称 12 | chatUserName: "", 13 | // 当前聊天列表 14 | currentChatList: List([]), 15 | // 当前聊天 ID 16 | currentMessageId: "", 17 | // 所有消息列表 18 | messageList: List([]), 19 | // 当前用户 ID 20 | userId: "", 21 | // 未读消息总数 22 | noReadCount: 0, 23 | // 未读消息数组,对应每个消息 24 | noReadCounts: List([]) 25 | }); 26 | 27 | // 给消息列表按照时间顺序排序 28 | function sortMessageList(list) { 29 | return list.sort((a, b) => { 30 | return ( 31 | a 32 | .get("messages") 33 | .get(-1) 34 | .get("date") < 35 | b 36 | .get("messages") 37 | .get(-1) 38 | .get("date") 39 | ); 40 | }); 41 | } 42 | 43 | export default function(state = initialState, action) { 44 | switch (action.type) { 45 | case GET_USERNAME: 46 | return state.merge({ userName: action.payload }); 47 | case GET_MESSAGE: 48 | return state.merge({ 49 | messageList: action.messageList, 50 | noReadCount: state.get("noReadCount") + action.isNoRead, 51 | currentChatList: action.currentChatList, 52 | noReadCounts: action.noReadCounts 53 | }); 54 | case GET_MESSAGE_LIST: 55 | return state.merge({ 56 | messageList: sortMessageList(action.payload), 57 | userId: action.userId, 58 | noReadCounts: action.noReadCounts, 59 | noReadCount: action.noReadCounts.reduce((sum, value) => sum + value, 0) 60 | }); 61 | case SET_CURRENT_LIST: 62 | return state.merge({ 63 | currentChatList: action.payload 64 | ? action.payload.get("messages") 65 | : List([]), 66 | currentMessageId: action.messageId 67 | }); 68 | case CLEAN_NO_READ: 69 | return state.merge({ 70 | noReadCounts: action.noReadCounts, 71 | noReadCount: state.get("noReadCount") - action.readCount 72 | }); 73 | default: 74 | return state; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /chat-buy-react/src/reducers/user.js: -------------------------------------------------------------------------------- 1 | import { 2 | REGISTER, 3 | LOGIN, 4 | GET_INFO, 5 | GET_MY_ORDERS, 6 | AFFIRM_ORDER, 7 | GET_ORDER_SUCCESS 8 | } from "../actions/type"; 9 | import { List, Map } from "immutable"; 10 | 11 | function changeOrderState(orders, id, state, userId) { 12 | return orders.update(orders.findIndex(v => v.get("_id") === id), order => { 13 | // 如果该订单被接单,需要手动加上外卖员的 ID 用以聊天 14 | if (userId) { 15 | return order.set("state", state).set("deliver", userId); 16 | } else { 17 | return order.set("state", state); 18 | } 19 | }); 20 | } 21 | 22 | const initialState = Map({ 23 | // 用户名 24 | user: "", 25 | // 用户类型 26 | type: "", 27 | // 用户 ID 28 | id: "", 29 | // 用户可以跳转的路由 30 | path: "", 31 | // 我的所有订单 32 | orders: List([]) 33 | }); 34 | 35 | export default function(state = initialState, action) { 36 | switch (action.type) { 37 | case REGISTER: 38 | return state.merge({ 39 | ...action.payload, 40 | path: action.payload.type === "customer" ? "/goods" : "/allOrders" 41 | }); 42 | case LOGIN: 43 | console.log( 44 | state.merge({ 45 | ...action.payload, 46 | path: action.payload.type === "customer" ? "/goods" : "/allOrders" 47 | }) 48 | ); 49 | return state.merge({ 50 | ...action.payload, 51 | path: action.payload.type === "customer" ? "/goods" : "/allOrders" 52 | }); 53 | case GET_INFO: 54 | return state.merge({ 55 | ...action.payload, 56 | path: action.payload.type === "customer" ? "/goods" : "/allOrders" 57 | }); 58 | case GET_MY_ORDERS: 59 | return state.set("orders", action.payload); 60 | case AFFIRM_ORDER: 61 | return state.set( 62 | "orders", 63 | changeOrderState(state.get("orders"), action.payload, 2) 64 | ); 65 | case GET_ORDER_SUCCESS: 66 | return state.set( 67 | "orders", 68 | changeOrderState( 69 | state.get("orders"), 70 | action.payload.orderId, 71 | 1, 72 | action.payload.id 73 | ) 74 | ); 75 | default: 76 | break; 77 | } 78 | return state; 79 | } 80 | -------------------------------------------------------------------------------- /chat-buy-react/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right