├── .env.development ├── .env.production ├── .gitignore ├── README.md ├── config-overrides.js ├── config ├── env.js ├── getHttpsConfig.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── modules.js ├── paths.js ├── pnpTs.js ├── webpack.config.js └── webpackDevServer.config.js ├── package-lock.json ├── package.json ├── public ├── assets │ └── iconfont │ │ ├── iconfont.css │ │ ├── iconfont.eot │ │ ├── iconfont.js │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ └── iconfont.woff ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── scripts ├── build.js ├── start.js └── test.js └── src ├── App.css ├── App.js ├── api └── api.js ├── assets ├── images │ ├── address-b.svg │ ├── address.png │ ├── cart.png │ ├── cart_sprits_all.png │ ├── cate.png │ ├── defalutAvatar.jpg │ ├── goods │ │ └── 1.jpg │ ├── index.png │ ├── lgbg.jpg │ ├── msg@default.png │ ├── my.png │ ├── purse.png │ └── return.png └── styles │ ├── AntdSearchBar.scss │ ├── Cart.scss │ ├── Cate.scss │ ├── Collection.scss │ ├── CreateOrder.scss │ ├── GoodsListImg.scss │ ├── Home.scss │ ├── ListPage.scss │ ├── Login.scss │ ├── My.scss │ ├── Nav.scss │ ├── Register.scss │ ├── Search.scss │ ├── browse-record.scss │ ├── goodsdetail.scss │ └── mypurse.scss ├── components ├── Adverts.jsx ├── AntdSearchBar.jsx ├── Banner.jsx ├── Cart │ ├── CartItem.js │ └── CartList.js ├── FilterComponents.jsx ├── Goods │ ├── GoodsList.jsx │ ├── GoodsListItem.jsx │ └── GoodsListItem.scss ├── GoodsListImg.jsx ├── Header │ ├── SearchHeader.jsx │ └── search_header.scss ├── Layout.jsx ├── Nav.jsx ├── Order │ ├── OrderItem.jsx │ ├── OrderItem.scss │ └── OrderList.jsx ├── ScrollToTop .jsx ├── SearchInput.jsx └── index.js ├── index.css ├── index.js ├── pages ├── Cart.jsx ├── Cate.jsx ├── CreateOrder.jsx ├── GoodsDetail.jsx ├── Home.jsx ├── ListPage.jsx ├── Login.jsx ├── My │ ├── BrowseRecord.jsx │ ├── Collection.jsx │ ├── Follow.jsx │ ├── My.jsx │ ├── Mypurse.jsx │ ├── Order.jsx │ └── SetUp.jsx ├── NotFound.jsx ├── Register.jsx └── Search.jsx ├── router ├── PrivateRoute.js ├── index.js └── modules │ └── my.js ├── serviceWorker.js ├── store ├── action │ ├── cateAction.js │ ├── goodsAction.js │ ├── orderAction.js │ ├── routerAction.js │ ├── searchAction.js │ └── usersAction.js ├── actionType │ ├── cateType.js │ ├── goodsType.js │ ├── orderType.js │ ├── routerType.js │ ├── searchType.js │ └── usersType.js ├── index.js └── reducer │ ├── cateReducer.js │ ├── goodsReducer.js │ ├── index.js │ ├── orderReducer.js │ ├── routerReducer.js │ ├── searchReducer.js │ └── usersReducer.js └── utils ├── Tool.js ├── request.js ├── storage.js └── urls.js /.env.development: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL=http://localhost:8090/api 2 | REACT_APP_STATIC_URL=http://localhost:8090 -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL=http://47.100.138.242:8090/api 2 | REACT_APP_STATIC_URL=http://47.100.138.242:8090 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 预览地址: 2 | http://47.100.138.242:8090/#/login 3 | 4 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 5 | 6 | ## Available Scripts 7 | 8 | In the project directory, you can run: 9 | 10 | ### `npm start` 11 | 12 | Runs the app in the development mode.
13 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 14 | 15 | The page will reload if you make edits.
16 | You will also see any lint errors in the console. 17 | 18 | ### `npm test` 19 | 20 | Launches the test runner in the interactive watch mode.
21 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 22 | 23 | ### `npm run build` 24 | 25 | Builds the app for production to the `build` folder.
26 | It correctly bundles React in production mode and optimizes the build for the best performance. 27 | 28 | The build is minified and the filenames include the hashes.
29 | Your app is ready to be deployed! 30 | 31 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 32 | 33 | ### `npm run eject` 34 | 35 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 36 | 37 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 38 | 39 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 40 | 41 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 42 | 43 | ## Learn More 44 | 45 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 46 | 47 | To learn React, check out the [React documentation](https://reactjs.org/). 48 | 49 | ### Code Splitting 50 | 51 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 52 | 53 | ### Analyzing the Bundle Size 54 | 55 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 56 | 57 | ### Making a Progressive Web App 58 | 59 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 60 | 61 | ### Advanced Configuration 62 | 63 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 64 | 65 | ### Deployment 66 | 67 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 68 | 69 | ### `npm run build` fails to minify 70 | 71 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 72 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const { override, fixBabelImports } = require('customize-cra'); 2 | const rewirePostcss = require('react-app-rewire-postcss'); 3 | const px2rem = require('postcss-px2rem') 4 | 5 | module.exports = override( 6 | fixBabelImports('import', { 7 | libraryName: 'antd-mobile', 8 | style: 'css', 9 | }), 10 | (config,env)=>{ 11 | // 重写postcss 12 | rewirePostcss(config,{ 13 | plugins: () => [ 14 | require('postcss-flexbugs-fixes'), 15 | require('postcss-preset-env')({ 16 | autoprefixer: { 17 | flexbox: 'no-2009', 18 | }, 19 | stage: 3, 20 | }), 21 | //关键:设置px2rem 22 | px2rem({ 23 | remUnit: 37.5, 24 | exclude:/node-modules/ 25 | }) 26 | ], 27 | }); 28 | return config 29 | } 30 | ); -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | const dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. Variable expansion is supported in .env files. 31 | // https://github.com/motdotla/dotenv 32 | // https://github.com/motdotla/dotenv-expand 33 | dotenvFiles.forEach(dotenvFile => { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebook/create-react-app/issues/253. 46 | // It works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of webpack shims. 50 | // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | // We support configuring the sockjs pathname during development. 81 | // These settings let a developer run multiple simultaneous projects. 82 | // They are used as the connection `hostname`, `pathname` and `port` 83 | // in webpackHotDevClient. They are used as the `sockHost`, `sockPath` 84 | // and `sockPort` options in webpack-dev-server. 85 | WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST, 86 | WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH, 87 | WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT, 88 | } 89 | ); 90 | // Stringify all values so we can feed into webpack DefinePlugin 91 | const stringified = { 92 | 'process.env': Object.keys(raw).reduce((env, key) => { 93 | env[key] = JSON.stringify(raw[key]); 94 | return env; 95 | }, {}), 96 | }; 97 | 98 | return { raw, stringified }; 99 | } 100 | 101 | module.exports = getClientEnvironment; 102 | -------------------------------------------------------------------------------- /config/getHttpsConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const crypto = require('crypto'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const paths = require('./paths'); 8 | 9 | // Ensure the certificate and key provided are valid and if not 10 | // throw an easy to debug error 11 | function validateKeyAndCerts({ cert, key, keyFile, crtFile }) { 12 | let encrypted; 13 | try { 14 | // publicEncrypt will throw an error with an invalid cert 15 | encrypted = crypto.publicEncrypt(cert, Buffer.from('test')); 16 | } catch (err) { 17 | throw new Error( 18 | `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}` 19 | ); 20 | } 21 | 22 | try { 23 | // privateDecrypt will throw an error with an invalid key 24 | crypto.privateDecrypt(key, encrypted); 25 | } catch (err) { 26 | throw new Error( 27 | `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${ 28 | err.message 29 | }` 30 | ); 31 | } 32 | } 33 | 34 | // Read file and throw an error if it doesn't exist 35 | function readEnvFile(file, type) { 36 | if (!fs.existsSync(file)) { 37 | throw new Error( 38 | `You specified ${chalk.cyan( 39 | type 40 | )} in your env, but the file "${chalk.yellow(file)}" can't be found.` 41 | ); 42 | } 43 | return fs.readFileSync(file); 44 | } 45 | 46 | // Get the https config 47 | // Return cert files if provided in env, otherwise just true or false 48 | function getHttpsConfig() { 49 | const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env; 50 | const isHttps = HTTPS === 'true'; 51 | 52 | if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) { 53 | const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE); 54 | const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE); 55 | const config = { 56 | cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), 57 | key: readEnvFile(keyFile, 'SSL_KEY_FILE'), 58 | }; 59 | 60 | validateKeyAndCerts({ ...config, keyFile, crtFile }); 61 | return config; 62 | } 63 | return isHttps; 64 | } 65 | 66 | module.exports = getHttpsConfig; 67 | -------------------------------------------------------------------------------- /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/en/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 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFilename}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /config/modules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const resolve = require('resolve'); 8 | 9 | /** 10 | * Get additional module paths based on the baseUrl of a compilerOptions object. 11 | * 12 | * @param {Object} options 13 | */ 14 | function getAdditionalModulePaths(options = {}) { 15 | const baseUrl = options.baseUrl; 16 | 17 | // We need to explicitly check for null and undefined (and not a falsy value) because 18 | // TypeScript treats an empty string as `.`. 19 | if (baseUrl == null) { 20 | // If there's no baseUrl set we respect NODE_PATH 21 | // Note that NODE_PATH is deprecated and will be removed 22 | // in the next major release of create-react-app. 23 | 24 | const nodePath = process.env.NODE_PATH || ''; 25 | return nodePath.split(path.delimiter).filter(Boolean); 26 | } 27 | 28 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 29 | 30 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is 31 | // the default behavior. 32 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { 33 | return null; 34 | } 35 | 36 | // Allow the user set the `baseUrl` to `appSrc`. 37 | if (path.relative(paths.appSrc, baseUrlResolved) === '') { 38 | return [paths.appSrc]; 39 | } 40 | 41 | // If the path is equal to the root directory we ignore it here. 42 | // We don't want to allow importing from the root directly as source files are 43 | // not transpiled outside of `src`. We do allow importing them with the 44 | // absolute path (e.g. `src/Components/Button.js`) but we set that up with 45 | // an alias. 46 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 47 | return null; 48 | } 49 | 50 | // Otherwise, throw an error. 51 | throw new Error( 52 | chalk.red.bold( 53 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." + 54 | ' Create React App does not support other values at this time.' 55 | ) 56 | ); 57 | } 58 | 59 | /** 60 | * Get webpack aliases based on the baseUrl of a compilerOptions object. 61 | * 62 | * @param {*} options 63 | */ 64 | function getWebpackAliases(options = {}) { 65 | const baseUrl = options.baseUrl; 66 | 67 | if (!baseUrl) { 68 | return {}; 69 | } 70 | 71 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 72 | 73 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 74 | return { 75 | src: paths.appSrc, 76 | }; 77 | } 78 | } 79 | 80 | /** 81 | * Get jest aliases based on the baseUrl of a compilerOptions object. 82 | * 83 | * @param {*} options 84 | */ 85 | function getJestAliases(options = {}) { 86 | const baseUrl = options.baseUrl; 87 | 88 | if (!baseUrl) { 89 | return {}; 90 | } 91 | 92 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 93 | 94 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 95 | return { 96 | '^src/(.*)$': '/src/$1', 97 | }; 98 | } 99 | } 100 | 101 | function getModules() { 102 | // Check if TypeScript is setup 103 | const hasTsConfig = fs.existsSync(paths.appTsConfig); 104 | const hasJsConfig = fs.existsSync(paths.appJsConfig); 105 | 106 | if (hasTsConfig && hasJsConfig) { 107 | throw new Error( 108 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' 109 | ); 110 | } 111 | 112 | let config; 113 | 114 | // If there's a tsconfig.json we assume it's a 115 | // TypeScript project and set up the config 116 | // based on tsconfig.json 117 | if (hasTsConfig) { 118 | const ts = require(resolve.sync('typescript', { 119 | basedir: paths.appNodeModules, 120 | })); 121 | config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config; 122 | // Otherwise we'll check if there is jsconfig.json 123 | // for non TS projects. 124 | } else if (hasJsConfig) { 125 | config = require(paths.appJsConfig); 126 | } 127 | 128 | config = config || {}; 129 | const options = config.compilerOptions || {}; 130 | 131 | const additionalModulePaths = getAdditionalModulePaths(options); 132 | 133 | return { 134 | additionalModulePaths: additionalModulePaths, 135 | webpackAliases: getWebpackAliases(options), 136 | jestAliases: getJestAliases(options), 137 | hasTsConfig, 138 | }; 139 | } 140 | 141 | module.exports = getModules(); 142 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 13 | // "public path" at which the app is served. 14 | // webpack needs to know it to put the right 37 | 38 | 39 | 40 |
41 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/public/logo512.png -------------------------------------------------------------------------------- /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 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /scripts/build.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 = 'production'; 5 | process.env.NODE_ENV = 'production'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const path = require('path'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const fs = require('fs-extra'); 21 | const webpack = require('webpack'); 22 | const configFactory = require('../config/webpack.config'); 23 | const paths = require('../config/paths'); 24 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 25 | const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); 26 | const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); 27 | const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); 28 | const printBuildError = require('react-dev-utils/printBuildError'); 29 | 30 | const measureFileSizesBeforeBuild = 31 | FileSizeReporter.measureFileSizesBeforeBuild; 32 | const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; 33 | const useYarn = fs.existsSync(paths.yarnLockFile); 34 | 35 | // These sizes are pretty large. We'll warn for bundles exceeding them. 36 | const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; 37 | const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; 38 | 39 | const isInteractive = process.stdout.isTTY; 40 | 41 | // Warn and crash if required files are missing 42 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 43 | process.exit(1); 44 | } 45 | 46 | // Generate configuration 47 | const config = configFactory('production'); 48 | 49 | // We require that you explicitly set browsers and do not fall back to 50 | // browserslist defaults. 51 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 52 | checkBrowsers(paths.appPath, isInteractive) 53 | .then(() => { 54 | // First, read the current file sizes in build directory. 55 | // This lets us display how much they changed later. 56 | return measureFileSizesBeforeBuild(paths.appBuild); 57 | }) 58 | .then(previousFileSizes => { 59 | // Remove all content but keep the directory so that 60 | // if you're in it, you don't end up in Trash 61 | fs.emptyDirSync(paths.appBuild); 62 | // Merge with the public folder 63 | copyPublicFolder(); 64 | // Start the webpack build 65 | return build(previousFileSizes); 66 | }) 67 | .then( 68 | ({ stats, previousFileSizes, warnings }) => { 69 | if (warnings.length) { 70 | console.log(chalk.yellow('Compiled with warnings.\n')); 71 | console.log(warnings.join('\n\n')); 72 | console.log( 73 | '\nSearch for the ' + 74 | chalk.underline(chalk.yellow('keywords')) + 75 | ' to learn more about each warning.' 76 | ); 77 | console.log( 78 | 'To ignore, add ' + 79 | chalk.cyan('// eslint-disable-next-line') + 80 | ' to the line before.\n' 81 | ); 82 | } else { 83 | console.log(chalk.green('Compiled successfully.\n')); 84 | } 85 | 86 | console.log('File sizes after gzip:\n'); 87 | printFileSizesAfterBuild( 88 | stats, 89 | previousFileSizes, 90 | paths.appBuild, 91 | WARN_AFTER_BUNDLE_GZIP_SIZE, 92 | WARN_AFTER_CHUNK_GZIP_SIZE 93 | ); 94 | console.log(); 95 | 96 | const appPackage = require(paths.appPackageJson); 97 | const publicUrl = paths.publicUrlOrPath; 98 | const publicPath = config.output.publicPath; 99 | const buildFolder = path.relative(process.cwd(), paths.appBuild); 100 | printHostingInstructions( 101 | appPackage, 102 | publicUrl, 103 | publicPath, 104 | buildFolder, 105 | useYarn 106 | ); 107 | }, 108 | err => { 109 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; 110 | if (tscCompileOnError) { 111 | console.log( 112 | chalk.yellow( 113 | 'Compiled with the following type errors (you may want to check these before deploying your app):\n' 114 | ) 115 | ); 116 | printBuildError(err); 117 | } else { 118 | console.log(chalk.red('Failed to compile.\n')); 119 | printBuildError(err); 120 | process.exit(1); 121 | } 122 | } 123 | ) 124 | .catch(err => { 125 | if (err && err.message) { 126 | console.log(err.message); 127 | } 128 | process.exit(1); 129 | }); 130 | 131 | // Create the production build and print the deployment instructions. 132 | function build(previousFileSizes) { 133 | // We used to support resolving modules according to `NODE_PATH`. 134 | // This now has been deprecated in favor of jsconfig/tsconfig.json 135 | // This lets you use absolute paths in imports inside large monorepos: 136 | if (process.env.NODE_PATH) { 137 | console.log( 138 | chalk.yellow( 139 | 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' 140 | ) 141 | ); 142 | console.log(); 143 | } 144 | 145 | console.log('Creating an optimized production build...'); 146 | 147 | const compiler = webpack(config); 148 | return new Promise((resolve, reject) => { 149 | compiler.run((err, stats) => { 150 | let messages; 151 | if (err) { 152 | if (!err.message) { 153 | return reject(err); 154 | } 155 | 156 | let errMessage = err.message; 157 | 158 | // Add additional information for postcss errors 159 | if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) { 160 | errMessage += 161 | '\nCompileError: Begins at CSS selector ' + 162 | err['postcssNode'].selector; 163 | } 164 | 165 | messages = formatWebpackMessages({ 166 | errors: [errMessage], 167 | warnings: [], 168 | }); 169 | } else { 170 | messages = formatWebpackMessages( 171 | stats.toJson({ all: false, warnings: true, errors: true }) 172 | ); 173 | } 174 | if (messages.errors.length) { 175 | // Only keep the first error. Others are often indicative 176 | // of the same problem, but confuse the reader with noise. 177 | if (messages.errors.length > 1) { 178 | messages.errors.length = 1; 179 | } 180 | return reject(new Error(messages.errors.join('\n\n'))); 181 | } 182 | if ( 183 | process.env.CI && 184 | (typeof process.env.CI !== 'string' || 185 | process.env.CI.toLowerCase() !== 'false') && 186 | messages.warnings.length 187 | ) { 188 | console.log( 189 | chalk.yellow( 190 | '\nTreating warnings as errors because process.env.CI = true.\n' + 191 | 'Most CI servers set it automatically.\n' 192 | ) 193 | ); 194 | return reject(new Error(messages.warnings.join('\n\n'))); 195 | } 196 | 197 | return resolve({ 198 | stats, 199 | previousFileSizes, 200 | warnings: messages.warnings, 201 | }); 202 | }); 203 | }); 204 | } 205 | 206 | function copyPublicFolder() { 207 | fs.copySync(paths.appPublic, paths.appBuild, { 208 | dereference: true, 209 | filter: file => file !== paths.appHtml, 210 | }); 211 | } 212 | -------------------------------------------------------------------------------- /scripts/start.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 = 'development'; 5 | process.env.NODE_ENV = 'development'; 6 | 7 | // Makes the script crash on unhandled rejections instead of silently 8 | // ignoring them. In the future, promise rejections that are not handled will 9 | // terminate the Node.js process with a non-zero exit code. 10 | process.on('unhandledRejection', err => { 11 | throw err; 12 | }); 13 | 14 | // Ensure environment variables are read. 15 | require('../config/env'); 16 | 17 | 18 | const fs = require('fs'); 19 | const chalk = require('react-dev-utils/chalk'); 20 | const webpack = require('webpack'); 21 | const WebpackDevServer = require('webpack-dev-server'); 22 | const clearConsole = require('react-dev-utils/clearConsole'); 23 | const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); 24 | const { 25 | choosePort, 26 | createCompiler, 27 | prepareProxy, 28 | prepareUrls, 29 | } = require('react-dev-utils/WebpackDevServerUtils'); 30 | const openBrowser = require('react-dev-utils/openBrowser'); 31 | const paths = require('../config/paths'); 32 | const configFactory = require('../config/webpack.config'); 33 | const createDevServerConfig = require('../config/webpackDevServer.config'); 34 | 35 | const useYarn = fs.existsSync(paths.yarnLockFile); 36 | const isInteractive = process.stdout.isTTY; 37 | 38 | // Warn and crash if required files are missing 39 | if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { 40 | process.exit(1); 41 | } 42 | 43 | // Tools like Cloud9 rely on this. 44 | const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; 45 | const HOST = process.env.HOST || '0.0.0.0'; 46 | 47 | if (process.env.HOST) { 48 | console.log( 49 | chalk.cyan( 50 | `Attempting to bind to HOST environment variable: ${chalk.yellow( 51 | chalk.bold(process.env.HOST) 52 | )}` 53 | ) 54 | ); 55 | console.log( 56 | `If this was unintentional, check that you haven't mistakenly set it in your shell.` 57 | ); 58 | console.log( 59 | `Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}` 60 | ); 61 | console.log(); 62 | } 63 | 64 | // We require that you explicitly set browsers and do not fall back to 65 | // browserslist defaults. 66 | const { checkBrowsers } = require('react-dev-utils/browsersHelper'); 67 | checkBrowsers(paths.appPath, isInteractive) 68 | .then(() => { 69 | // We attempt to use the default port but if it is busy, we offer the user to 70 | // run on a different port. `choosePort()` Promise resolves to the next free port. 71 | return choosePort(HOST, DEFAULT_PORT); 72 | }) 73 | .then(port => { 74 | if (port == null) { 75 | // We have not found a port. 76 | return; 77 | } 78 | 79 | const config = configFactory('development'); 80 | const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; 81 | const appName = require(paths.appPackageJson).name; 82 | const useTypeScript = fs.existsSync(paths.appTsConfig); 83 | const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; 84 | const urls = prepareUrls( 85 | protocol, 86 | HOST, 87 | port, 88 | paths.publicUrlOrPath.slice(0, -1) 89 | ); 90 | const devSocket = { 91 | warnings: warnings => 92 | devServer.sockWrite(devServer.sockets, 'warnings', warnings), 93 | errors: errors => 94 | devServer.sockWrite(devServer.sockets, 'errors', errors), 95 | }; 96 | // Create a webpack compiler that is configured with custom messages. 97 | const compiler = createCompiler({ 98 | appName, 99 | config, 100 | devSocket, 101 | urls, 102 | useYarn, 103 | useTypeScript, 104 | tscCompileOnError, 105 | webpack, 106 | }); 107 | // Load proxy config 108 | const proxySetting = require(paths.appPackageJson).proxy; 109 | const proxyConfig = prepareProxy( 110 | proxySetting, 111 | paths.appPublic, 112 | paths.publicUrlOrPath 113 | ); 114 | // Serve webpack assets generated by the compiler over a web server. 115 | const serverConfig = createDevServerConfig( 116 | proxyConfig, 117 | urls.lanUrlForConfig 118 | ); 119 | const devServer = new WebpackDevServer(compiler, serverConfig); 120 | // Launch WebpackDevServer. 121 | devServer.listen(port, HOST, err => { 122 | if (err) { 123 | return console.log(err); 124 | } 125 | if (isInteractive) { 126 | clearConsole(); 127 | } 128 | 129 | // We used to support resolving modules according to `NODE_PATH`. 130 | // This now has been deprecated in favor of jsconfig/tsconfig.json 131 | // This lets you use absolute paths in imports inside large monorepos: 132 | if (process.env.NODE_PATH) { 133 | console.log( 134 | chalk.yellow( 135 | 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' 136 | ) 137 | ); 138 | console.log(); 139 | } 140 | 141 | console.log(chalk.cyan('Starting the development server...\n')); 142 | openBrowser(urls.localUrlForBrowser); 143 | }); 144 | 145 | ['SIGINT', 'SIGTERM'].forEach(function(sig) { 146 | process.on(sig, function() { 147 | devServer.close(); 148 | process.exit(); 149 | }); 150 | }); 151 | 152 | if (isInteractive || process.env.CI !== 'true') { 153 | // Gracefully exit when stdin ends 154 | process.stdin.on('end', function() { 155 | devServer.close(); 156 | process.exit(); 157 | }); 158 | process.stdin.resume(); 159 | } 160 | }) 161 | .catch(err => { 162 | if (err && err.message) { 163 | console.log(err.message); 164 | } 165 | process.exit(1); 166 | }); 167 | -------------------------------------------------------------------------------- /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 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--watchAll') === -1 && 45 | argv.indexOf('--watchAll=false') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .loading { 2 | position: absolute; 3 | left: 50%; 4 | top: 50%; 5 | transform: translate(-50% -50%); 6 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react' 2 | import './App.css' 3 | import { HashRouter as Router, Route, Switch, Redirect } from "react-router-dom" 4 | import { PrivateRoute } from './router/PrivateRoute' 5 | import routes from './router' 6 | // import NotFound from './pages/NotFound' 7 | 8 | function App() { 9 | return ( 10 | 11 | 12 | {/* 请求根路径时重定向到 /index 主页 */} 13 | 14 | { 15 | routes.map((item,index) => { 16 | const { component: Component, ...rest } = item 17 | return 20 | Loading...}> 21 | { 22 | !item.auth ? 23 | : 24 | } 25 | 26 | } 27 | key={index} /> 28 | }) 29 | } 30 | {/* 路径不匹配显示404页面 */} 31 | {/* */} 32 | 33 | 34 | ); 35 | } 36 | 37 | export default App; 38 | -------------------------------------------------------------------------------- /src/api/api.js: -------------------------------------------------------------------------------- 1 | import axios from '../utils/request' 2 | 3 | const Api = { 4 | login: '/user/login', 5 | register: '/user/register', 6 | getUserInfo: '/user/getUserInfo', 7 | 8 | getBanners: '/home/getBanners', 9 | getNavs: '/home/getNavs', 10 | getAdverts: '/home/getAdverts', 11 | 12 | getCate: '/cate', 13 | 14 | getCartList: '/alliance/cartList', 15 | 16 | getGoodsList: '/goods/list', 17 | getGoodInfo: '/alliance/goodInfo', 18 | getOrderList: '/alliance/order/list', 19 | } 20 | 21 | export { 22 | Api, 23 | axios 24 | } -------------------------------------------------------------------------------- /src/assets/images/address-b.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/address.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/src/assets/images/address.png -------------------------------------------------------------------------------- /src/assets/images/cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/src/assets/images/cart.png -------------------------------------------------------------------------------- /src/assets/images/cart_sprits_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/src/assets/images/cart_sprits_all.png -------------------------------------------------------------------------------- /src/assets/images/cate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/src/assets/images/cate.png -------------------------------------------------------------------------------- /src/assets/images/defalutAvatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/src/assets/images/defalutAvatar.jpg -------------------------------------------------------------------------------- /src/assets/images/goods/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/src/assets/images/goods/1.jpg -------------------------------------------------------------------------------- /src/assets/images/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/src/assets/images/index.png -------------------------------------------------------------------------------- /src/assets/images/lgbg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/src/assets/images/lgbg.jpg -------------------------------------------------------------------------------- /src/assets/images/msg@default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/src/assets/images/msg@default.png -------------------------------------------------------------------------------- /src/assets/images/my.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/src/assets/images/my.png -------------------------------------------------------------------------------- /src/assets/images/purse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/src/assets/images/purse.png -------------------------------------------------------------------------------- /src/assets/images/return.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ianj21/react_mall/99f222051477166d8497320b139801c9f5ce1935/src/assets/images/return.png -------------------------------------------------------------------------------- /src/assets/styles/AntdSearchBar.scss: -------------------------------------------------------------------------------- 1 | .search-head { 2 | height:44px !important; 3 | display: flex; 4 | background-color: #fff; 5 | border-bottom: 0.0625rem solid #eee; 6 | .center{ 7 | flex: 1; 8 | .am-search{ 9 | background-color: #fff; 10 | .am-search-input{ 11 | background-color: #f7f7f7; 12 | border-radius:44px; 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/assets/styles/Cart.scss: -------------------------------------------------------------------------------- 1 | .cart-page{ 2 | height: 100%; 3 | width: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | .cart-tip{ 7 | display: flex; 8 | align-items:center; 9 | justify-content: center; 10 | min-height: 60px; 11 | color:#555; 12 | } 13 | .edit{ 14 | padding-right:10px; 15 | font-size:14px; 16 | } 17 | .am-navbar-right{ 18 | color: #3c3c33; 19 | } 20 | .cart-body{ 21 | height:100%; 22 | flex: 1; 23 | display: flex; 24 | background-color:#fff; 25 | overflow-y: auto; 26 | .cart-main{ 27 | width:100%; 28 | >.am-list-item{ 29 | border-bottom: 0.0625rem solid #eee; 30 | .am-list-line::after{ 31 | background-color: transparent; 32 | } 33 | } 34 | >.am-list-item.am-list-item-active{ 35 | background-color: transparent; 36 | } 37 | .cart-c-title{ 38 | display: flex; 39 | align-items: center; 40 | justify-content: space-between; 41 | >div{ 42 | flex:1; 43 | } 44 | span{ 45 | font-size:14px; 46 | margin-left:10px; 47 | } 48 | .next{ 49 | width:14px; 50 | height:14px; 51 | display: inline-block; 52 | margin-left:10px; 53 | } 54 | i{ 55 | font-style: inherit; 56 | font-size:14px; 57 | display: block; 58 | padding:0 10px; 59 | } 60 | } 61 | .cart-c-body{ 62 | .am-list-item-active{ 63 | background-color: transparent; 64 | } 65 | .am-list-line{ 66 | padding-right: 0rem; 67 | } 68 | border-bottom: 0.0625rem solid #eee; 69 | .am-list-content{ 70 | position: relative; 71 | } 72 | .cart-c-item{ 73 | overflow: hidden; 74 | position: relative; 75 | padding:10px 10px 10px 0; 76 | box-sizing: border-box; 77 | .cart-c-check{ 78 | width:40px; 79 | display: flex; 80 | align-items: center; 81 | justify-content: center; 82 | height:calc(100% - 1.25rem); 83 | box-sizing: border-box; 84 | position: absolute; 85 | } 86 | } 87 | .cart-ci-left{ 88 | position: absolute; 89 | top: 0; 90 | left: 40px; 91 | height: 100%; 92 | overflow: hidden; 93 | display: flex; 94 | justify-content: center; 95 | align-items: center; 96 | img{ 97 | height:85px; 98 | width:85px; 99 | display: block; 100 | border:0.0625rem solid #eee; 101 | } 102 | } 103 | .cart-ci-right{ 104 | min-height:87px; 105 | margin-left:135px; 106 | margin-right:8px; 107 | display: flex; 108 | flex-direction: column; 109 | justify-content: space-between; 110 | 111 | .r-title{ 112 | font-size:15px; 113 | white-space:normal; 114 | font-weight:bold; 115 | overflow : hidden; 116 | max-height: 46px; 117 | span{ 118 | overflow : hidden; 119 | text-overflow: ellipsis; 120 | display: -webkit-box; 121 | /*! autoprefixer: ignore next */ 122 | -webkit-box-orient: vertical; 123 | white-space:inherit; 124 | -webkit-line-clamp: 2; 125 | } 126 | } 127 | .r-step{ 128 | display: flex; 129 | justify-content: space-between; 130 | align-items: center; 131 | .r-price{ 132 | color:#ff5b05; 133 | font-size:14px; 134 | span{ 135 | font-size:12px; 136 | } 137 | } 138 | .span-stepper{ 139 | width:100px; 140 | .am-stepper.showNumber { 141 | max-width: 100px; 142 | height: 36px; 143 | } 144 | .am-stepper-handler{ 145 | line-height: 28px; 146 | font-size: 14px; 147 | width: 26px; 148 | height: 26px; 149 | display:flex; 150 | justify-content: center; 151 | align-items: center; 152 | } 153 | .am-stepper-input{ 154 | font-size:14px; 155 | } 156 | } 157 | } 158 | } 159 | } 160 | } 161 | } 162 | .cart-footer{ 163 | display: flex; 164 | justify-content: space-between; 165 | border-top:0.0625rem solid #eee; 166 | background-color:rgba(255,255,255,.7); 167 | height:50px; 168 | &>div:nth-child(1){ 169 | width:50px; 170 | text-align: center; 171 | height:50px; 172 | padding-top: 5px; 173 | div{ 174 | font-size:12px; 175 | color:#555; 176 | } 177 | } 178 | .all-pirce{ 179 | flex:1; 180 | margin-right:10px; 181 | display: flex; 182 | align-items: center; 183 | p{ 184 | margin:0; 185 | padding:0; 186 | font-weight: 700; 187 | font-size:16px; 188 | text-align: right; 189 | width:100%; 190 | span:nth-child(2){ 191 | color: #ff5b05; 192 | } 193 | } 194 | } 195 | .cart-footer-right{ 196 | width: 110px; 197 | line-height: 50px; 198 | text-align: center; 199 | font-size: 16px; 200 | font-weight:bold; 201 | background-color: #d7d7d7; 202 | color:#888; 203 | span{ 204 | font-size: 10px; 205 | font-weight:400; 206 | } 207 | } 208 | div.active{ 209 | background-color: #0259fa; 210 | color:#fff; 211 | } 212 | } 213 | } -------------------------------------------------------------------------------- /src/assets/styles/Cate.scss: -------------------------------------------------------------------------------- 1 | .cate-page{ 2 | height: 100%; 3 | width: 100%; 4 | padding: 10px; 5 | display: flex; 6 | flex-direction: column; 7 | .cate-body{ 8 | height:100%; 9 | flex: 1; 10 | display: flex; 11 | background-color:#fff; 12 | .tabs-r{ 13 | background-color:#fff; 14 | width:100%; 15 | .tabs-r-box{ 16 | width:100%; 17 | box-sizing:border-box; 18 | .tabs-r-title{ 19 | height:44px; 20 | line-height:44px; 21 | font-size:14px; 22 | background-color:#f9f9f9; 23 | border-bottom:1px solid #eee; 24 | color:#000; 25 | text-indent: 20px; 26 | } 27 | .tabs-r-body{ 28 | width:100%; 29 | display:flex; 30 | flex-wrap:wrap; 31 | .tabs-r-item{ 32 | flex:1; 33 | width:33.33%; 34 | min-width:33.33%; 35 | max-width:33.33%; 36 | overflow: hidden; 37 | margin-bottom:3px; 38 | box-sizing:border-box; 39 | display:flex; 40 | flex-direction:column; 41 | justify-content: space-between; 42 | align-items: center; 43 | .img_box { 44 | width: 40px; 45 | height: 40px; 46 | border-radius: 50%; 47 | } 48 | img{ 49 | width: 100%; 50 | height: 100%; 51 | padding:5px; 52 | display: block; 53 | box-sizing:border-box; 54 | } 55 | span{ 56 | text-align:center; 57 | height:30px; 58 | line-height:30px; 59 | font-size:12px; 60 | box-sizing:border-box; 61 | align-content: flex-end; 62 | } 63 | span.border{ 64 | // border:1px solid #eee; 65 | // border-radius:2px; 66 | // margin:10px 0; 67 | } 68 | } 69 | .tabs-r-item.flex{ 70 | justify-content: center; 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/assets/styles/Collection.scss: -------------------------------------------------------------------------------- 1 | .collection-page { 2 | 3 | 4 | } -------------------------------------------------------------------------------- /src/assets/styles/CreateOrder.scss: -------------------------------------------------------------------------------- 1 | .create-order-page { 2 | 3 | .address { 4 | padding: 10px; 5 | margin-bottom: 10px; 6 | background-color: #fff; 7 | display: flex; 8 | align-items: center; 9 | .addr-icon { 10 | width: 25px; 11 | height: 25px; 12 | margin-right: 10px; 13 | } 14 | .info { 15 | flex: 1; 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | .info-l { 20 | flex: 1; 21 | font-size: 16px; 22 | .contact { 23 | .username { 24 | margin-right: 5px; 25 | } 26 | } 27 | .addr { 28 | display: -webkit-box; 29 | -webkit-box-orient: vertical; 30 | -webkit-line-clamp: 2; 31 | overflow: hidden; 32 | } 33 | } 34 | .info-r { 35 | width: 25px; 36 | height: 25px; 37 | } 38 | } 39 | } 40 | 41 | .order { 42 | padding: 10px; 43 | background-color: #fff; 44 | margin-bottom: 10px; 45 | .title { 46 | font-size: 16px; 47 | color: #999; 48 | margin-bottom: 10px; 49 | } 50 | .order-item { 51 | padding: 10px; 52 | background-color: #eee; 53 | display: flex; 54 | align-items: center; 55 | img { 56 | width: 64px; 57 | height: 64px; 58 | margin-right: 10px; 59 | } 60 | .order-info-r { 61 | flex: 1; 62 | display: flex; 63 | .left { 64 | .title { 65 | font-size: 14px; 66 | color: #000; 67 | } 68 | .desc { 69 | font-size: 12px; 70 | color: #999; 71 | } 72 | } 73 | .right { 74 | margin-left: 10px; 75 | font-size: 16px; 76 | .price { 77 | color: #f00; 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | .calculate { 85 | padding: 10px; 86 | background-color: #fff; 87 | .amount,.freight { 88 | display: flex; 89 | justify-content: space-between; 90 | align-items: center; 91 | font-size: 16px; 92 | .price { 93 | color: #f00; 94 | } 95 | } 96 | .total-price { 97 | margin: 15px 0; 98 | display: flex; 99 | justify-content: flex-end; 100 | font-size: 16px; 101 | span { 102 | color: #f00; 103 | } 104 | } 105 | } 106 | 107 | } -------------------------------------------------------------------------------- /src/assets/styles/GoodsListImg.scss: -------------------------------------------------------------------------------- 1 | .list { 2 | display: flex; 3 | flex-wrap: wrap; 4 | background: #efeff4; 5 | justify-content: space-between; 6 | } 7 | .list_item { 8 | display: flex; 9 | flex-direction: column; 10 | background: #fff; 11 | padding: 10px; 12 | box-sizing: border-box; 13 | margin-bottom: 10px; 14 | width: 48%; 15 | .img_box { 16 | width: 100%; 17 | height: 150px; 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | img { 22 | width: 80%; 23 | height: 100%; 24 | } 25 | } 26 | .list_item_content { 27 | display: flex; 28 | justify-content: space-between; 29 | flex-direction: column; 30 | padding: 10px 0; 31 | .title{ 32 | display: -webkit-box; 33 | -webkit-box-orient: vertical; 34 | -webkit-line-clamp: 2; 35 | overflow: hidden; 36 | font-size: 16px; 37 | } 38 | .desc { 39 | margin: 10px 0; 40 | display: -webkit-box; 41 | -webkit-box-orient: vertical; 42 | -webkit-line-clamp: 1; 43 | overflow: hidden; 44 | color: '#888'; 45 | font-size: 14px; 46 | } 47 | .list_item_content_bottom { 48 | display: flex; 49 | justify-content: space-between; 50 | .price { 51 | font-size: 12px; 52 | color: #f00; 53 | span { 54 | font-size: 16px; 55 | } 56 | } 57 | .sale { 58 | font-size: 12px; 59 | color: #888; 60 | span { 61 | font-size: 16px; 62 | } 63 | } 64 | } 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /src/assets/styles/Home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | padding: 10px; 3 | } -------------------------------------------------------------------------------- /src/assets/styles/ListPage.scss: -------------------------------------------------------------------------------- 1 | .ListPage { 2 | 3 | } -------------------------------------------------------------------------------- /src/assets/styles/Login.scss: -------------------------------------------------------------------------------- 1 | .bg{ 2 | height: 30vh; 3 | background: url('../images/lgbg.jpg') no-repeat; 4 | background-size:100%; 5 | // animation: left 5s infinite linear; 6 | // @keyframes left { 7 | // 0% { 8 | // background-position: 0 0; 9 | // } 10 | // 100% { 11 | // background-position: -70px 0; 12 | // } 13 | // } 14 | span{ 15 | position: fixed; 16 | top: 15px; 17 | left: 15px; 18 | color: #fff; 19 | } 20 | } 21 | 22 | .login{ 23 | height: 70vh; 24 | padding: 20px 50px; 25 | .registered{ 26 | display: flex; 27 | justify-content: space-between; 28 | align-items: center; 29 | h3 { 30 | font-size: 16px; 31 | } 32 | span { 33 | font-size: 16px; 34 | color: '#005980' 35 | } 36 | } 37 | .phone { 38 | margin: 15px 0; 39 | } 40 | .login_btn{ 41 | height: 40px; 42 | margin-top: 20px; 43 | display: flex; 44 | justify-content: center; 45 | align-items: center; 46 | background: #fe7300; 47 | font-size: 16px; 48 | color: #fff; 49 | } 50 | .login_forget{ 51 | margin-top: 20px; 52 | display: flex; 53 | justify-content: center; 54 | align-items: center; 55 | font-size: 16px; 56 | color: #005980; 57 | } 58 | } 59 | 60 | .absolute{ 61 | position: absolute; 62 | bottom: 100px; 63 | // left: 7%; 64 | width: 275px; 65 | } -------------------------------------------------------------------------------- /src/assets/styles/My.scss: -------------------------------------------------------------------------------- 1 | .top { 2 | background: linear-gradient(90deg, #eb3c3c, #ff7459);; 3 | padding: 10px 0; 4 | .top_info { 5 | padding: 20px 15px; 6 | display: flex; 7 | justify-content: space-between; 8 | .top_l { 9 | display: flex; 10 | align-items: center; 11 | img { 12 | width: 60px; 13 | height: 60px; 14 | border-radius: 36px; 15 | } 16 | 17 | .userInfo { 18 | margin-left: 10px; 19 | color: #fff; 20 | .username { 21 | margin-bottom: 10px; 22 | font-size: 16px; 23 | font-weight: bold; 24 | } 25 | 26 | .userNav { 27 | display: flex; 28 | div { 29 | margin-right: 15px; 30 | font-size: 16px; 31 | } 32 | } 33 | } 34 | } 35 | 36 | .top-r { 37 | .am-badge { 38 | margin-bottom: 10px; 39 | } 40 | .iconfont { 41 | font-size: 20px; 42 | color: #fff; 43 | font-weight: bold; 44 | } 45 | .iconfont.set { 46 | margin: 0 15px; 47 | } 48 | } 49 | } 50 | 51 | .top_nav { 52 | display: flex; 53 | padding: 10px 0; 54 | .top_nav-item { 55 | height: 40px; 56 | font-size: 16px; 57 | color: #fff; 58 | flex: 1; 59 | display: flex; 60 | flex-direction: column; 61 | justify-content: space-between; 62 | align-items: center; 63 | } 64 | } 65 | } 66 | 67 | .MyOrder{ 68 | padding: 10px; 69 | .title { 70 | display: flex; 71 | justify-content: space-between; 72 | align-items: center; 73 | h3 { 74 | font-size: 16px; 75 | } 76 | div { 77 | display: flex; 78 | justify-content: space-between; 79 | align-items: center; 80 | span { 81 | font-size: 14px; 82 | color: #999; 83 | } 84 | } 85 | } 86 | } 87 | .tools { 88 | display: flex; 89 | flex-wrap: wrap; 90 | background: #fff; 91 | border-radius: 10px; 92 | padding: 15px; 93 | .tools_item{ 94 | height: 50px; 95 | width: 25%; 96 | margin-bottom: 10px; 97 | font-size: 14px; 98 | display: flex; 99 | flex-direction: column; 100 | justify-content: space-between; 101 | align-items: center; 102 | } 103 | } -------------------------------------------------------------------------------- /src/assets/styles/Nav.scss: -------------------------------------------------------------------------------- 1 | .Nav { 2 | display: flex; 3 | flex-wrap: wrap; 4 | margin: 15px 0; 5 | } 6 | .Nav_item{ 7 | width: 25%; 8 | text-align: center; 9 | } 10 | .Nav_item img{ 11 | width: 50px; 12 | height: 50px; 13 | } -------------------------------------------------------------------------------- /src/assets/styles/Register.scss: -------------------------------------------------------------------------------- 1 | .bg{ 2 | height: 30vh; 3 | // background: skyblue; 4 | background: url('../images/lgbg.jpg') no-repeat; 5 | background-size:100%; 6 | // animation: left 5s infinite linear; 7 | // @keyframes left { 8 | // 0% { 9 | // background-position: 0 0; 10 | // } 11 | // 100% { 12 | // background-position: -200px 0; 13 | // } 14 | // } 15 | span{ 16 | position: fixed; 17 | top: 15px; 18 | left: 15px; 19 | color: #fff; 20 | } 21 | } 22 | 23 | .login{ 24 | height: 70vh; 25 | padding: 20px 50px; 26 | .registered{ 27 | display: flex; 28 | justify-content: space-between; 29 | align-items: center; 30 | h3 { 31 | font-size: 16px; 32 | } 33 | } 34 | .userName{ 35 | margin: 15px 0; 36 | } 37 | .password{ 38 | 39 | } 40 | .login_btn{ 41 | height: 40px; 42 | margin-top: 20px; 43 | display: flex; 44 | justify-content: center; 45 | align-items: center; 46 | background: #fe7300; 47 | font-size: 16px; 48 | color: #fff; 49 | } 50 | } -------------------------------------------------------------------------------- /src/assets/styles/Search.scss: -------------------------------------------------------------------------------- 1 | .Search { 2 | background-color: #fff; 3 | height: 100vh; 4 | .recent_search { 5 | 6 | .title { 7 | color: #f00; 8 | font-size: 16px; 9 | } 10 | .del { 11 | font-size: 20px; 12 | color: #f00; 13 | } 14 | 15 | .history_list { 16 | display: flex; 17 | flex-wrap: wrap; 18 | align-items: center; 19 | .history_list_item { 20 | margin-bottom: 10px; 21 | border-radius: 5px; 22 | width: 30%; 23 | margin: 5px; 24 | line-height: 30px; 25 | font-size: 16px; 26 | text-align: center; 27 | background-color: #eee; 28 | overflow: hidden; 29 | text-overflow:ellipsis; 30 | white-space: nowrap; 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/assets/styles/browse-record.scss: -------------------------------------------------------------------------------- 1 | .browse-record-page{ 2 | .browse-record-main{ 3 | .browse-wrap{ 4 | padding:10px; 5 | background-color: #fff; 6 | margin-bottom:10px; 7 | .browse-time{ 8 | line-height: 34px; 9 | height: 34px; 10 | border-bottom: 1px solid #eee; 11 | font-size:16px; 12 | color:#555; 13 | } 14 | .browse-main{ 15 | padding-top:10px; 16 | .browse-item{ 17 | padding:10px 0; 18 | min-height:90px; 19 | display: flex; 20 | img{ 21 | width: 90px; 22 | height: 90px; 23 | border: 1px solid #eee; 24 | margin-right: 10px; 25 | } 26 | .browse-right{ 27 | flex: 1; 28 | display: flex; 29 | flex-direction: column; 30 | justify-content: space-between; 31 | .title{ 32 | overflow : hidden; 33 | text-overflow: ellipsis; 34 | display: -webkit-box; 35 | -webkit-line-clamp: 2; 36 | -webkit-box-orient: vertical; 37 | font-size: 16px; 38 | } 39 | .price{ 40 | margin-top:10px; 41 | color:#ff5b05; 42 | font-weight: bold; 43 | font-size: 18px; 44 | span{ 45 | font-size:12px; 46 | } 47 | } 48 | } 49 | } 50 | .browse-item:not(:first-child){ 51 | border-top:1px solid #eee; 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/assets/styles/goodsdetail.scss: -------------------------------------------------------------------------------- 1 | .goods-page{ 2 | padding-bottom:50px; 3 | .goods-main{ 4 | .buy-wrap{ 5 | padding: 15px 10px; 6 | overflow: hidden; 7 | padding-top: 12px; 8 | padding-bottom: 10px; 9 | position: relative; 10 | min-height: 36px; 11 | background-color: #fff; 12 | .goods-name{ 13 | padding-right: 52px; 14 | display: flex; 15 | align-items: center; 16 | min-height: 36px; 17 | .goods-title{ 18 | font-size: 16px; 19 | color: #333; 20 | line-height: 18px; 21 | } 22 | .goods-favour{ 23 | position: absolute; 24 | right: 0; 25 | top: 12px; 26 | display: inline-block; 27 | padding-top: 22px; 28 | margin-top:0; 29 | line-height: 1em; 30 | height: 10px; 31 | width: 50px; 32 | font-size: 10px; 33 | color: #333; 34 | text-align: center; 35 | } 36 | .goods-favour:before { 37 | content: ""; 38 | width: 0; 39 | display: block; 40 | border-left: 1px solid #ddd; 41 | position: absolute; 42 | top: 0; 43 | bottom: 0; 44 | left: 0; 45 | } 46 | .goods-favour:after { 47 | content: ""; 48 | position: absolute; 49 | top: 0; 50 | left: 14px; 51 | width: 22px; 52 | height: 21px; 53 | background-image: url(../images/cart_sprits_all.png); 54 | background-size: 100px 100px; 55 | background-position: -50px -3px; 56 | } 57 | } 58 | .goods-desc{ 59 | color: #888; 60 | padding: 5px 0 0; 61 | line-height: 1.3; 62 | position: relative; 63 | font-size: 12px; 64 | max-height: 46px; 65 | overflow: hidden; 66 | } 67 | } 68 | .price-wrap{ 69 | display: flex; 70 | justify-content: space-between; 71 | padding:10px; 72 | background-color: #fff; 73 | .price{ 74 | >span:nth-child(1){ 75 | font-size: 16px; 76 | color: #e4393c; 77 | font-weight: 700; 78 | line-height: 1.3; 79 | display: inline-block; 80 | i{ 81 | font-size:12px; 82 | } 83 | } 84 | >span:nth-child(2){ 85 | margin: 0 0 0 2px; 86 | text-decoration: line-through; 87 | color: #999; 88 | font-size: 12px; 89 | } 90 | } 91 | .scoket{ 92 | color:#333; 93 | span{ 94 | font-size:12px; 95 | } 96 | } 97 | } 98 | .sku-wrap{ 99 | background:#fff; 100 | margin-top:10px; 101 | padding:10px; 102 | .sku { 103 | position: relative; 104 | padding-left: 33px; 105 | h3{ 106 | margin: 0; 107 | padding: 0; 108 | vertical-align: baseline; 109 | position: absolute; 110 | top: 20px; 111 | left: 0; 112 | line-height: 1.2; 113 | transform: translateY(-50%); 114 | width: 35px; 115 | font-size: 12px; 116 | font-weight: 400; 117 | color: #999; 118 | } 119 | .sku-list { 120 | overflow: hidden; 121 | .option { 122 | float: left; 123 | position: relative; 124 | padding: 5px 10px 4px; 125 | margin: 5px 10px 5px 0; 126 | min-width: 30px; 127 | border-radius: 2px; 128 | text-align: center; 129 | word-break: break-all; 130 | font-size: 14px; 131 | color: #333; 132 | background-color: #fff; 133 | border:1px solid #eee; 134 | line-height: 20px; 135 | } 136 | .option-selected { 137 | color: #c09947; 138 | border:1px solid #c09947; 139 | } 140 | .am-stepper-handler{ 141 | line-height: 26px; 142 | } 143 | } 144 | } 145 | .sku:not(.sku_date):not(.sku_size_adv):not(.sku_gift_choose):not(.sku_num) { 146 | margin: 5px 0; 147 | } 148 | .stepper{ 149 | width:120px; 150 | min-width: 120px; 151 | touch-action:none; 152 | } 153 | .sku-num{ 154 | overflow: hidden; 155 | } 156 | } 157 | .info-wrap{ 158 | padding:10px; 159 | background-color: #fff; 160 | margin-top:10px; 161 | .info-header{ 162 | padding-top:5px; 163 | padding-bottom:15px; 164 | border-bottom:1px solid #eee; 165 | margin-bottom:15px; 166 | } 167 | } 168 | .item-list{ 169 | padding: 10px; 170 | background-color: #fff; 171 | margin-top:10px; 172 | position: relative; 173 | padding-left: 43px; 174 | h3{ 175 | margin: 0; 176 | padding: 0; 177 | position: absolute; 178 | top: 17px; 179 | left: 10px; 180 | line-height: 1.2; 181 | transform: translateY(-50%); 182 | width: 35px; 183 | font-size: 12px; 184 | font-weight: 400; 185 | color: #999; 186 | } 187 | .item-content{ 188 | overflow: hidden; 189 | font-size:12px; 190 | color:#555; 191 | line-height: 1.2; 192 | } 193 | } 194 | } 195 | .fixed-btns{ 196 | position: fixed; 197 | bottom:0; 198 | left:0; 199 | width:100%; 200 | height:50px; 201 | background-color: rgba(255,255,255,.9); 202 | border-top:1px solid #eee; 203 | display: flex; 204 | justify-content: space-between; 205 | .icon{ 206 | width:70px; 207 | height:50px; 208 | display: flex; 209 | flex-direction: column; 210 | align-items: center; 211 | justify-content: center; 212 | border-right:1px solid #eee; 213 | img{ 214 | width:24px; 215 | height:24px; 216 | } 217 | span{ 218 | color:#555; 219 | } 220 | } 221 | button{ 222 | flex:1; 223 | border:0; 224 | height:50px; 225 | z-index: 10; 226 | line-height: 50px; 227 | text-align: center; 228 | background: #e4393c; 229 | color: #fff; 230 | font-size: 14px; 231 | cursor: pointer; 232 | } 233 | .btn-orange{ 234 | background: #ff9600; 235 | } 236 | } 237 | } 238 | @media only screen and (-webkit-min-device-pixel-ratio: 2){ 239 | 240 | } -------------------------------------------------------------------------------- /src/assets/styles/mypurse.scss: -------------------------------------------------------------------------------- 1 | .mypurse { 2 | 3 | .container { 4 | padding: 20px 0; 5 | img { 6 | width:120px; 7 | display: block; 8 | margin:0 auto; 9 | } 10 | .tip { 11 | text-align: center; 12 | margin: 15px 0; 13 | font-size:18px; 14 | } 15 | .price { 16 | text-align: center; 17 | font-size:28px; 18 | color:#ff5b05; 19 | font-weight: bold; 20 | span { 21 | font-size:18px; 22 | } 23 | } 24 | .btns { 25 | padding: 20px; 26 | div { 27 | text-align: center; 28 | font-size: 18px; 29 | padding: 8px 0; 30 | background-color: #fff; 31 | &:nth-child(1){ 32 | margin-bottom: 10px; 33 | background-color: #ff5b05; 34 | color: #fff; 35 | } 36 | } 37 | 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/components/Adverts.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { REACT_APP_STATIC_URL } from '../utils/urls' 3 | 4 | export default function Adverts(props) { 5 | const [imgHeight,setImgHeight] = useState(200) 6 | return ( 7 |
8 | { 9 | props.imgs.map((item,index) => ( 10 | 15 | 暂无图片 { 20 | // fire window resize event to change height 21 | window.dispatchEvent(new Event('resize')); 22 | setImgHeight('auto') 23 | }} 24 | onClick={(e) => e.preventDefault()} 25 | /> 26 | 27 | )) 28 | } 29 |
30 | ) 31 | } -------------------------------------------------------------------------------- /src/components/AntdSearchBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { SearchBar, Toast, Icon } from 'antd-mobile'; 3 | import { withRouter } from 'react-router-dom' 4 | import store from '../store' 5 | 6 | import { connect } from "react-redux"; 7 | import { changePath } from '../store/action/routerAction' 8 | import { clear } from '../store/action/searchAction' 9 | 10 | import '../assets/styles/AntdSearchBar.scss' 11 | 12 | function showToast() { 13 | Toast.info('请输入想要搜索的商品', 1); 14 | } 15 | 16 | class AntdSearchBar extends Component { 17 | constructor(props) { 18 | super(props) 19 | store.subscribe(() => { 20 | 21 | }) 22 | } 23 | state = { 24 | value: '' 25 | } 26 | componentDidMount() { 27 | //自动获取光标 28 | this.autoFocusInst.focus(); 29 | const value = store.getState().searchReducer.searchInput.keyword 30 | this.setState({ value }) 31 | } 32 | onSubmit = () => { 33 | const { value } = this.state 34 | if (!value) { 35 | showToast() 36 | return 37 | } 38 | this.props.onSubmit(value) 39 | 40 | } 41 | render() { 42 | const { value } = this.state 43 | return ( 44 |
45 | { 46 | const { path } = this.props.match 47 | this.props.changePath(path) 48 | this.props.history.go(-1) 49 | this.props.clear() 50 | }} /> 51 |
52 | this.autoFocusInst = ref} 56 | onSubmit={this.onSubmit} 57 | onChange={value => { 58 | this.setState({ value }) 59 | }} 60 | onCancel={() => this.props.history.go(-1)} 61 | style={{ flex: 1 }} 62 | /> 63 |
64 |
65 | ) 66 | } 67 | } 68 | 69 | const mapDispatchToProps = (dispatch) => { 70 | return { 71 | changePath: (value) => { 72 | dispatch(changePath(value)); 73 | }, 74 | clear: () => { 75 | dispatch(clear()) 76 | } 77 | } 78 | } 79 | 80 | export default withRouter(connect(null, mapDispatchToProps)(AntdSearchBar)) 81 | -------------------------------------------------------------------------------- /src/components/Banner.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Carousel } from 'antd-mobile'; 3 | import { REACT_APP_STATIC_URL } from '../utils/urls' 4 | 5 | export default class Banner extends Component { 6 | state = { 7 | imgHeight: 176, 8 | } 9 | componentDidMount() { 10 | 11 | } 12 | render() { 13 | const { banners } = this.props 14 | return ( 15 | 19 | {banners.map((item,i) => ( 20 | 25 | { 30 | e.preventDefault() 31 | this.props.onClickBanner(i) 32 | }} 33 | onLoad={() => { 34 | // fire window resize event to change height 35 | window.dispatchEvent(new Event('resize')); 36 | this.setState({ imgHeight: 176 }); 37 | }} 38 | /> 39 | 40 | ))} 41 | 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Cart/CartItem.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import {Checkbox, Stepper, SwipeAction, Toast} from 'antd-mobile' 3 | 4 | 5 | class CratItem extends Component{ 6 | //构造函数 7 | constructor(props){ 8 | super(props) 9 | this.state={ 10 | val:props.item.value 11 | } 12 | } 13 | onChange(e,id){ 14 | let checked = e.target.checked; 15 | // console.log(checked,id) 16 | this.props.checkChange(id,checked) 17 | } 18 | render(){ 19 | let item = this.props.item 20 | return ( 21 | { 27 | this.props.deleteById(item.id) 28 | return false; 29 | }, 30 | style: { backgroundColor: '#F4333C', color: 'white' }, 31 | }, 32 | ]} 33 | onOpen={() => console.log('global open')} 34 | onClose={() => { 35 | console.log('global close') 36 | return false; 37 | }} 38 | autoClose 39 | > 40 |
41 |
42 | { 43 | this.onChange(e,item.id) 44 | }}/> 45 |
46 | {/* 商品图片 */} 47 |
48 | {item.label}/ 49 |
50 | {/* 商品信息 */} 51 |
52 |
{item.lable}
53 |
54 | {item.price.toFixed(2)} 55 | 56 | {/* 加减步进器 */} 57 | { 63 | if(val>item.stockNum){ 64 | Toast.info("库存不足",1) 65 | this.props.changeStock(item.id,item.stockNum) 66 | }else{ 67 | this.props.changeStock(item.id,val) 68 | } 69 | }} 70 | /> 71 | 72 |
73 |
74 |
75 |
76 | ) 77 | } 78 | } 79 | export default CratItem -------------------------------------------------------------------------------- /src/components/Cart/CartList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | import CartItem from './CartItem' 4 | 5 | class CratList extends Component { 6 | render() { 7 | return ( 8 |
9 | {/* 遍历购物车商品列表 */} 10 |
11 | { 12 | this.props.data.length > 0 ? 13 | this.props.data.map((item, i) => { 14 | return ( 15 | { 17 | this.props.changeStock(id, val) 18 | }} 19 | checkChange={(id, val) => { 20 | this.props.checkChange(id, val) 21 | }} 22 | deleteById={(id) => this.props.deleteById(id) } 23 | key={i} 24 | item={item}> 25 | 26 | ) 27 | }) 28 | :
暂无商品
29 | } 30 |
31 |
32 | ) 33 | } 34 | } 35 | export default CratList -------------------------------------------------------------------------------- /src/components/FilterComponents.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Icon } from 'antd-mobile'; 3 | 4 | //搜索结果页条件筛选组件 5 | export default class FilterComponents extends Component { 6 | state = { 7 | list: [ 8 | { 9 | title: '综合', 10 | conditions: [ 11 | { 12 | desc: '综合', iconType: 'down' 13 | }, 14 | { 15 | desc: '价格升序', iconType: 'up' 16 | }, 17 | { 18 | desc: '价格降序', iconType: 'down' 19 | }, 20 | ] 21 | }, 22 | { 23 | title: '销量' 24 | }, 25 | { 26 | title: '店铺' 27 | }, 28 | { 29 | title: '筛选' 30 | }, 31 | ], 32 | showConditions: false, 33 | activeIndex: -1, 34 | iconType: 'down' 35 | } 36 | onClickItem = (activeIndex) => { 37 | const { showConditions } = this.state 38 | this.setState({ activeIndex }) 39 | if (activeIndex === 0) { 40 | //显示隐藏蒙层 41 | this.setState({ showConditions: !showConditions }) 42 | } 43 | 44 | } 45 | 46 | renderConditions = () => { 47 | let { list, iconType } = this.state 48 | const { conditions } = list[0] 49 | return ( 50 |
51 | { 52 | conditions.map((item, i) => ( 53 |
{ 55 | // 选中条件 56 | list[0].title = item.desc.substring(0, 2) 57 | // 切换icon图标 58 | iconType = item.iconType 59 | this.setState({ 60 | showConditions: false, 61 | list, 62 | iconType 63 | }) 64 | }} 65 | >{item.desc}
66 | )) 67 | } 68 | {/* 遮罩层部分 */} 69 |
this.setState({ showConditions: false })} 71 | style={{ backgroundColor: '#000', opacity: 0.6, height: '100vh' }}>
72 |
73 | ) 74 | } 75 | 76 | render() { 77 | const { list, showConditions, activeIndex, iconType } = this.state 78 | return ( 79 |
80 |
81 | { 82 | list.map((item, index) => ( 83 |
90 | {item.title} 91 | { 92 | index === 0 ? : null 93 | } 94 |
95 | )) 96 | } 97 |
98 | { 99 | showConditions ? this.renderConditions() : null 100 | } 101 | 102 |
103 | ) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/components/Goods/GoodsList.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import GoodsListItem from './GoodsListItem' 3 | export default class GoodsList extends Component { 4 | 5 | goodsItemClick = (id) => { 6 | this.props.goodsItemClick(id) 7 | } 8 | render() { 9 | const { goodsList } = this.props 10 | return ( 11 |
12 | { 13 | goodsList.map((item, index) => ( 14 | 19 | ))} 20 |
21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Goods/GoodsListItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import './GoodsListItem.scss' 3 | export default class GoodsListItem extends Component { 4 | 5 | goodsItemClick = (id) => { 6 | this.props.goodsItemClick(id) 7 | } 8 | render() { 9 | const { item } = this.props 10 | return ( 11 |
16 |
17 | 18 |
19 |
20 |
{item.goods_title}
21 |
¥{item.price}
22 |
23 |
24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Goods/GoodsListItem.scss: -------------------------------------------------------------------------------- 1 | .goods_list_item { 2 | padding: 10px; 3 | display: flex; 4 | justify-content: space-between; 5 | background: #fff; 6 | .goods_img_box { 7 | width: 120px; 8 | height: 100%; 9 | img { 10 | width: 100%; 11 | height: 100%; 12 | } 13 | } 14 | .goods_list_item_content { 15 | flex: 1; 16 | display: flex; 17 | justify-content: space-between; 18 | flex-direction: column; 19 | padding: 15px 0; 20 | .title { 21 | color: #868584; 22 | line-height: 20px; 23 | font-size: 14px; 24 | } 25 | .price { 26 | color: #f00; 27 | font-size: 14px; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/components/GoodsListImg.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import '../assets/styles/GoodsListImg.scss' 3 | 4 | export default class GoodsListImg extends Component { 5 | 6 | goodsItemClick = (id) => { 7 | this.props.goodsItemClick(id) 8 | } 9 | render() { 10 | const { goodsList } = this.props 11 | return ( 12 |
13 | {goodsList.map((item, index) => ( 14 |
19 |
20 | 21 |
22 |
23 |
{item.goods_title}
24 |
{item.desc}
25 |
27 |
28 | ¥ {item.price} 29 |
30 |
31 | 销量 {item.sales} 32 |
33 |
34 |
35 |
36 | ))} 37 |
38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/Header/SearchHeader.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | //引入antd-mobile组件 3 | import { Icon,Popover } from 'antd-mobile' 4 | //支持路由跳转 5 | import {withRouter} from 'react-router-dom' 6 | 7 | import './search_header.scss' 8 | 9 | const Item = Popover.Item; 10 | 11 | class SearchHeader extends Component { 12 | //默认参数 13 | static defaultProps={ 14 | returnbtn:false, 15 | value:'', 16 | pathname:'/' 17 | } 18 | //构造函数 19 | constructor(props){ 20 | super(props); 21 | this.state={ 22 | visible:false, 23 | selected: '', 24 | } 25 | } 26 | //返回某个页面 27 | returnPage(){ 28 | this.props.history.push(this.props.pathname||'/') 29 | sessionStorage.setItem('__search_prev_path__',this.props.pathname||'/') 30 | } 31 | //选择下拉菜单 32 | onSelect(opt){ 33 | this.setState({ 34 | visible: false, 35 | selected: opt.props.value, 36 | }); 37 | //跳转到页面 38 | this.props.history.push(opt.props.path) 39 | sessionStorage.setItem('__search_prev_path__',opt.props.path) 40 | } 41 | //隐藏下拉菜单 42 | handleVisibleChange(visible){ 43 | this.setState({ 44 | visible, 45 | }); 46 | } 47 | 48 | render() { 49 | //下拉菜单列的图片 50 | const myImg = src => ; 51 | //下拉菜单组 52 | let menuArr = [ 53 | (首页), 54 | (分类), 55 | (购物车), 56 | (个人中心), 57 | ] 58 | return ( 59 |
60 | {/* 判断是否需要返回按钮 */} 61 | { 62 | this.props.returnbtn 63 | ? 64 |
{ 65 | // 返回某个页面 66 | this.props.history.go(-1) 67 | }}> 68 | return 69 |
70 | :null 71 | } 72 | {/* 搜索框 */} 73 |
74 | {this.props.text} 75 |
76 |
77 | {/* 下拉菜单 */} 78 | 90 | { 91 | this.setState({ 92 | visible:true 93 | }) 94 | }} type="ellipsis" /> 95 | 96 |
97 |
98 | ) 99 | } 100 | } 101 | //路由跳转装饰这个类 102 | export default withRouter(SearchHeader) -------------------------------------------------------------------------------- /src/components/Header/search_header.scss: -------------------------------------------------------------------------------- 1 | .search-head{ 2 | height:44px; 3 | display: flex; 4 | background-color: #fff; 5 | border-bottom: 1px solid #eee; 6 | .left{ 7 | width:44px; 8 | height:44px; 9 | line-height:44px; 10 | text-align: center; 11 | img{ 12 | width:14px; 13 | height:14px; 14 | display: block; 15 | padding:15px; 16 | } 17 | } 18 | .center{ 19 | flex: 1; 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | font-size: 18px; 24 | font-weight: bold; 25 | } 26 | .right{ 27 | width:44px; 28 | height:44px; 29 | line-height:44px; 30 | .am-icon-md{ 31 | padding:11px; 32 | } 33 | 34 | } 35 | .right-btn{ 36 | width:auto; 37 | .submit-btn{ 38 | line-height: 30px; 39 | border:0; 40 | height:30px; 41 | margin:7px 10px; 42 | margin-left:0; 43 | border-radius:3px; 44 | background-color:rgb(255, 91, 5); 45 | color:#fff; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/components/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { TabBar } from 'antd-mobile'; 3 | import { withRouter } from "react-router-dom" 4 | /* 5 | 1. {this.props.children} 是Item渲染子节点内容 6 | 2. selected 判断Item是否选中 7 | 3. onPress 是Item 选中事件 8 | 4. 字体图片 9 | 5. import { withRouter } from "react-router-dom" 添加路由信息 10 | 11 | */ 12 | class Layout extends Component { 13 | state = { 14 | data: [ 15 | { 16 | title: '首页', key: 'Home', icon: , url: '/index' 17 | }, 18 | { 19 | title: '分类', key: 'cate', icon: , url: '/cate' 20 | }, 21 | { 22 | title: '购物车', key: 'cart', icon: ,url: '/cart' 23 | }, 24 | { 25 | title: '我的', key: 'profile', icon: ,url: '/my' 26 | }, 27 | ] 28 | } 29 | render() { 30 | return ( 31 |
32 | 37 | { 38 | this.state.data.map((item, index) => 39 | { 46 | this.props.history.push(item.url); 47 | }} 48 | > 49 | {this.props.match.url === item.url && this.props.children} 50 | 51 | ) 52 | } 53 | 54 |
55 | ) 56 | } 57 | } 58 | 59 | export default withRouter(Layout) -------------------------------------------------------------------------------- /src/components/Nav.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import '../assets/styles/Nav.scss' 3 | import { REACT_APP_STATIC_URL } from '../utils/urls' 4 | 5 | export default class Nav extends Component { 6 | 7 | render() { 8 | const { Navs } = this.props 9 | return ( 10 |
11 | { 12 | Navs.map((item, index) => ( 13 |
this.props.onClickNav(item)}> 14 |
15 | 16 |
17 | {item.title} 18 |
19 | )) 20 | } 21 |
22 | 23 |
24 |
25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/Order/OrderItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Icon, Tag } from 'antd-mobile' 3 | import './OrderItem.scss' 4 | 5 | export default class OrderItem extends Component { 6 | 7 | onChange = (selected) => { 8 | console.log(`tag selected: ${selected}`); 9 | } 10 | render() { 11 | const { item } = this.props 12 | return ( 13 |
14 | {/* 店铺名称 */} 15 |
16 |
17 | 暂无logo 18 | {item.storeName} 19 | 20 |
21 |
22 | {/* 订单信息 */} 23 |
24 | { 25 | item.orderItems.map((jtem,index) => ( 26 |
27 | {/* 商品图片 */} 28 | 暂无图片 29 | {/* 商品简介 */} 30 |
31 |
32 |
{jtem.name}
33 |
34 |
¥ {jtem.price}
35 |
x{jtem.quantity}
36 |
37 |
38 |
39 | {jtem.sku} 40 |
41 |
42 |
43 | )) 44 | } 45 |
46 |
47 | ) 48 | } 49 | } -------------------------------------------------------------------------------- /src/components/Order/OrderItem.scss: -------------------------------------------------------------------------------- 1 | .order_item{ 2 | padding: 10px; 3 | background-color: #fff; 4 | margin-bottom: 10px; 5 | .storeName { 6 | >div { 7 | display: flex; 8 | align-items: center; 9 | img { 10 | width: 20px; 11 | height: 20px; 12 | margin-right: 10px; 13 | } 14 | span { 15 | font-size: 16px; 16 | } 17 | } 18 | } 19 | .order_info{ 20 | padding: 10px 0; 21 | .order_info_item{ 22 | display: flex; 23 | img{ 24 | width: 80px; 25 | height: 80px; 26 | margin-right: 10px; 27 | } 28 | .order_info_item_r{ 29 | flex: 1; 30 | .item_r_top{ 31 | display: flex; 32 | justify-content: space-between; 33 | font-size: 14px; 34 | .name{ 35 | flex: 1; 36 | display: -webkit-box; 37 | -webkit-box-orient: vertical; 38 | -webkit-line-clamp: 2; 39 | overflow: hidden; 40 | font-size: 14px; 41 | } 42 | .price{ 43 | width: 50px; 44 | text-align: right; 45 | color: #f00; 46 | font-size: 14px; 47 | .count { 48 | font-size: 14px; 49 | color: #999; 50 | } 51 | } 52 | } 53 | .tag-container { 54 | padding-top: 10px; 55 | } 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/components/Order/OrderList.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import OrderItem from './OrderItem' 3 | 4 | export default class OrderList extends Component { 5 | goodsItemClick = (id) => { 6 | this.props.goodsItemClick(id) 7 | } 8 | render() { 9 | const { order_list } = this.props 10 | return ( 11 |
12 | {/* 遍历订单商品列表 */} 13 |
14 | { 15 | order_list.length > 0 ? order_list.map((item,index) => ( 16 | 19 | 20 | )) 21 | :
暂无商品
22 | } 23 |
24 |
25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/ScrollToTop .jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Icon } from 'antd-mobile'; 3 | 4 | //返回页面顶部组件 5 | export default class ScrollToTop extends Component { 6 | constructor(props) { 7 | super(props) 8 | //show为true时回到顶部按钮显示,false时隐藏 9 | this.state = ({ 10 | show: false 11 | }) 12 | } 13 | componentDidMount() { 14 | window.addEventListener('scroll', this.changeScrollTopShow) 15 | } 16 | componentWillUnmount() { 17 | window.removeEventListener('scroll', this.changeScrollTopShow) 18 | } 19 | render() { 20 | const { show } = this.state; 21 | return ( 22 |
24 | { 25 | // 逻辑与符号左边的show为true时返回右边的html标签 26 | show && 27 |
31 | 32 |
33 | } 34 |
35 | 36 | ) 37 | } 38 | //控制show的状态从而控制回到顶部按钮的显示和隐藏 39 | changeScrollTopShow = () => { 40 | if (window.pageYOffset < 500) { 41 | this.setState({ 42 | show: false 43 | }) 44 | } else { 45 | this.setState({ 46 | show: true 47 | }) 48 | } 49 | } 50 | //添加动画效果 51 | scrollToTop = () => { 52 | const scrollToTop = window.setInterval(() => { 53 | let pos = window.pageYOffset; 54 | if (pos > 0) { 55 | window.scrollTo(0, pos - 20); 56 | } else { 57 | window.clearInterval(scrollToTop); 58 | } 59 | }, 1); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/SearchInput.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withRouter } from "react-router-dom" 3 | 4 | class SearchInput extends Component { 5 | state = { 6 | value: '', 7 | }; 8 | onChange = (value) => { 9 | this.setState({ value }); 10 | }; 11 | render() { 12 | return ( 13 |
14 |
this.props.history.push('/search')} 17 | > 18 | 请输入关键词 19 |
20 |
21 | ) 22 | } 23 | } 24 | 25 | export default withRouter(SearchInput) 26 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | //导出所有组件 2 | import Banner from './Banner' 3 | import SearchInput from './SearchInput' 4 | import Nav from './Nav' 5 | import Adverts from './Adverts' 6 | import AntdSearchBar from './AntdSearchBar' 7 | import FilterComponents from './FilterComponents' 8 | 9 | import GoodsList from './Goods/GoodsList' 10 | import GoodsListItem from './Goods/GoodsListItem' 11 | import GoodsListImg from './GoodsListImg' 12 | 13 | import OrderList from './Order/OrderList' 14 | 15 | import CartList from './Cart/CartList' 16 | import CartItem from './Cart/CartItem' 17 | 18 | import SearchHeader from './Header/SearchHeader' 19 | import ScrollToTop from './ScrollToTop ' 20 | import Layout from './Layout' 21 | 22 | export { 23 | Banner, 24 | SearchInput, 25 | Nav, 26 | Adverts, 27 | AntdSearchBar, 28 | FilterComponents, 29 | 30 | GoodsList, 31 | GoodsListItem, 32 | GoodsListImg, 33 | 34 | OrderList, 35 | 36 | CartList, 37 | CartItem, 38 | 39 | SearchHeader, 40 | ScrollToTop, 41 | Layout 42 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | 15 | .content_wrap_2 { 16 | overflow: hidden; 17 | display: -webkit-box; 18 | -webkit-box-orient: vertical; 19 | -webkit-line-clamp: 2; 20 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | // 引入 react-redux 负责将store和组件连接起来 7 | import { Provider } from "react-redux"; 8 | import store from "./store"; 9 | // 引入redux-persist实现redux数据持久化 10 | import { persistor } from './store' 11 | import { PersistGate } from 'redux-persist/lib/integration/react'; 12 | 13 | import "lib-flexible" 14 | 15 | ReactDOM.render( 16 | 17 | 18 | 19 | 20 | , 21 | document.getElementById('root') 22 | ); 23 | 24 | // If you want your app to work offline and load faster, you can change 25 | // unregister() to register() below. Note this comes with some pitfalls. 26 | // Learn more about service workers: https://bit.ly/CRA-PWA 27 | serviceWorker.unregister(); 28 | -------------------------------------------------------------------------------- /src/pages/Cart.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { withRouter } from "react-router-dom" 4 | import { Checkbox, NavBar, Button } from 'antd-mobile' 5 | 6 | import { Layout,CartList } from '../components/index' 7 | import '../assets/styles/Cart.scss' 8 | import classnames from 'classnames' 9 | import { Api,axios } from '../api/api' 10 | import { Cookie } from '../utils/storage' 11 | 12 | // 顶部导航 13 | function TextHeader(props) { 14 | return ( 15 | console.log('onLeftClick')} 18 | rightContent={[ 19 | props.children 20 | ]} 21 | >购物车 22 | ) 23 | } 24 | 25 | class Cart extends Component { 26 | constructor(props) { 27 | super(props) 28 | this.state = { 29 | token: Cookie.getItem('token') || '', 30 | deleteAll: false, 31 | checkedNum: 0, 32 | allPrice: 0, 33 | cartNmu: 0, 34 | data: [] 35 | } 36 | } 37 | 38 | componentDidMount() { 39 | this.state.token && this.getCartList() 40 | } 41 | //获取购物车列表 42 | async getCartList() { 43 | let { code,data } = await axios.get(Api.getCartList).then(res => res); 44 | if(code === 200) { 45 | this.setState({ 46 | data: data, 47 | checkedNum: 0, 48 | allPrice: 0, 49 | cartNmu: 0, 50 | }) 51 | } 52 | } 53 | //全选 54 | allChange = (e) => { 55 | let checked = e.target.checked 56 | let newData = this.state.data.map((item, i) => { 57 | return { 58 | ...item, 59 | check: checked 60 | } 61 | }) 62 | this.setState({ 63 | data: newData 64 | }) 65 | this.calc(newData) 66 | } 67 | //改变库存 68 | changeStock = (id, val) => { 69 | let newData = this.state.data.map((item, i) => { 70 | if (item.id === id) { 71 | return { 72 | ...item, 73 | value: val 74 | } 75 | } else { 76 | return item; 77 | } 78 | }) 79 | this.setState({ 80 | data: newData 81 | }) 82 | this.calc(newData) 83 | } 84 | //点击 85 | checkChange = (id, check) => { 86 | let newData = this.state.data.map((item, i) => { 87 | if (item.id === id) { 88 | return { 89 | ...item, 90 | check: check 91 | } 92 | } else { 93 | return item; 94 | } 95 | }) 96 | this.setState({ 97 | data: newData 98 | }) 99 | this.calc(newData) 100 | } 101 | //计算总价 102 | calc = (newData) => { 103 | let allPrice = 0; 104 | let checkedNum = 0; 105 | let cartNmu = 0; 106 | newData.forEach((item, i) => { 107 | if (item.check) { 108 | cartNmu += 1; 109 | checkedNum += parseFloat(item.value); 110 | allPrice += parseFloat(item.value) * parseFloat(item.price) 111 | } 112 | }) 113 | this.setState({ 114 | checkedNum, 115 | allPrice, 116 | cartNmu 117 | }) 118 | } 119 | //购买 120 | buy = () => { 121 | const { data } = this.state 122 | //选中数据 123 | let selectData = data.filter(v => { 124 | return v.check === true 125 | }) 126 | console.log(selectData) 127 | } 128 | delete = () => { 129 | const { data } = this.state 130 | //未被选中数据 131 | let unDeleteData = data.filter(v => { 132 | return v.check === false 133 | }) 134 | console.log(unDeleteData) 135 | this.setState({ 136 | data: unDeleteData 137 | }) 138 | 139 | } 140 | //右滑删除 141 | deleteById = (id) => { 142 | const { data } = this.state 143 | //未被选中数据 144 | let unDeleteData = data.filter(v => { 145 | return v.id !== id 146 | }) 147 | this.setState({ 148 | data: unDeleteData 149 | }) 150 | } 151 | render() { 152 | const { data, deleteAll } = this.state 153 | if (this.state.token) { 154 | return ( 155 | 156 |
157 | 158 | { 159 | e.preventDefault(); 160 | this.setState({ deleteAll: !deleteAll }) 161 | }}> 162 | { 163 | deleteAll ? '返回' : '编辑' 164 | } 165 | 166 | 167 |
168 | { 169 | this.deleteById(id)}> 174 | 175 | } 176 |
177 | { 178 | deleteAll ? 179 |
180 |
181 | { 182 | this.allChange(e) 183 | }} /> 184 |
全选
185 |
186 |
0, 188 | 'cart-footer-right': true 189 | })} onClick={() => { 190 | if (this.state.cartNmu > 0) { 191 | this.delete() 192 | } 193 | }}> 194 | 删除({this.state.cartNmu}) 195 |
196 |
197 | : 198 |
199 |
200 | { 201 | this.allChange(e) 202 | }} /> 203 |
全选
204 |
205 |
206 |

207 | 总计: 208 | ¥{this.state.allPrice} 209 |

210 |
211 |
0, 213 | 'cart-footer-right': true 214 | })} onClick={() => { 215 | this.state.checkedNum > 0 && this.buy() 216 | }}> 217 | 去结算({this.state.checkedNum}件) 218 |
219 |
220 | } 221 | 222 |
223 |
224 | ) 225 | } else { 226 | return ( 227 | 228 |
229 | console.log('onLeftClick')} 232 | >购物车 233 |
234 | 235 |
236 |
237 |
238 | ) 239 | 240 | } 241 | } 242 | } 243 | 244 | export default connect()(withRouter(Cart)) -------------------------------------------------------------------------------- /src/pages/Cate.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Tabs } from 'antd-mobile'; 3 | import { Layout,SearchInput } from '../components/index' 4 | import classnames from 'classnames' 5 | import { withRouter } from "react-router-dom" 6 | 7 | import { Api,axios } from '../api/api' 8 | import '../assets/styles/Cate.scss' 9 | 10 | class Cate extends Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | tabs: [ 15 | ], 16 | cates: [] 17 | } 18 | } 19 | componentDidMount() { 20 | // this.props.getCate() 21 | this.getCate() 22 | 23 | } 24 | async getCate(){ 25 | let {data:cates} = await axios.get(Api.getCate) 26 | this.setState({ 27 | cates 28 | }) 29 | } 30 | render() { 31 | let cates = this.state.cates ? this.state.cates.map(v => { 32 | return { 33 | ...v, 34 | title: v.name.split('').length > 5 ? v.name.split('').slice(0, 4).join('') + '...' : v.name 35 | } 36 | }) : [] 37 | return ( 38 | 39 |
40 |
41 | 42 |
43 | 44 | { 45 |
46 | } 60 | tabBarPosition="left" 61 | tabDirection="vertical" 62 | > 63 | { 64 | cates && cates.map((item, i) => { 65 | return ( 66 |
67 | { 68 | item.childs.length > 0 ? item.childs.map((jtem, j) => { 69 | return ( 70 |
71 |
{jtem.name}
72 |
73 | { 74 | jtem.childs.length > 0 ? jtem.childs.map((ktem, k) => { 75 | return ( 76 |
80 | { 81 | ktem.logo ? 82 |
83 | 84 |
85 | : null 86 | } 87 | { 88 | ktem.logo ? 89 | {ktem.name} 90 | : {ktem.name} 91 | } 92 |
93 | ) 94 | }) 95 | :
暂无分类
96 | } 97 | 98 |
99 |
100 | ) 101 | }) 102 | :
暂无该分类
103 | } 104 |
105 | ) 106 | }) 107 | } 108 |
109 |
110 | } 111 |
112 |
113 | ) 114 | } 115 | } 116 | 117 | export default withRouter(Cate) -------------------------------------------------------------------------------- /src/pages/CreateOrder.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { NavBar, Icon, Button } from 'antd-mobile'; 3 | import '../assets/styles/CreateOrder.scss' 4 | 5 | export default class CreateOrder extends Component { 6 | state = { 7 | user: {}, 8 | } 9 | componentDidMount() { 10 | const user = JSON.parse(localStorage.getItem('user')) || {} 11 | console.log('user: ', user); 12 | this.setState({ user }) 13 | } 14 | render() { 15 | const { user } = this.state 16 | return ( 17 |
18 | } 21 | onLeftClick={() => this.props.history.go(-1)} 22 | >确认订单 23 | 24 | {/* 收货地址 */} 25 |
26 | 27 |
28 |
29 |
30 | {user.username} 31 | {user.phone} 32 |
33 |
广东省广州市
34 |
35 |
36 |
37 |
38 | 39 |
40 |
41 | 已选商品 42 |
43 |
44 | 45 |
46 |
47 |
戴尔(DELL)成就5880英特尔酷睿i5商用办公台式机电脑整机(i5-10400F 8G 256G 1T 2G独显 三年上门)23.8英寸
48 |
内存容量:16G;硬盘容量:512G固态硬盘
49 |
50 |
51 |
10.00
52 |
x1
53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 | 商品金额 61 | ¥ 10.00 62 |
63 |
64 | 运费 65 | ¥ 0.00 66 |
67 |
68 | 总价:¥ 10.00 69 |
70 | 71 | 72 |
73 |
74 | ) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/pages/GoodsDetail.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { SearchHeader, Banner } from '../components' 3 | import { Stepper, Toast } from 'antd-mobile' 4 | import { PhotoSlider } from 'react-photo-view'; 5 | import 'react-photo-view/dist/index.css'; 6 | import '../assets/styles/goodsdetail.scss' 7 | import { REACT_APP_STATIC_URL } from '../utils/urls' 8 | import { Api, axios } from '../api/api' 9 | 10 | 11 | export default class Detail extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | data: null, 16 | val: 1, 17 | goodId: props.match.params.id, 18 | stockNum: null, 19 | stockId: null, 20 | popPrice: null, 21 | standard: null, 22 | productName: null, 23 | //图片缩放 24 | visible: false, 25 | photoIndex: 0 26 | } 27 | } 28 | componentDidMount() { 29 | this.getGoodsInfo() 30 | } 31 | //获取商品信息 32 | getGoodsInfo = async () => { 33 | let res = await axios.get(Api.getGoodInfo) 34 | let data = res.data 35 | console.log('data', data) 36 | this.setState({ 37 | data 38 | }) 39 | this.filterData(data) 40 | } 41 | 42 | //设置初始 43 | setAllDataStandard = (sku) => { 44 | //统一处理点击后的规格处理 45 | let that = this; 46 | let stockNum = sku.stockNum; 47 | let popPrice = sku.price.toFixed(2); 48 | let skuId = sku.id; 49 | let productName = sku.productName; 50 | that.setState({ 51 | stockNum: stockNum, 52 | stockId: skuId, 53 | popPrice: popPrice, 54 | standard: '颜色 . 尺码', 55 | productName: productName, 56 | }) 57 | } 58 | 59 | //过滤规格数据 60 | filterData = (result) => { 61 | //刚开始进来的时候先显示一部分内容 62 | let that = this; 63 | let skuData = result.sku[0]; 64 | that.setState({ 65 | sku: result.sku 66 | }) 67 | //第一次进来设置数据 68 | that.setAllDataStandard(skuData); 69 | } 70 | 71 | //数量改变 72 | stepperChange = (e) => { 73 | if (e > this.state.stockNum) { 74 | this.setState({ 75 | val: this.state.stockNum 76 | }) 77 | Toast.info('库存不足', 1); 78 | return; 79 | } 80 | this.setState({ 81 | val: e 82 | }) 83 | } 84 | 85 | addGoodToCart = () => { 86 | console.log('addGoodToCart') 87 | } 88 | 89 | render() { 90 | const { data } = this.state 91 | return ( 92 |
93 | { 94 | data && ({ src: REACT_APP_STATIC_URL + item.src }))} 96 | visible={this.state.visible} 97 | onClose={() => this.setState({ visible: false }) 98 | } 99 | index={this.state.photoIndex} 100 | onIndexChange={(photoIndex) => this.setState({ photoIndex })} 101 | /> 102 | } 103 | 104 | 105 | 106 |
107 | {/* <--商品图片 */} 108 | { 109 | data && { 111 | this.setState({ visible: true,photoIndex }) 112 | }} 113 | /> 114 | 115 | } 116 | 117 | {/* <--商品详情 */} 118 |
119 |
120 |
121 | {data && data.productName} 122 |
123 |
124 | 分享 125 |
126 |
127 |
128 | {data && data.desc} 129 |
130 |
131 | {/* 商品详情--> */} 132 | 133 | {/* <--价格 */} 134 |
135 |
136 | {data && this.state.popPrice} 137 | ¥{data && data.originaPrice} 138 |
139 |
140 | 库存 {data && this.state.stockNum} 141 |
142 |
143 | {/* 价格--> */} 144 | 145 |
146 |

选择

147 |
148 | {this.state.standard} 149 |
150 |
151 | 152 | {/* <--规格 */} 153 |
154 | 155 | 156 | {/* 商品数量 */} 157 |
158 |
159 |

数量

160 |
161 | { 167 | this.stepperChange(e) 168 | }} 169 | /> 170 |
171 |
172 |
173 | {/* 商品数量 */} 174 |
175 | {/* 规格--> */} 176 |
177 |
商品详情
178 |
179 |
180 |
181 |
182 | 183 | {/* body-> */} 184 |
185 |
186 |
187 | 客服 188 | 客服 189 |
190 |
191 | 客服 192 | 收藏 193 |
194 |
195 | 196 | 199 |
200 |
201 | ) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withRouter } from "react-router-dom" 3 | import { Toast } from 'antd-mobile' 4 | import { Layout,SearchInput, Banner, Nav, Adverts, GoodsListItem } from '../components/index' 5 | import '../assets/styles/Home.scss' 6 | import 'react-virtualized/styles.css'; 7 | import { AutoSizer, List, CellMeasurer, CellMeasurerCache } from 'react-virtualized'; 8 | import { Api, axios } from '../api/api' 9 | const cache = new CellMeasurerCache({ defaultHeight: 30, fixedWidth: true }); 10 | 11 | class Home extends Component { 12 | state = { 13 | banners: [], 14 | Navs: [], 15 | adverts: [], 16 | goodsList: [], 17 | isMore: true 18 | } 19 | Qparams = { 20 | page: 1, 21 | limit: 10 22 | } 23 | 24 | //防止请求多次加载 25 | loading = false; 26 | 27 | async componentDidMount() { 28 | const res1 = await axios.get(Api.getBanners) 29 | const banners = res1.data.map(item => { 30 | item.src = item.url 31 | return item 32 | }) 33 | const res2 = await axios.get(Api.getNavs) 34 | const res3 = await axios.get(Api.getAdverts) 35 | this.setState({ 36 | banners: banners, 37 | Navs: res2.data, 38 | adverts: res3.data 39 | }) 40 | this.getGoodsList() 41 | 42 | } 43 | 44 | //获取商品 45 | getGoodsList = async () => { 46 | this.loading = true; 47 | // console.log(this.Qparams) 48 | const res = await axios.get(Api.getGoodsList, { 49 | params: this.Qparams 50 | }) 51 | if (res.data.length < this.Qparams.limit) { 52 | Toast.info('没有更多了', 2, null, true) 53 | this.setState({ isMore: false }) 54 | } 55 | this.setState({ 56 | goodsList: [...this.state.goodsList, ...res.data] 57 | }) 58 | this.loading = false; 59 | } 60 | 61 | cellRenderer = ({ index, key, parent, style }) => { 62 | let item = this.state.goodsList[index]; 63 | return ( 64 | 71 |
74 | this.props.history.push('/goodsDetail/' + id)} 78 | /> 79 |
80 |
81 | ); 82 | } 83 | 84 | //记录上次滚动距离 85 | scrollTop = 0; 86 | //列表滚动刷新 87 | handleScorll = ({ clientHeight, scrollHeight, scrollTop }) => { 88 | //clientHeight 外组件的最大高度 89 | //scrollHeight 这里列表的最大高度 90 | //scrollTop这里滚动的距离 91 | // console.log(scrollHeight, clientHeight, scrollTop) 92 | //初始化不请求 93 | if (scrollHeight === 0 && clientHeight === 0 && scrollTop === 0) return 94 | //下拉不再请求 95 | if(this.scrollTop > scrollTop) return 96 | 97 | if ((scrollHeight - clientHeight - scrollTop) < 20) { 98 | this.scrollTop = scrollTop; 99 | //上拉已经触底了 发请求了 100 | if (!this.loading && this.state.isMore) { 101 | this.Qparams.page += 1; 102 | this.getGoodsList(); 103 | } 104 | 105 | } 106 | 107 | } 108 | 109 | render() { 110 | const { banners, Navs, adverts, goodsList } = this.state 111 | return ( 112 | 113 | 114 |
115 | 116 | 117 | console.log(i)} /> 118 | 119 |
146 | 147 |
148 | 149 | ) 150 | } 151 | } 152 | 153 | export default withRouter(Home) -------------------------------------------------------------------------------- /src/pages/ListPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { AntdSearchBar, GoodsListImg, ScrollToTop } from '../components/index' 3 | import { connect } from "react-redux"; 4 | import { searchByKeyWord } from '../store/action/searchAction' 5 | import { getGoodsList } from '../store/action/goodsAction' 6 | import '../assets/styles/ListPage.scss' 7 | 8 | class ListPage extends Component { 9 | state = { 10 | 11 | } 12 | 13 | Qparams = { 14 | page: 1, 15 | limit: 10 16 | } 17 | 18 | async componentDidMount() { 19 | const { keyword } = this.props.match.params 20 | this.props.getGoodsList(this.Qparams) 21 | } 22 | goodsItemClick = (id) => { 23 | this.props.history.push('/goodsDetail/' + id) 24 | } 25 | onSubmit = (value) => { 26 | //更新store中的state值 27 | this.props.searchByKeyWord(value) 28 | } 29 | 30 | render() { 31 | const goodsList = this.props.goods 32 | return ( 33 |
34 |
35 | 36 |
37 | 38 | {/* 筛选器 */} 39 | 40 | {/* 列表 */} 41 | { 42 | goodsList.length > 0 ? 43 | 47 | :
48 | 没有搜索数据 49 |
50 | } 51 | 52 |
53 | ) 54 | } 55 | } 56 | 57 | const mapStateToProps = (state) => ({ 58 | goods: state.goodsReducer.goods 59 | }) 60 | const mapDispatchToProps = (dispatch) => { 61 | return { 62 | searchByKeyWord: (value) => { 63 | dispatch(searchByKeyWord(value)); 64 | }, 65 | getGoodsList: (search) => { 66 | dispatch(getGoodsList(search)) 67 | } 68 | } 69 | } 70 | 71 | // 用 connect 将store中的数据通过props的方式传递到App上 72 | export default connect(mapStateToProps, mapDispatchToProps)(ListPage) -------------------------------------------------------------------------------- /src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { InputItem, Toast } from 'antd-mobile'; 3 | import { withRouter } from 'react-router-dom' 4 | import "../assets/styles/Login.scss" 5 | 6 | import { connect } from "react-redux"; 7 | import { login } from '../store/action/usersAction' 8 | import store from '../store' 9 | import { encrypt } from '../utils/Tool' 10 | 11 | class Login extends Component { 12 | constructor(props){ 13 | super(props); 14 | store.subscribe(() => { 15 | // const { token, user } = state.usersReducer 16 | const { token } = store.getState().usersReducer 17 | // console.log('token',token) 18 | if(token) { 19 | this.props.history.push('/index') 20 | } 21 | }) 22 | this.state = { 23 | phone: '10086100108', 24 | password: '123456' 25 | } 26 | } 27 | 28 | login = async () => { 29 | const { phone, password } = this.state; 30 | if(phone.trim() == '' || phone.length < 11) { 31 | Toast.fail('手机号格式不正确',1) 32 | return 33 | } 34 | if(password.trim() == '') { 35 | Toast.fail('密码不能为空',1) 36 | return 37 | }else if(password.length < 6) { 38 | Toast.fail('密码长度应不小于6位数',1) 39 | return 40 | } 41 | this.props.login({ phone, password: encrypt(password) }) 42 | } 43 | 44 | render() { 45 | return ( 46 |
47 |
48 | {/* this.props.history.go(-1)}>X */} 49 |
50 |
51 |
52 |

登录

53 | { 54 | this.props.history.push('/register') 55 | }}>点此注册 56 |
57 | 58 |
59 | this.setState({ phone })} 64 | > 65 |
66 |
67 | this.setState({ password })} 73 | > 74 |
75 | {/*
76 | 短信验证码登录 77 |
*/} 78 |
79 | 登录 80 |
81 |
82 | 已有账号,忘记密码? 83 |
84 |
85 |
86 | ) 87 | } 88 | } 89 | 90 | const mapDispatchToProps = (dispatch) => { 91 | return { 92 | login: (value) => { 93 | dispatch(login(value)) 94 | } 95 | } 96 | } 97 | export default withRouter(connect(null, mapDispatchToProps)(Login)) -------------------------------------------------------------------------------- /src/pages/My/BrowseRecord.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { NavBar, Icon } from 'antd-mobile'; 3 | import '../../assets/styles/browse-record.scss' 4 | 5 | export default class BrowseRecord extends Component { 6 | state = { 7 | data: [ 8 | { 9 | title: '戴尔(DELL)成就5880英特尔酷睿i5商用办公台式机电脑整机(i5-10400F 8G 256G 1T 2G独显 三年上门)23.8英寸', 10 | img: require(`../../assets/images/goods/1.jpg`), 11 | price: 4699.00 12 | }, 13 | { 14 | title: '戴尔(DELL)成就5880英特尔酷睿i5商用办公台式机电脑整机(i5-10400F 8G 256G 1T 2G独显 三年上门)23.8英寸', 15 | img: require(`../../assets/images/goods/1.jpg`), 16 | price: 4699.00 17 | }, 18 | { 19 | title: '戴尔(DELL)成就5880英特尔酷睿i5商用办公台式机电脑整机(i5-10400F 8G 256G 1T 2G独显 三年上门)23.8英寸', 20 | img: require(`../../assets/images/goods/1.jpg`), 21 | price: 4699.00 22 | }, 23 | ] 24 | } 25 | render() { 26 | 27 | return ( 28 |
29 | } 32 | onLeftClick={() => this.props.history.go(-1)} 33 | >我的足迹 34 |
35 |
36 |
2020-08-25
37 |
38 | { 39 | this.state.data.map((item,index) => ( 40 |
41 | 42 |
43 |
{item.title}
44 |
{item.price}
45 |
46 |
47 | )) 48 | } 49 |
50 |
51 |
52 |
53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/pages/My/Collection.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { SearchHeader } from '../../components' 3 | import { Tabs } from 'antd-mobile'; 4 | import '../../assets/styles/Collection.scss'; 5 | 6 | const tabs = [ 7 | { title: '按时间查看' }, 8 | { title: '按店铺查看' }, 9 | { title: '看降价' }, 10 | ]; 11 | 12 | 13 | export default class Collection extends Component { 14 | 15 | render() { 16 | return ( 17 |
18 | 19 | { }} 22 | onTabClick={(tab, index) => { console.log('onTabClick', index, tab); }} 23 | > 24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 | ) 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/pages/My/Follow.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { NavBar, Icon } from 'antd-mobile'; 3 | 4 | export default class Follow extends Component { 5 | render() { 6 | return ( 7 |
8 | } 11 | onLeftClick={() => this.props.history.go(-1)} 12 | >我的关注 13 |
14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/My/My.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { withRouter } from 'react-router-dom' 3 | import { Badge, Icon, Toast } from 'antd-mobile'; 4 | import { Layout } from '../../components/index' 5 | import '../../assets/styles/My.scss' 6 | // 默认头像 7 | import defalutAvatar from '../../assets/images/defalutAvatar.jpg' 8 | 9 | class My extends Component { 10 | state = { 11 | messageCount: 1, 12 | navs: [ 13 | { 14 | title: '收藏', num: 4, path: '/my/collection' 15 | }, 16 | { 17 | title: '关注', num: 3, path: '/my/follow' 18 | }, 19 | { 20 | title: '足迹', num: 3, path: '/my/browserecord' 21 | }, 22 | { 23 | title: '余额', num: 666, path: '/my/purse' 24 | }, 25 | ], 26 | tools: [ 27 | { 28 | pathname: '/my/order', 29 | text: '待付款', 30 | icon: 32 | }, 33 | { 34 | pathname: '/my/order', 35 | text: '待发货', 36 | icon: 38 | }, 39 | { 40 | pathname: '/my/order', 41 | text: '待收货', 42 | icon: 44 | }, 45 | { 46 | pathname: '/my/order', 47 | text: '待评价', 48 | icon: 50 | }, 51 | { 52 | pathname: '/my/aftersale', 53 | text: '退款/售后', 54 | icon: 56 | } 57 | ], 58 | user: {} 59 | } 60 | 61 | componentDidMount() { 62 | const user = JSON.parse(localStorage.getItem('user')) || {} 63 | this.setState({ user }) 64 | } 65 | renderNavs = () => { 66 | return ( 67 |
68 | {this.state.navs.map((item, index) => ( 69 |
{ 71 | this.props.history.push({ 72 | pathname: item.path, 73 | query: { data : item } 74 | }) 75 | }} 76 | > 77 | {item.num} 78 | {item.title} 79 |
80 | ))} 81 |
82 | ) 83 | } 84 | 85 | RenderTools = () => { 86 | return
87 | { 88 | this.state.tools.map((item, index) => ( 89 |
96 | {item.icon} 97 |
{item.text}
98 |
99 | )) 100 | } 101 |
102 | } 103 | toolsItemClick = (item) => { 104 | if (item.pathname === '/my/aftersale') { 105 | Toast.info('此功能暂未开通', 1) 106 | return 107 | } 108 | this.props.history.push({ 109 | pathname: item.pathname, 110 | initialPage: item.initialPage, 111 | text: item.text 112 | }) 113 | } 114 | render() { 115 | const { messageCount, user } = this.state 116 | return ( 117 | 118 |
119 |
120 |
this.props.history.push('/login')}> 121 | 122 | { 123 | user.username ?
124 |
{user.username || '请登录'}
125 |
126 |
{user.gender || ''}
127 |
128 |
:
129 |
请登录
130 |
131 | } 132 | 133 |
134 |
135 | this.props.history.push('/search')} 136 | > 137 | this.props.history.push('/my/setup')} 139 | > 140 | { 141 | messageCount > 0 ? 142 | 143 | { 145 | Toast.info('此功能暂未开通', 1) 146 | console.log('message') 147 | }} 148 | >﹣ 149 | : { 151 | Toast.info('此功能暂未开通', 1) 152 | console.log('message') 153 | }} 154 | >﹣ 155 | } 156 | 157 |
158 |
159 | {this.renderNavs()} 160 |
161 |
162 |
163 |

我的订单

164 |
171 | 查看全部 172 | 173 |
174 |
175 | {this.RenderTools()} 176 |
177 |
178 | ) 179 | } 180 | } 181 | 182 | export default withRouter(My) 183 | -------------------------------------------------------------------------------- /src/pages/My/Mypurse.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { NavBar, Icon } from 'antd-mobile'; 3 | import '../../assets/styles/mypurse.scss' 4 | 5 | export default class Mypurse extends Component { 6 | render() { 7 | const { data } = this.props.location.query 8 | console.log('data: ', data); 9 | 10 | return ( 11 |
12 | } 15 | onLeftClick={() => this.props.history.go(-1)} 16 | >我的钱包 17 | 18 |
19 | 20 |
我的零钱
21 |
{data.num}
22 |
23 |
充值
24 |
提现
25 |
26 |
27 |
28 | ) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/pages/My/Order.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { SearchHeader, OrderList } from '../../components' 3 | import { Tabs } from 'antd-mobile'; 4 | 5 | import { connect } from "react-redux"; 6 | import { getOrderList } from '../../store/action/orderAction' 7 | import store from '../../store' 8 | 9 | const tabs = [ 10 | { title: '全部' }, 11 | { title: '待付款' }, 12 | { title: '待发货' }, 13 | { title: '待收货' }, 14 | { title: '待评价' }, 15 | ]; 16 | class Order extends Component { 17 | constructor(props) { 18 | super(props); 19 | store.subscribe(() => { 20 | const state = store.getState() 21 | const order_list = state.orderReducer.order_list 22 | // console.log('order_list: ', order_list); 23 | this.setState({ 24 | order_list 25 | }) 26 | }) 27 | this.state = { 28 | order_list: [] 29 | } 30 | 31 | } 32 | async componentDidMount() { 33 | //获取订单数据 34 | await this.props.getOrderList(0) 35 | } 36 | 37 | componentWillUnmount() { 38 | // 卸载异步操作设置状态 39 | this.setState = (state, callback) => { 40 | return; 41 | } 42 | } 43 | 44 | onChange = (tab) => { 45 | this.setState({ text: tab.title }) 46 | } 47 | render() { 48 | const { order_list } = this.state 49 | const { initialPage, text } = this.props.location 50 | return ( 51 |
52 | 53 | this.onChange(tab)} 56 | onTabClick={(tab, index) => { console.log('onTabClick', tab); }} 57 | > 58 | { 59 | tabs.map((item, index) => ( 60 | 61 | )) 62 | } 63 | 64 |
65 | ) 66 | } 67 | } 68 | const mapStateToProps = (state) => { 69 | return { 70 | order_list: state.orderReducer.order_list 71 | } 72 | } 73 | 74 | const mapDispatchToProps = (dispatch) => { 75 | return { 76 | getOrderList: (value) => { 77 | dispatch(getOrderList(value)); 78 | } 79 | } 80 | } 81 | export default connect(mapStateToProps, mapDispatchToProps)(Order) -------------------------------------------------------------------------------- /src/pages/My/SetUp.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { SearchHeader } from '../../components' 3 | import { List, Button, Modal } from 'antd-mobile'; 4 | import { Cookie } from '../../utils/storage' 5 | 6 | const alert = Modal.alert 7 | const Item = List.Item; 8 | 9 | export default class SetUp extends Component { 10 | state = { 11 | list: [ 12 | { thumb: 'https://zos.alipayobjects.com/rmsportal/dNuvNrtqUztHCwM.png', title: '个人信息' }, 13 | { thumb: 'https://zos.alipayobjects.com/rmsportal/dNuvNrtqUztHCwM.png', title: '账户安全' }, 14 | { thumb: 'https://zos.alipayobjects.com/rmsportal/dNuvNrtqUztHCwM.png', title: '通知消息提醒' }, 15 | { thumb: 'https://zos.alipayobjects.com/rmsportal/dNuvNrtqUztHCwM.png', title: '通用' }, 16 | { thumb: 'https://zos.alipayobjects.com/rmsportal/dNuvNrtqUztHCwM.png', title: '隐私' } 17 | ] 18 | } 19 | logOut = () => { 20 | alert('退出登录', '', [ 21 | { text: '取消', onPress: () => console.log('cancel') }, 22 | { text: '确定', onPress: () => { 23 | // 清除token 24 | Cookie.removeItem('token') 25 | localStorage.clear() 26 | this.props.history.push('/login') 27 | } }, 28 | ]) 29 | 30 | } 31 | render() { 32 | return ( 33 |
34 | 35 | 36 | {this.state.list.map((item, index) => ( 37 | { console.log(item.title) }} 43 | >{item.title} 44 | ))} 45 | 46 |
47 | 48 |
49 |
50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/pages/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class NotFound extends React.Component { 4 | 5 | render() { 6 | return ( 7 |

404 not found page

8 | ) 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/pages/Register.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component,Fragment } from 'react' 2 | import { InputItem, Toast } from 'antd-mobile'; 3 | import { withRouter } from 'react-router-dom' 4 | import { connect } from "react-redux"; 5 | import { login } from '../store/action/usersAction' 6 | import { axios, Api } from "../api/api" 7 | import "../assets/styles/Register.scss" 8 | import { encrypt } from '../utils/Tool' 9 | 10 | class Register extends Component { 11 | state = { 12 | phone: '', 13 | password: '', 14 | } 15 | register = async () => { 16 | const { phone, password } = this.state; 17 | if(phone.trim() == '' || phone.length < 11) { 18 | Toast.fail('手机号格式不正确',1) 19 | return 20 | } 21 | if(password.trim() == '') { 22 | Toast.fail('密码不能为空',1) 23 | return 24 | }else if(password.length < 6) { 25 | Toast.fail('密码长度应不小于6位数',1) 26 | return 27 | } 28 | let res = await axios.post(Api.register,{ 29 | phone, 30 | password: encrypt(password) 31 | }) 32 | if(res.code == 401) { 33 | Toast.fail(res.msg,1) 34 | }else { 35 | this.props.login({ phone, password: encrypt(password) }) 36 | } 37 | } 38 | render() { 39 | return ( 40 | 41 |
42 | {/* this.props.history.push('/login')}>X */} 43 |
44 |
45 |
46 |

{ 47 | this.props.history.push('/login') 48 | }}>登录

49 |
50 |
51 | this.setState({ phone })} 55 | > 56 |
57 |
58 | this.setState({ password })} 63 | > 64 |
65 |
66 | 注册 67 |
68 |
69 |
70 | ) 71 | } 72 | } 73 | const mapDispatchToProps = (dispatch) => { 74 | return { 75 | login: (value) => { 76 | dispatch(login(value)) 77 | } 78 | } 79 | } 80 | export default withRouter(connect(null, mapDispatchToProps)(Register)) -------------------------------------------------------------------------------- /src/pages/Search.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { AntdSearchBar } from '../components/index' 3 | import { connect } from "react-redux"; 4 | import { searchByKeyWord } from '../store/action/searchAction' 5 | import '../assets/styles/Search.scss' 6 | 7 | class Search extends Component { 8 | state = { 9 | // ['手机', '电脑', '耳机', '投影仪', '办公设备', '手表', '宠物', '花卉'] 10 | history_list: [] 11 | } 12 | 13 | componentDidMount() { 14 | //获取本地存储搜索记录 15 | let history_list = JSON.parse(localStorage.getItem('history_list')) || [] 16 | this.setState({ 17 | history_list 18 | }) 19 | //模拟数据 20 | localStorage.setItem('history_list', JSON.stringify(history_list)) 21 | } 22 | onSubmit = (value) => { 23 | //更新store中的state值 24 | this.props.searchByKeyWord(value) 25 | // 跳转列表页 26 | this.props.history.push({ 27 | pathname: '/listpage/' + value 28 | }) 29 | //获取本地存储搜索记录 30 | let history_list = JSON.parse(localStorage.getItem('history_list')) || [] 31 | if (history_list.includes(value)) return 32 | //添加新的搜索到本地存储 33 | history_list.unshift(value) 34 | localStorage.setItem('history_list', JSON.stringify(history_list)) 35 | } 36 | 37 | delHistory = () => { 38 | this.setState({ history_list: [] }) 39 | localStorage.removeItem('history_list') 40 | } 41 | 42 | render() { 43 | const { history_list } = this.state 44 | return ( 45 |
46 | 47 | {/* 最近搜索部分 */} 48 |
49 |
50 |

最近搜索

51 | 53 |
54 |
55 | {history_list.map((item, index) => ( 56 |
{ 60 | this.props.searchByKeyWord(item) 61 | this.props.history.push({ 62 | pathname: '/listpage/' + item 63 | }) 64 | }}> 65 | {item} 66 |
67 | ))} 68 |
69 |
70 |
71 | ) 72 | } 73 | } 74 | 75 | const mapStateToProps = (state) => { 76 | return { 77 | routes: state.routerReducer 78 | } 79 | } 80 | 81 | const mapDispatchToProps = (dispatch) => { 82 | return { 83 | searchByKeyWord: (value) => { 84 | dispatch(searchByKeyWord(value)); 85 | } 86 | } 87 | } 88 | 89 | // 用 connect 将store中的数据通过props的方式传递到App上 90 | export default connect(mapStateToProps, mapDispatchToProps)(Search) -------------------------------------------------------------------------------- /src/router/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import {Redirect,Route} from 'react-router-dom'; 4 | import { Cookie } from '../utils/storage' 5 | 6 | const PrivateRoute = ({ component: Component, ...rest }) => { 7 | const token = Cookie.getItem("token") 8 | console.log('token: ', token); 9 | return ( 10 | 15 | Boolean(token) ? 16 | ( 17 | 18 | ) 19 | : ( 20 | 26 | ) 27 | } 28 | /> 29 | ) 30 | } 31 | export { 32 | PrivateRoute 33 | } -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MyRoutes from './modules/my' 3 | 4 | export default [ 5 | { 6 | path: '/login', 7 | component: React.lazy( () => import('../pages/Login')), 8 | exact: true 9 | }, 10 | { 11 | path: '/register', 12 | component: React.lazy( () => import('../pages/Register')), 13 | exact: true 14 | }, 15 | { 16 | path: '/index', 17 | component: React.lazy( () => import('../pages/Home')), 18 | exact: true 19 | }, 20 | { 21 | path: '/cate', 22 | component: React.lazy( () => import('../pages/Cate')), 23 | exact: true 24 | }, 25 | { 26 | path: '/cart', 27 | component: React.lazy( () => import('../pages/Cart')), 28 | exact: true 29 | }, 30 | { 31 | path: '/search', 32 | component: React.lazy( () => import('../pages/Search')), 33 | 34 | }, 35 | { 36 | path: '/listpage/:keyword', 37 | component: React.lazy( () => import('../pages/ListPage')), 38 | }, 39 | { 40 | path: '/goodsDetail/:id', 41 | component: React.lazy( () => import('../pages/GoodsDetail')), 42 | }, 43 | { 44 | path: '/createOrder/:id', 45 | component: React.lazy( () => import('../pages/CreateOrder')), 46 | }, 47 | ...MyRoutes, 48 | 49 | { 50 | component: React.lazy( () => import('../pages/NotFound')), 51 | } 52 | ] -------------------------------------------------------------------------------- /src/router/modules/my.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default [ 4 | { 5 | path: '/my', 6 | component: React.lazy( () => import('../../pages/My/My')), 7 | exact: true, 8 | }, 9 | { 10 | path: '/my/collection', 11 | component: React.lazy( () => import('../../pages/My/Collection')), 12 | auth: true 13 | }, 14 | { 15 | path: '/my/follow', 16 | component: React.lazy( () => import('../../pages/My/Follow')), 17 | auth: true 18 | }, 19 | { 20 | path: '/my/browserecord', 21 | component: React.lazy( () => import('../../pages/My/BrowseRecord')), 22 | auth: true 23 | }, 24 | { 25 | path: '/my/purse', 26 | component: React.lazy( () => import('../../pages/My/Mypurse')), 27 | auth: true 28 | }, 29 | { 30 | path: '/my/setup', 31 | component: React.lazy( () => import('../../pages/My/SetUp')), 32 | auth: true 33 | }, 34 | { 35 | path: '/my/order', 36 | component: React.lazy( () => import('../../pages/My/Order')), 37 | auth: true 38 | }, 39 | ] -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/store/action/cateAction.js: -------------------------------------------------------------------------------- 1 | import { GET_CATE } from '../actionType/cateType' 2 | import { Api, axios } from '../../api/api' 3 | 4 | export const getCate = () => async (dispatch) => { 5 | let {data} = await axios.get(Api.getCate) 6 | // console.log('data: ', data); 7 | const action = { 8 | type: GET_CATE, 9 | data 10 | } 11 | dispatch(action) 12 | } -------------------------------------------------------------------------------- /src/store/action/goodsAction.js: -------------------------------------------------------------------------------- 1 | import {GETGOODSLIST} from '../actionType/goodsType' 2 | import { Api, axios } from '../../api/api' 3 | 4 | export const getGoodsList = (params) => async(dispatch)=>{ 5 | 6 | const res = await axios.get(Api.getGoodsList,{ 7 | params 8 | }) 9 | dispatch({type:GETGOODSLIST,goods: res.data}) 10 | } -------------------------------------------------------------------------------- /src/store/action/orderAction.js: -------------------------------------------------------------------------------- 1 | import { GETORDERSLIST } from '../actionType/orderType' 2 | import { Api, axios } from '../../api/api' 3 | 4 | export const getOrderList = (status) => async(dispatch)=>{ 5 | const res = await axios.post(Api.getOrderList,{status}) 6 | 7 | const { data: order_list } = res.data 8 | dispatch({type:GETORDERSLIST,order_list}) 9 | } -------------------------------------------------------------------------------- /src/store/action/routerAction.js: -------------------------------------------------------------------------------- 1 | import {CHANGEPATH} from '../actionType/routerType' 2 | export const changePath = (path) => { 3 | return { 4 | type:CHANGEPATH, 5 | path 6 | } 7 | } -------------------------------------------------------------------------------- /src/store/action/searchAction.js: -------------------------------------------------------------------------------- 1 | import { searchKeyWord,clearKeyWord } from "../actionType/searchType"; 2 | 3 | // SearchBar搜索 4 | export const searchByKeyWord = (value) => ({ 5 | type: searchKeyWord, 6 | value 7 | }) 8 | 9 | // 清空SearchBar 10 | export const clear= () => ({ 11 | type: clearKeyWord, 12 | value: '' 13 | }) -------------------------------------------------------------------------------- /src/store/action/usersAction.js: -------------------------------------------------------------------------------- 1 | import { LOGIN } from '../actionType/usersType' 2 | import { GET_USER_INFO } from '../actionType/usersType' 3 | import { Api, axios } from '../../api/api' 4 | import { Cookie } from '../../utils/storage' 5 | 6 | export const login = ({phone,password}) => async(dispatch)=>{ 7 | const res = await axios.post(Api.login,{phone,password}) 8 | // console.log('res',res) 9 | const action = { 10 | type: LOGIN, 11 | data: res.data 12 | } 13 | dispatch(action) 14 | Cookie.setItem('token',JSON.stringify(res.data),2) 15 | dispatch(getUserInfo(res.data)) 16 | } 17 | 18 | export const getUserInfo = (token) => async(dispatch)=>{ 19 | const res = await axios.post(Api.getUserInfo,{token}) 20 | // console.log('res',res) 21 | const { data } = res 22 | localStorage.setItem('user',JSON.stringify(data)) 23 | const action = { 24 | type: GET_USER_INFO, 25 | data 26 | } 27 | dispatch(action) 28 | } -------------------------------------------------------------------------------- /src/store/actionType/cateType.js: -------------------------------------------------------------------------------- 1 | export const GET_CATE = 'GET_CATE' -------------------------------------------------------------------------------- /src/store/actionType/goodsType.js: -------------------------------------------------------------------------------- 1 | export const GETGOODSLIST = 'GETGOODSLIST' 2 | -------------------------------------------------------------------------------- /src/store/actionType/orderType.js: -------------------------------------------------------------------------------- 1 | export const GETORDERSLIST = 'GETORDERSLIST' 2 | -------------------------------------------------------------------------------- /src/store/actionType/routerType.js: -------------------------------------------------------------------------------- 1 | export const CHANGEPATH = 'CHANGEPATH' -------------------------------------------------------------------------------- /src/store/actionType/searchType.js: -------------------------------------------------------------------------------- 1 | export const searchKeyWord = "searchKeyWord"; 2 | export const clearKeyWord = 'clearKeyWord' -------------------------------------------------------------------------------- /src/store/actionType/usersType.js: -------------------------------------------------------------------------------- 1 | export const LOGIN ='LOGIN' 2 | export const GET_USER_INFO = 'GET_USER_INFO' 3 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | // 引入 store生成器 2 | import { createStore, applyMiddleware } from "redux"; 3 | // 引入reducer 4 | import reducer from "./reducer"; 5 | import reduxThunk from "redux-thunk"; 6 | 7 | // 引入redux-persist实现redux数据持久化 8 | import {persistStore, persistReducer} from 'redux-persist'; 9 | import storage from 'redux-persist/lib/storage'; 10 | import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'; 11 | 12 | const persistConfig = { 13 | key: 'root', 14 | storage: storage, 15 | stateReconciler: autoMergeLevel2 // 查看 'Merge Process' 部分的具体情况 16 | }; 17 | 18 | const myPersistReducer = persistReducer(persistConfig, reducer) 19 | 20 | // 创建store 使用中间件连接器将redux-thunk传入 store构造器 21 | // const store = createStore(reducer, applyMiddleware(reduxThunk)); 22 | const store = createStore(myPersistReducer, applyMiddleware(reduxThunk)); 23 | 24 | 25 | export const persistor = persistStore(store) 26 | export default store -------------------------------------------------------------------------------- /src/store/reducer/cateReducer.js: -------------------------------------------------------------------------------- 1 | import { GET_CATE } from '../actionType/cateType' 2 | const initState={ 3 | cates:[] 4 | } 5 | export default (state=initState,action)=>{ 6 | switch(action.type){ 7 | case GET_CATE: 8 | return { 9 | ...state, 10 | cates:action.data 11 | } 12 | default: 13 | return state 14 | } 15 | } -------------------------------------------------------------------------------- /src/store/reducer/goodsReducer.js: -------------------------------------------------------------------------------- 1 | import {GETGOODSLIST} from '../actionType/goodsType' 2 | 3 | const initState ={ 4 | goods:[] 5 | } 6 | export default (state=initState,action)=>{ 7 | switch(action.type){ 8 | case GETGOODSLIST: 9 | return { 10 | ...state, 11 | goods:action.goods 12 | } 13 | default: 14 | return state; 15 | } 16 | } -------------------------------------------------------------------------------- /src/store/reducer/index.js: -------------------------------------------------------------------------------- 1 | // 1 引入 合并reducer的对象 2 | import { combineReducers } from "redux"; 3 | //用户 4 | import usersReducer from './usersReducer' 5 | //搜索 6 | import searchReducer from "./searchReducer"; 7 | //商品 8 | import goodsReducer from './goodsReducer' 9 | //订单 10 | import orderReducer from './orderReducer' 11 | //路由 12 | import routerReducer from "./routerReducer"; 13 | //分类 14 | import cateReducer from './cateReducer' 15 | 16 | // 2 对象的形式传入 要合并的reducer 17 | 18 | const rootReducer = combineReducers({ 19 | usersReducer, 20 | searchReducer, 21 | goodsReducer, 22 | orderReducer, 23 | routerReducer, 24 | cateReducer 25 | }); 26 | export default rootReducer; -------------------------------------------------------------------------------- /src/store/reducer/orderReducer.js: -------------------------------------------------------------------------------- 1 | import {GETORDERSLIST} from '../actionType/orderType' 2 | 3 | const initState ={ 4 | order_list:[] 5 | } 6 | export default (state=initState,action)=>{ 7 | const { type, order_list } = action 8 | switch(type){ 9 | case GETORDERSLIST: 10 | return { 11 | ...state, 12 | order_list: order_list 13 | } 14 | default: 15 | return state; 16 | } 17 | } -------------------------------------------------------------------------------- /src/store/reducer/routerReducer.js: -------------------------------------------------------------------------------- 1 | import {CHANGEPATH} from '../actionType/routerType' 2 | const initState={ 3 | path:null, 4 | paths:['/','/index','/cate','/cart','/my'] 5 | } 6 | export default (state=initState,action)=>{ 7 | switch(action.type){ 8 | case CHANGEPATH: 9 | return { 10 | ...state, 11 | path:action.path 12 | } 13 | default: 14 | return state 15 | } 16 | } -------------------------------------------------------------------------------- /src/store/reducer/searchReducer.js: -------------------------------------------------------------------------------- 1 | import { searchKeyWord, clearKeyWord } from "../actionType/searchType"; 2 | const defaultState = { 3 | searchInput: { 4 | keyword: '' 5 | } 6 | }; 7 | export default (state = defaultState, action) => { 8 | if (action.type === searchKeyWord) { 9 | let newState = Object.assign({}, state); 10 | newState.searchInput.keyword = action.value; 11 | return newState; 12 | }else if (action.type === clearKeyWord) { 13 | let newState = Object.assign({}, state); 14 | newState.searchInput.keyword = action.value; 15 | return newState; 16 | } 17 | return state; 18 | } -------------------------------------------------------------------------------- /src/store/reducer/usersReducer.js: -------------------------------------------------------------------------------- 1 | import { LOGIN } from '../actionType/usersType' 2 | import { GET_USER_INFO } from '../actionType/usersType' 3 | 4 | const initState = { 5 | token: '', 6 | user: { 7 | 8 | } 9 | } 10 | export default (state = initState, action) => { 11 | const { type, data } = action 12 | if(type === LOGIN) { 13 | const newState = state 14 | newState.token = data 15 | return newState 16 | }else if(type === GET_USER_INFO) { 17 | const newState = state 18 | newState.user = data 19 | return newState 20 | } 21 | return state 22 | } -------------------------------------------------------------------------------- /src/utils/Tool.js: -------------------------------------------------------------------------------- 1 | const CryptoJS = require("crypto-js"); 2 | 3 | const key = 'useyourkey' 4 | 5 | // Encrypt 加密 6 | export function encrypt(text){ 7 | return CryptoJS.AES.encrypt(text, key).toString(); 8 | } 9 | 10 | 11 | // Decrypt 解密 12 | export function decrypt(cipherText){ 13 | let bytes = CryptoJS.AES.decrypt(cipherText, key); 14 | return bytes.toString(CryptoJS.enc.Utf8); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | 2 | import Axios from "axios" 3 | import { REACT_APP_API_URL } from "./urls"; 4 | import { Toast } from 'antd-mobile' 5 | import { Cookie } from './storage' 6 | 7 | //1.添加通用前缀 8 | const axios = Axios.create({ 9 | baseURL: REACT_APP_API_URL, 10 | }); 11 | //2.添加请求和返回的拦截 12 | //3.加遮罩 13 | axios.interceptors.request.use(function (config) { 14 | // Toast.loading('Loading...', 0, null,true); 15 | config.timeout = 20000; 16 | const token = Cookie.getItem('token') || '' 17 | if(token) { 18 | config.headers['Authorization'] = 'Bearer ' + token 19 | } 20 | return config; 21 | }, function (error) { 22 | console.log('error: ', error); 23 | // Do something with request error 24 | // Toast.hide(); 25 | return Promise.reject(error); 26 | }); 27 | 28 | // Add a response interceptor 29 | axios.interceptors.response.use(function (response) { 30 | // Toast.hide(); 31 | console.log('response.data: ', response.data); 32 | if(response.data.code === 403){ 33 | Toast.fail(response.data.msg, 2, null, true) 34 | 35 | }else if(response.data.code === 500){ 36 | Toast.fail(response.data.msg, 2, null, true) 37 | 38 | } 39 | return response.data; 40 | 41 | }, function (error) { 42 | // Toast.fail('请求失败', 2, null, true) 43 | Toast.fail(error.response.msg, 2, null, true) 44 | return Promise.reject(error); 45 | }); 46 | 47 | 48 | export default axios -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | const Cookie = { 2 | //根据key值获取对应的cookie 3 | getItem(key) { 4 | //获取cookie 5 | const data = document.cookie 6 | //获取key第一次出现的位置 7 | let startIndex = data.indexOf(key + '=') 8 | //如果开始索引值大于0表示有cookie 9 | if (startIndex > -1) { 10 | //key的起始位置等于出现的位置加key的长度+1 11 | startIndex = startIndex + key.length + 1 12 | //结束位置等于从key开始的位置之后第一次;号所出现的位置 13 | let endIndex = data.indexOf(';', startIndex) 14 | //如果未找到结尾位置则结尾位置等于cookie长度,之后的内容全部获取 15 | endIndex = endIndex < 0 ? data.length : endIndex 16 | 17 | return decodeURIComponent(data.substring(startIndex, endIndex)) 18 | } else { 19 | return '' 20 | } 21 | }, 22 | 23 | setItem(key, value, time) { 24 | //默认保存时间 25 | const times = time || 1 26 | //获取当前时间 27 | const cur = new Date() 28 | // 设置指定时间 29 | cur.setTime(cur.getTime() + times * 24 * 3600 * 1000) 30 | //创建cookie 并且设置生存周期为UTC时间 31 | document.cookie = key + '=' + encodeURIComponent(value) + ';expires=' + (times === undefined ? '' : cur.toUTCString()) 32 | }, 33 | 34 | removeItem(key) { 35 | //获取cookie 36 | const data = this.getItem(key) 37 | //如果获取到cookie则重新设置cookie的生存周期为过去时间 38 | if (data !== false) { 39 | this.setItem(key, data, -1) 40 | } 41 | } 42 | } 43 | 44 | export { 45 | Cookie 46 | } -------------------------------------------------------------------------------- /src/utils/urls.js: -------------------------------------------------------------------------------- 1 | /* 2 | 接口访问的前缀 3 | 静态资源访问的前缀 4 | */ 5 | 6 | const REACT_APP_API_URL = process.env.REACT_APP_API_URL 7 | 8 | const REACT_APP_STATIC_URL = process.env.REACT_APP_STATIC_URL 9 | 10 | export { 11 | REACT_APP_API_URL, 12 | REACT_APP_STATIC_URL 13 | } --------------------------------------------------------------------------------