commit cc3972e7196c23c809710fb77c546720c73e3079 Author: wtq <2394975549@qq.com> Date: Fri Nov 28 09:19:55 2025 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d29575 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d9614c --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+You will also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +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. + +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. + +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. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting + +### Analyzing the Bundle Size + +This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size + +### Making a Progressive Web App + +This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app + +### Advanced Configuration + +This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration + +### Deployment + +This section has moved here: https://facebook.github.io/create-react-app/docs/deployment + +### `npm run build` fails to minify + +This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify diff --git a/config/env.js b/config/env.js new file mode 100644 index 0000000..b0344c5 --- /dev/null +++ b/config/env.js @@ -0,0 +1,93 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const paths = require('./paths'); + +// Make sure that including paths.js after env.js will read .env variables. +delete require.cache[require.resolve('./paths')]; + +const NODE_ENV = process.env.NODE_ENV; +if (!NODE_ENV) { + throw new Error( + 'The NODE_ENV environment variable is required but was not specified.' + ); +} + +// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use +var dotenvFiles = [ + `${paths.dotenv}.${NODE_ENV}.local`, + `${paths.dotenv}.${NODE_ENV}`, + // Don't include `.env.local` for `test` environment + // since normally you expect tests to produce the same + // results for everyone + NODE_ENV !== 'test' && `${paths.dotenv}.local`, + paths.dotenv, +].filter(Boolean); + +// Load environment variables from .env* files. Suppress warnings using silent +// if this file is missing. dotenv will never modify any environment variables +// that have already been set. Variable expansion is supported in .env files. +// https://github.com/motdotla/dotenv +// https://github.com/motdotla/dotenv-expand +dotenvFiles.forEach(dotenvFile => { + if (fs.existsSync(dotenvFile)) { + require('dotenv-expand')( + require('dotenv').config({ + path: dotenvFile, + }) + ); + } +}); + +// We support resolving modules according to `NODE_PATH`. +// This lets you use absolute paths in imports inside large monorepos: +// https://github.com/facebook/create-react-app/issues/253. +// It works similar to `NODE_PATH` in Node itself: +// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders +// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. +// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. +// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 +// We also resolve them to make sure all tools using them work consistently. +const appDirectory = fs.realpathSync(process.cwd()); +process.env.NODE_PATH = (process.env.NODE_PATH || '') + .split(path.delimiter) + .filter(folder => folder && !path.isAbsolute(folder)) + .map(folder => path.resolve(appDirectory, folder)) + .join(path.delimiter); + +// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be +// injected into the application via DefinePlugin in Webpack configuration. +const REACT_APP = /^REACT_APP_/i; + +function getClientEnvironment(publicUrl) { + const raw = Object.keys(process.env) + .filter(key => REACT_APP.test(key)) + .reduce( + (env, key) => { + env[key] = process.env[key]; + return env; + }, + { + // Useful for determining whether we’re running in production mode. + // Most importantly, it switches React into the correct mode. + NODE_ENV: process.env.NODE_ENV || 'development', + // Useful for resolving the correct path to static assets in `public`. + // For example, . + // This should only be used as an escape hatch. Normally you would put + // images into the `src` and `import` them in code to get their paths. + PUBLIC_URL: publicUrl, + } + ); + // Stringify all values so we can feed into Webpack DefinePlugin + const stringified = { + 'process.env': Object.keys(raw).reduce((env, key) => { + env[key] = JSON.stringify(raw[key]); + return env; + }, {}), + }; + + return { raw, stringified }; +} + +module.exports = getClientEnvironment; diff --git a/config/jest/cssTransform.js b/config/jest/cssTransform.js new file mode 100644 index 0000000..8f65114 --- /dev/null +++ b/config/jest/cssTransform.js @@ -0,0 +1,14 @@ +'use strict'; + +// This is a custom Jest transformer turning style imports into empty objects. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process() { + return 'module.exports = {};'; + }, + getCacheKey() { + // The output is always the same. + return 'cssTransform'; + }, +}; diff --git a/config/jest/fileTransform.js b/config/jest/fileTransform.js new file mode 100644 index 0000000..4ed6bdb --- /dev/null +++ b/config/jest/fileTransform.js @@ -0,0 +1,31 @@ +'use strict'; + +const path = require('path'); + +// This is a custom Jest transformer turning file imports into filenames. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process(src, filename) { + const assetFilename = JSON.stringify(path.basename(filename)); + + if (filename.match(/\.svg$/)) { + return `const React = require('react'); + module.exports = { + __esModule: true, + default: ${assetFilename}, + ReactComponent: React.forwardRef((props, ref) => ({ + $$typeof: Symbol.for('react.element'), + type: 'svg', + ref: ref, + key: null, + props: Object.assign({}, props, { + children: ${assetFilename} + }) + })), + };`; + } + + return `module.exports = ${assetFilename};`; + }, +}; diff --git a/config/paths.js b/config/paths.js new file mode 100644 index 0000000..c24b4dd --- /dev/null +++ b/config/paths.js @@ -0,0 +1,89 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +// Make sure any symlinks in the project folder are resolved: +// https://github.com/facebook/create-react-app/issues/637 +const appDirectory = fs.realpathSync(process.cwd()); +const resolveApp = relativePath => path.resolve(appDirectory, relativePath); + +const envPublicUrl = process.env.PUBLIC_URL; + +function ensureSlash(inputPath, needsSlash) { + const hasSlash = inputPath.endsWith('/'); + if (hasSlash && !needsSlash) { + return inputPath.substr(0, inputPath.length - 1); + } else if (!hasSlash && needsSlash) { + return `${inputPath}/`; + } else { + return inputPath; + } +} + +const getPublicUrl = appPackageJson => + envPublicUrl || require(appPackageJson).homepage; + +// We use `PUBLIC_URL` environment variable or "homepage" field to infer +// "public path" at which the app is served. +// Webpack needs to know it to put the right + + + +
+ + + + diff --git a/public/inobounce.min.js b/public/inobounce.min.js new file mode 100644 index 0000000..ba690e1 --- /dev/null +++ b/public/inobounce.min.js @@ -0,0 +1 @@ +(function(global){var startY=0;var enabled=false;var supportsPassiveOption=false;try{var opts=Object.defineProperty({},"passive",{get:function(){supportsPassiveOption=true}});window.addEventListener("test",null,opts)}catch(e){}var handleTouchmove=function(evt){var el=evt.target;while(el!==document.body&&el!==document){var style=window.getComputedStyle(el);if(!style){break}if(el.nodeName==="INPUT"&&el.getAttribute("type")==="range"){return}var scrolling=style.getPropertyValue("-webkit-overflow-scrolling");var overflowY=style.getPropertyValue("overflow-y");var height=parseInt(style.getPropertyValue("height"),10);var isScrollable=scrolling==="touch"&&(overflowY==="auto"||overflowY==="scroll");var canScroll=el.scrollHeight>el.offsetHeight;if(isScrollable&&canScroll){var curY=evt.touches?evt.touches[0].screenY:evt.screenY;var isAtTop=startY<=curY&&el.scrollTop===0;var isAtBottom=startY>=curY&&el.scrollHeight-el.scrollTop===height;if(isAtTop||isAtBottom){evt.preventDefault()}return}el=el.parentNode}evt.preventDefault()};var handleTouchstart=function(evt){startY=evt.touches?evt.touches[0].screenY:evt.screenY};var enable=function(){window.addEventListener("touchstart",handleTouchstart,supportsPassiveOption?{passive:false}:false);window.addEventListener("touchmove",handleTouchmove,supportsPassiveOption?{passive:false}:false);enabled=true};var disable=function(){window.removeEventListener("touchstart",handleTouchstart,false);window.removeEventListener("touchmove",handleTouchmove,false);enabled=false};var isEnabled=function(){return enabled};var testDiv=document.createElement("div");document.documentElement.appendChild(testDiv);testDiv.style.WebkitOverflowScrolling="touch";var scrollSupport="getComputedStyle"in window&&window.getComputedStyle(testDiv)["-webkit-overflow-scrolling"]==="touch";document.documentElement.removeChild(testDiv);if(scrollSupport){enable()}var iNoBounce={enable:enable,disable:disable,isEnabled:isEnabled};if(typeof module!=="undefined"&&module.exports){module.exports=iNoBounce}if(typeof global.define==="function"){(function(define){define("iNoBounce",[],function(){return iNoBounce})})(global.define)}else{global.iNoBounce=iNoBounce}})(this); \ No newline at end of file diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..1f2f141 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/scripts/build.js b/scripts/build.js new file mode 100644 index 0000000..e01b7a5 --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,192 @@ +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'production'; +process.env.NODE_ENV = 'production'; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); + + +const path = require('path'); +const chalk = require('react-dev-utils/chalk'); +const fs = require('fs-extra'); +const webpack = require('webpack'); +const bfj = require('bfj'); +const configFactory = require('../config/webpack.config'); +const paths = require('../config/paths'); +const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); +const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); +const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); +const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); +const printBuildError = require('react-dev-utils/printBuildError'); + +const measureFileSizesBeforeBuild = + FileSizeReporter.measureFileSizesBeforeBuild; +const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; +const useYarn = fs.existsSync(paths.yarnLockFile); + +// These sizes are pretty large. We'll warn for bundles exceeding them. +const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; +const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; + +const isInteractive = process.stdout.isTTY; + +// Warn and crash if required files are missing +if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { + process.exit(1); +} + +// Process CLI arguments +const argv = process.argv.slice(2); +const writeStatsJson = argv.indexOf('--stats') !== -1; + +// Generate configuration +const config = configFactory('production'); + +// We require that you explicitly set browsers and do not fall back to +// browserslist defaults. +const { checkBrowsers } = require('react-dev-utils/browsersHelper'); +checkBrowsers(paths.appPath, isInteractive) + .then(() => { + // First, read the current file sizes in build directory. + // This lets us display how much they changed later. + return measureFileSizesBeforeBuild(paths.appBuild); + }) + .then(previousFileSizes => { + // Remove all content but keep the directory so that + // if you're in it, you don't end up in Trash + fs.emptyDirSync(paths.appBuild); + // Merge with the public folder + copyPublicFolder(); + // Start the webpack build + return build(previousFileSizes); + }) + .then( + ({ stats, previousFileSizes, warnings }) => { + if (warnings.length) { + console.log(chalk.yellow('Compiled with warnings.\n')); + console.log(warnings.join('\n\n')); + console.log( + '\nSearch for the ' + + chalk.underline(chalk.yellow('keywords')) + + ' to learn more about each warning.' + ); + console.log( + 'To ignore, add ' + + chalk.cyan('// eslint-disable-next-line') + + ' to the line before.\n' + ); + } else { + console.log(chalk.green('Compiled successfully.\n')); + } + + console.log('File sizes after gzip:\n'); + printFileSizesAfterBuild( + stats, + previousFileSizes, + paths.appBuild, + WARN_AFTER_BUNDLE_GZIP_SIZE, + WARN_AFTER_CHUNK_GZIP_SIZE + ); + console.log(); + + const appPackage = require(paths.appPackageJson); + const publicUrl = paths.publicUrl; + const publicPath = config.output.publicPath; + const buildFolder = path.relative(process.cwd(), paths.appBuild); + printHostingInstructions( + appPackage, + publicUrl, + publicPath, + buildFolder, + useYarn + ); + }, + err => { + console.log(chalk.red('Failed to compile.\n')); + printBuildError(err); + process.exit(1); + } + ) + .catch(err => { + if (err && err.message) { + console.log(err.message); + } + process.exit(1); + }); + +// Create the production build and print the deployment instructions. +function build(previousFileSizes) { + console.log('Creating an optimized production build...'); + + let compiler = webpack(config); + return new Promise((resolve, reject) => { + compiler.run((err, stats) => { + let messages; + if (err) { + if (!err.message) { + return reject(err); + } + messages = formatWebpackMessages({ + errors: [err.message], + warnings: [], + }); + } else { + messages = formatWebpackMessages( + stats.toJson({ all: false, warnings: true, errors: true }) + ); + } + if (messages.errors.length) { + // Only keep the first error. Others are often indicative + // of the same problem, but confuse the reader with noise. + if (messages.errors.length > 1) { + messages.errors.length = 1; + } + return reject(new Error(messages.errors.join('\n\n'))); + } + if ( + process.env.CI && + (typeof process.env.CI !== 'string' || + process.env.CI.toLowerCase() !== 'false') && + messages.warnings.length + ) { + console.log( + chalk.yellow( + '\nTreating warnings as errors because process.env.CI = true.\n' + + 'Most CI servers set it automatically.\n' + ) + ); + return reject(new Error(messages.warnings.join('\n\n'))); + } + + const resolveArgs = { + stats, + previousFileSizes, + warnings: messages.warnings, + }; + if (writeStatsJson) { + return bfj + .write(paths.appBuild + '/bundle-stats.json', stats.toJson()) + .then(() => resolve(resolveArgs)) + .catch(error => reject(new Error(error))); + } + + return resolve(resolveArgs); + }); + }); +} + +function copyPublicFolder() { + fs.copySync(paths.appPublic, paths.appBuild, { + dereference: true, + filter: file => file !== paths.appHtml, + }); +} diff --git a/scripts/start.js b/scripts/start.js new file mode 100644 index 0000000..99bb4ab --- /dev/null +++ b/scripts/start.js @@ -0,0 +1,132 @@ +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'development'; +process.env.NODE_ENV = 'development'; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); + + +const fs = require('fs'); +const chalk = require('react-dev-utils/chalk'); +const webpack = require('webpack'); +const WebpackDevServer = require('webpack-dev-server'); +const clearConsole = require('react-dev-utils/clearConsole'); +const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); +const { + choosePort, + createCompiler, + prepareProxy, + prepareUrls, +} = require('react-dev-utils/WebpackDevServerUtils'); +const openBrowser = require('react-dev-utils/openBrowser'); +const paths = require('../config/paths'); +const configFactory = require('../config/webpack.config'); +const createDevServerConfig = require('../config/webpackDevServer.config'); + +const useYarn = fs.existsSync(paths.yarnLockFile); +const isInteractive = process.stdout.isTTY; + +// Warn and crash if required files are missing +if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { + process.exit(1); +} + +// Tools like Cloud9 rely on this. +const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; +const HOST = process.env.HOST || '0.0.0.0'; + +if (process.env.HOST) { + console.log( + chalk.cyan( + `Attempting to bind to HOST environment variable: ${chalk.yellow( + chalk.bold(process.env.HOST) + )}` + ) + ); + console.log( + `If this was unintentional, check that you haven't mistakenly set it in your shell.` + ); + console.log( + `Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}` + ); + console.log(); +} + +// We require that you explictly set browsers and do not fall back to +// browserslist defaults. +const { checkBrowsers } = require('react-dev-utils/browsersHelper'); +checkBrowsers(paths.appPath, isInteractive) + .then(() => { + // We attempt to use the default port but if it is busy, we offer the user to + // run on a different port. `choosePort()` Promise resolves to the next free port. + return choosePort(HOST, DEFAULT_PORT); + }) + .then(port => { + if (port == null) { + // We have not found a port. + return; + } + const config = configFactory('development'); + const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; + const appName = require(paths.appPackageJson).name; + const useTypeScript = fs.existsSync(paths.appTsConfig); + const urls = prepareUrls(protocol, HOST, port); + const devSocket = { + warnings: warnings => + devServer.sockWrite(devServer.sockets, 'warnings', warnings), + errors: errors => + devServer.sockWrite(devServer.sockets, 'errors', errors), + }; + // Create a webpack compiler that is configured with custom messages. + const compiler = createCompiler({ + appName, + config, + devSocket, + urls, + useYarn, + useTypeScript, + webpack, + }); + // Load proxy config + const proxySetting = require(paths.appPackageJson).proxy; + const proxyConfig = prepareProxy(proxySetting, paths.appPublic); + // Serve webpack assets generated by the compiler over a web server. + const serverConfig = createDevServerConfig( + proxyConfig, + urls.lanUrlForConfig + ); + const devServer = new WebpackDevServer(compiler, serverConfig); + // Launch WebpackDevServer. + devServer.listen(port, HOST, err => { + if (err) { + return console.log(err); + } + if (isInteractive) { + clearConsole(); + } + console.log(chalk.cyan('Starting the development server...\n')); + openBrowser(urls.localUrlForBrowser); + }); + + ['SIGINT', 'SIGTERM'].forEach(function(sig) { + process.on(sig, function() { + devServer.close(); + process.exit(); + }); + }); + }) + .catch(err => { + if (err && err.message) { + console.log(err.message); + } + process.exit(1); + }); diff --git a/scripts/test.js b/scripts/test.js new file mode 100644 index 0000000..4172e42 --- /dev/null +++ b/scripts/test.js @@ -0,0 +1,60 @@ +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'test'; +process.env.NODE_ENV = 'test'; +process.env.PUBLIC_URL = ''; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); + + +const jest = require('jest'); +const execSync = require('child_process').execSync; +let argv = process.argv.slice(2); + +function isInGitRepository() { + try { + execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); + return true; + } catch (e) { + return false; + } +} + +function isInMercurialRepository() { + try { + execSync('hg --cwd . root', { stdio: 'ignore' }); + return true; + } catch (e) { + return false; + } +} + +// Watch unless on CI, in coverage mode, explicitly adding `--no-watch`, +// or explicitly running all tests +if ( + !process.env.CI && + argv.indexOf('--coverage') === -1 && + argv.indexOf('--no-watch') === -1 && + argv.indexOf('--watchAll') === -1 +) { + // https://github.com/facebook/create-react-app/issues/5210 + const hasSourceControl = isInGitRepository() || isInMercurialRepository(); + argv.push(hasSourceControl ? '--watch' : '--watchAll'); +} + +// Jest doesn't have this option so we'll remove it +if (argv.indexOf('--no-watch') !== -1) { + argv = argv.filter(arg => arg !== '--no-watch'); +} + + +jest.run(argv); diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000..3ecfdb9 --- /dev/null +++ b/src/App.js @@ -0,0 +1,185 @@ +import React, { Component } from 'react'; +import {Route} from 'react-router-dom'; +import AnimatedRouter from 'react-animated-router'; //我们的AnimatedRouter组件 +import 'react-animated-router/animate.css'; //引入默认的动画样式定义 +import {connect} from 'react-redux'; +import {setToken, setCMS, setVendorOrgCode, setCityLevel2, setLogin} from '@/store/actions'; +import {DEBUG, temToken, debugPath} from '@/config'; +import fetchJson from '@/utils/fetch'; +import {setCookie, getCookie} from '@/utils/tools'; +import {mapPlaces} from '@/utils/mapData'; +// import * as dd from 'dingtalk-jsapi'; +import {getRoot} from '@/utils/getRoot.js' + +// import HomePage from '@/pages/HomePage'; +import BottomBar from '@/components/BottomBar'; +import {IndexList, goodsManager, orderManager} from '@/router'; +import Other from '@/pages/Other'; +import DDAuth from '@/pages/DDAuth'; +import Loading from '@/components/Loading'; +import Register from '@/pages/Register'; +import KeepAlive, {AliveScope} from 'react-activation' +import OrderManager from '@/pages/OrderManager'; +// import Dialog from '@/components/Dialog'; + +// 钉钉左侧按钮 +// if (dd.ios) { +// dd.ready(() => { +// dd.biz.navigation.setLeft({ +// control: true, +// text: '关闭', +// onSuccess: () => { +// Dialog.show('提示', '是否离开【京西菜市管理平台】', {}, res => { +// if (res) { +// dd.biz.navigation.close(); +// } +// }); +// } +// }); +// }) +// } else if (dd.android) { +// dd.ready(() => { +// document.addEventListener('backbutton', function(e) { +// e.preventDefault(); +// Dialog.show('提示', '是否离开【京西菜市管理平台】', {}, res => { +// if (res) { +// dd.biz.navigation.close(); +// } +// }); +// return false; +// // window.location.href =src; +// }); +// }) +// } + +// 关闭手机滑动 +document.querySelector('body').addEventListener('touchmove', e => { + e.preventDefault(); +}, {passive: false}); + +class App extends Component { + // constructor (...args) { + // super(...args); + // } + // async componentDidUpdate () { + // console.log('app did update'); + // // await this.checkLogin(); + // } + async componentDidMount () { + // 获取权限信息 + console.log('获取权限') + await getRoot() + await this.checkLogin(); + } + // 检测登录信息 + async checkLogin () { + setCookie('token', '') + if (!this.props.user.login && this.props.location.pathname !== '/ddauth') { + if (DEBUG) { + // 调试模式 + console.log('Debug') + await this.props.setToken(temToken); + await setCookie('token', temToken); + await this.props.setLogin(true); + await this.getBase(); + // this.props.history.push('/orderdetail?vendorOrderID=916837487000241'); + // this.props.history.push('/wmtojx'); + this.props.history.push(debugPath); + } else { + // 正式环境 + // 查看cookie是否存在 + let token = getCookie('token'); + console.log(token); + if (!token) { + console.log('token 不存在'); + this.props.history.push('/ddauth'); + return false; + } else { + // 存在token + console.log('App.js:存在token') + await this.props.setToken(token, 7); + await this.props.setLogin(true); + await this.getBase(); + } + } + } + } + // 请求基础信息 + async getBase () { + try { + Loading.show(); + // 请求 + let {metaData} = await fetchJson('/v2/cms/GetServiceInfo'); + // 请求城市列表 level = 2 + let res = await fetchJson('/v2/cms/GetPlaces?level=2'); + // 请求平台账号 + let vendorOrgCode = await fetchJson('/v2/cms/GetVendorOrgCodeInfo') + // let vendorOrgCode = {"0":["320406","349454"],"1":["589"],"3":["34665"]} + // console.log(metaData); + // DEBUG + // if (DEBUG) vendorOrgCode['0'][1] = '123456' + console.log('平台账号', vendorOrgCode) + this.props.setCMS(metaData); + this.props.setCityLevel2(mapPlaces(res)); + this.props.setVendorOrgCode(vendorOrgCode) + console.log('Places', mapPlaces(res)); + console.log('CMS', this.props.system.cms); + } catch (e) {} finally { + Loading.hide(); + } + } + render() { + // 是否显示底部菜单 + let bottomShow = IndexList.some(item => item.to === this.props.location.pathname) + return ( +
+ { + this.props.user.login && ( +
+ + { + IndexList.map((link, index) => ( + link.text !== '订单管理' ? : + ( + + + + ) + }> + + )) + } + + + {/* 商品管理子页面 */} + { + goodsManager.map((link, index) => ( + + )) + } + {/* 订单管理子页面 */} + { + orderManager.map((link, index) => ( + + )) + } +
+ ) + } + {/* 钉钉auth */} + + + + {/* 底部菜单 */} + { + bottomShow && + } +
+ ); + } +} + +export default connect((state, props) => Object.assign({}, props, state), { + setToken, setCMS, setCityLevel2, setLogin, setVendorOrgCode +})(App); diff --git a/src/apis/store.txt b/src/apis/store.txt new file mode 100644 index 0000000..5a30423 --- /dev/null +++ b/src/apis/store.txt @@ -0,0 +1,195 @@ +get /store/GetStores 获取门店信息 + *token + keyword str 关键字 + storeID int 门店ID + name str 门店名称(模糊) + placeID int 所属地点ID + placeLevel int 所属地点级别 + address str 门店地址 + tel str 电话 + statuss str [-1, 0, 1] -1禁用 0休息 1正常 + vendorStoreCond str 查询平台门店绑定条件 and与 or或 + vendorStoreConds str {vendorID: cond} cond: -1没有关联 0不限定 1有关联 + courierStoreCond str 查询专送门店绑定条件 and与 or或 + courierStoreConds + offset int 起始序号 + pageSize int 页码大小 + + { + "id": 101914, + "createdAt": "2019-02-25T15:07:33+08:00", + "updatedAt": "2019-02-25T15:07:33+08:00", + "lastOperator": "zhaominfu", + "deletedAt": "1970-01-01T00:00:00+08:00", + "name": "成华悦都农贸店", + "cityCode": 510100, + "districtCode": 510108, + "address": "成华区桃竹路55号一楼44、45号", + "tel1": "15088537275", + "tel2": "", + "openTime1": 730, + "closeTime1": 1800, + "openTime2": 0, + "closeTime2": 0, + "deliveryRangeType": 2, + "deliveryRange": "104.106488,30.680477;104.109639,30.682390;104.108228,30.685010;104.111904,30.687921;104.116220,30.684203;104.118888,30.686362;104.123694,30.682666;104.121355,30.680289;104.121605,30.679253;104.108628,30.675569;104.106502,30.679381;104.106488,30.680477", + "status": -1, + "changePriceType": 0, + "idCardFront": "", + "idCardBack": "", + "idCardHand": "", + "licence": "", + "licenceCode": "", + "lng": 104.116913, + "lat": 30.680007, + "cityName": "成都市", + "districtName": "成华区", + "StoreMaps": [ + { + "status": 1, + "vendorID": 0, + "vendorStoreID": "11850802" + } + ], + "CourierMaps": [ + { + "status": 1, + "vendorID": 101, + "vendorStoreID": "101914" + } + ] + } + +// 创建门店 +post /store/CreateStore +token +payload + +// 修改门店 +put /store/UpdateStore formData +token +storeID +payload + +// 获取平台门店 +get /store/GetStoreVendorMaps +storeID +vendorID 默认全部 + +// 获取转送门店 +get /store/GetStoreCourierMaps +storeID +vendorID 默认全部 + +// 查询平台门店 +get /store/GetVendorStore +vendorStoreID +vendorID + +// 绑定平台门店 +post /store/AddStoreVendorMap +storeID +vendorID +payload + +// 绑定专送门店 +post /store/AddStoreCourierMap +storeID +vendorID +payload status, vendorStoreId + +创建门店json +{ + "id": 0, + "createdAt": "0001-01-01T00:00:00Z", + "updatedAt": "0001-01-01T00:00:00Z", + "lastOperator": "", + "deletedAt": "0001-01-01T00:00:00Z", + "name": "成华悦都农贸店2", + "cityCode": 510100, + "districtCode": 510108, + "address": "成华区桃竹路55号一楼44、45号", + "tel1": "15088537275", + "tel2": "", + "openTime1": 730, + "closeTime1": 1800, + "openTime2": 0, + "closeTime2": 0, + "deliveryRangeType": 3, 半径 2规划范围 + "deliveryRange": "3000", + "status": 1, + "changePriceType": 0, + "idCardFront": "", + "idCardBack": "", + "idCardHand": "", + "licence": "", + "licenceCode": "", + "deliveryType": 0, + "lng": 104.116913, + "lat": 30.680007, + "cityName": "成都市", + "districtName": "成华区", + "StoreMaps": null, + "CourierMaps": null, + "getVendorStore": "11850802" +} + +绑定 payload + +{ + "vendorStoreID":"11850802", + "status":1, + "vendorID":0, + "autoPickup":1, + "deliveryCompetition":1, + "pricePercentage":100, + "isSync":1 +} + + +编辑门店映射 put /store/UpdateStoreVendorMap +storeID int query +vendorID +payload +获取的数据 + +autoPickup: 1 +deliveryCompetition: 1 +deliveryType: 0 +isSync: 1 +pricePercentage: 100 +status: 1 +storeID: 101912 +syncStatus: 0 +vendorID: 0 +vendorStoreID: "11733019" + +删除门店映射 delete /store/DeleteStoreVendorMap +storeID +vendorID + +----------------------------- +得到专送门店绑定信息 get /store/GetStoreCourierMaps +storeID +vendorID + +{ + storeID: 100118, + vendorID: 102, + vendorStoreID: 123123, + status: 1 +} + +修改专送门店 put /store/UpdateStoreCourierMap formdata +storeID +vendorID +payload + +新增专送门店 post /store/AddStoreCourierMap formdata +storeID +vendorID +payload + +删除专送门店 delete /store/DeleteStoreCourierMap query +storeID +vendorID diff --git a/src/apis/tool.txt b/src/apis/tool.txt new file mode 100644 index 0000000..6501380 --- /dev/null +++ b/src/apis/tool.txt @@ -0,0 +1,29 @@ +// 京西门店复制到京西门店 +post /store/sku/CopyStoreSkus formData +fromStoreID +toStoreID +copyMode fresh全部清除后复制 update只复制指定商品 +pricePercentage +skuIDs: [] + +// 同步到平台 +put /store/sku/SyncStoresSkus formData +storeIDs [] +vendorIDs: [] +isAsync boo +isContinueWhenError 单个失败继续 +skuIDs: [] + +// 初始化商品 +put /sync/FullSyncStoresSkus formData +storeIDs +vendorIDs +isAsync +isContinueWhenError + +// 删除远程门店 +delete /sync/DeleteRemoteStoreSkus +storeIDs +vendorIDs +isAsync +isContinueWhenError diff --git a/src/apis/user.txt b/src/apis/user.txt new file mode 100644 index 0000000..81bf2b8 --- /dev/null +++ b/src/apis/user.txt @@ -0,0 +1,50 @@ +get /user/TmpGetStoreUsers +storeID + +// 数据 +createdAt: "0001-01-01T00:00:00Z" +id: 671 +lastOperator: "" +members: null +nickname: "null" +openID: "oYN_usu4RYlLR4QcfXGpkhwrqgLI" +openIDMini: "" +openIDUnion: "" +parentID: -1 +parentMobile: "" +storeID: 100118 +tel: "18583789973" +updatedAt: "0001-01-01T00:00:00Z" + +members + +createdAt: "0001-01-01T00:00:00Z" +id: 18763 +lastOperator: "" +nickname: "周小扬" +openID: "" +openIDMini: "" +openIDUnion: "" +parentID: 671 +storeID: 0 +tel: "13684045763" +updatedAt: "0001-01-01T00:00:00Z" + +解绑 +PUT /user/TmpUnbindMobile +mobile + +绑定分组 +PUT /user/TmpBindMobile2Store +mobile +storeID + +绑定组员 +PUT /user/TmpAddMobile2Mobile +parentMobile +mobile + +修改号码 +PUT /user/TmpChangeMobile +curMobile 当前 +expectedMobile 新 diff --git a/src/apis/登录接口.txt b/src/apis/登录接口.txt new file mode 100644 index 0000000..5b429d7 --- /dev/null +++ b/src/apis/登录接口.txt @@ -0,0 +1,81 @@ +钉钉扫码 +https://oapi.dingtalk.com/connect/qrconnect?appid=dingoashf4onhetkegzh3i&response_type=code&scope=snsapi_login&state=&redirect_uri=http://www.jxc4.com/beta/v2/auth2/DingDingOAuth2 + +微信公众号认证回调接口 +/auth2/WeixinMPOAuth2 get + *code str 客户同意后得到的code + *block str 回调地址 + state str 微信回调的登录状态 + ----得到的数据 + type 从哪登录 + TokenType 1是有用户信息,2是没有用户信息 + +user2 +用户注册 +/user2/RegisterUser post(formData) + *payload json数据,User对象(手机号必填) + *mobileVerifyCode 手机验证码(通过auth2.SendVerifyCode获得) + authToken 之前通过login得到的认证TOKEN(可以为空) + + ----payload + UserID2 str 用户名(英文数字下划线) + Name str 昵称 + Mobile str 手机号 + Email string `orm:"size(32)" json:"email"` + IDCardNo string `orm:"size(18);column(id_card_no)" json:"idCardNo"` // 身份证号 + +发送验证码 +/auth2/SendVerifyCode post + *captchaID str 图片验证码ID + *captchaValue str 图片验证码值 + authToken str 之前的认证token + *authID str 手机号或邮件 + +登录接口 +/auth2/Login post(formData) + *authType str localpass:本地账号密码 + mobile:手机短信 + weixin:微信登录--扫码登录 + weixinsns:微信公众号登录 + weixinmini:小程序登录 + ddstaff: 钉钉 + *authSecret str 登录密码, localpass(md5) + authID str 对应type值 + authIDType str localpass时才有意义,分别为 userid2:用户名,email,mobild + +auth2 +绑定认证方式 +/auth2/AddAuthBind post(formData) + *token 认证token + authToken 之前通过login得到的新认证TOKEN(临时token) + +修改密码 +/auth2/ChangePassword put + *token + oldPwd 原密码md5,如果是重置或新设,为空 + newPwd 新密码md5 + +生成校验图 +/auth2/CreateCaptcha post(formData) + *width int 图片宽 + *height int 图片高 + captchaLen 验证码长度(默认4) + +登出接口 +/auth2/Logout delete + *token + +解绑认证方式 +/auth2/RemoveAuthBind post + *token + authType str weixin:微信登录,weixinsns:微信公众号登录,weixinmini;小程序登录 + +微信认证回调接口-扫码登录 +/auth2/WeixinOAuth2 get + *code str 客户同意后得到的code + *block str 回调地址 + state str 微信回调的登录状态 + +得到用户已经成功绑定的认证信息 +/user2/GetBindAuthInfo get + token 认证token diff --git a/src/assets/imgs/dd-bind.png b/src/assets/imgs/dd-bind.png new file mode 100644 index 0000000..d03a960 Binary files /dev/null and b/src/assets/imgs/dd-bind.png differ diff --git a/src/assets/imgs/ic_eb.png b/src/assets/imgs/ic_eb.png new file mode 100644 index 0000000..1066ccc Binary files /dev/null and b/src/assets/imgs/ic_eb.png differ diff --git a/src/assets/imgs/ic_jd.png b/src/assets/imgs/ic_jd.png new file mode 100644 index 0000000..3a43348 Binary files /dev/null and b/src/assets/imgs/ic_jd.png differ diff --git a/src/assets/imgs/ic_jx.png b/src/assets/imgs/ic_jx.png new file mode 100644 index 0000000..7a01e50 Binary files /dev/null and b/src/assets/imgs/ic_jx.png differ diff --git a/src/assets/imgs/ic_mt.png b/src/assets/imgs/ic_mt.png new file mode 100644 index 0000000..80d35da Binary files /dev/null and b/src/assets/imgs/ic_mt.png differ diff --git a/src/assets/imgs/icon-clear.png b/src/assets/imgs/icon-clear.png new file mode 100644 index 0000000..01c2b77 Binary files /dev/null and b/src/assets/imgs/icon-clear.png differ diff --git a/src/assets/imgs/icon-code.png b/src/assets/imgs/icon-code.png new file mode 100644 index 0000000..d7fb4a3 Binary files /dev/null and b/src/assets/imgs/icon-code.png differ diff --git a/src/assets/imgs/icon-nickname.png b/src/assets/imgs/icon-nickname.png new file mode 100644 index 0000000..5308bb6 Binary files /dev/null and b/src/assets/imgs/icon-nickname.png differ diff --git a/src/assets/imgs/icon-nickname2.png b/src/assets/imgs/icon-nickname2.png new file mode 100644 index 0000000..dfa0061 Binary files /dev/null and b/src/assets/imgs/icon-nickname2.png differ diff --git a/src/assets/imgs/icon-tel.png b/src/assets/imgs/icon-tel.png new file mode 100644 index 0000000..45d7464 Binary files /dev/null and b/src/assets/imgs/icon-tel.png differ diff --git a/src/assets/imgs/icon-username.png b/src/assets/imgs/icon-username.png new file mode 100644 index 0000000..7ccf13e Binary files /dev/null and b/src/assets/imgs/icon-username.png differ diff --git a/src/assets/imgs/loading.gif b/src/assets/imgs/loading.gif new file mode 100644 index 0000000..581b04c Binary files /dev/null and b/src/assets/imgs/loading.gif differ diff --git a/src/assets/style/_colors.scss b/src/assets/style/_colors.scss new file mode 100644 index 0000000..53fa4c3 --- /dev/null +++ b/src/assets/style/_colors.scss @@ -0,0 +1,32 @@ +/* + element 配色风格 +*/ + +// 主色 +$lightBlue: #58B7FF; +$blue: #20A0FF; +$darkBlue: #1D8CE0; + +// 辅助色 +$primary: #20A0FF; +$success: #13CE66; +$warning: #F7BA2A; +$danger: #FF4949; + +// 中性色 +$black: #1F2D3D; +$lightBlack: #324057; +$extraLightBlack: #475669; + +$sliver: #8492A6; +$lightSliver: #99A9BF; +$extraLightSliver: #C0CCDA; + +$gray: #D3DCE6; +$lightGray: #E5E9F2; +$extraLightGray: #EFF2F7; + +$darkWhite: #F9FAFC; +$white: #FFFFFF; + +$maskColor: rgba(black, .6); diff --git a/src/assets/style/_icon.scss b/src/assets/style/_icon.scss new file mode 100644 index 0000000..f099d83 --- /dev/null +++ b/src/assets/style/_icon.scss @@ -0,0 +1,11 @@ +%icon-store { + background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cpath d='M950.635 366.916c0-3.869-1.937-5.802-1.937-5.802l-75.446-187.648c-13.542-38.69-50.296-61.905-94.79-61.905h-526.18c-44.494 0-79.312 23.213-92.853 59.968L78.181 363.046c0 1.932 0 1.932-1.938 3.869-5.803 17.41-9.672 36.754-9.672 54.165 0 65.77 36.755 125.742 92.858 154.757 23.213 13.542 52.228 19.35 83.181 19.35 52.234 0 100.597-23.219 135.413-63.844 32.89 38.692 81.25 61.905 133.481 61.905 52.234 0 100.592-23.212 133.482-61.905 32.885 38.692 81.25 61.905 135.415 61.905 32.884 0 59.967-7.738 85.117-19.343 56.098-30.951 92.853-90.919 92.853-154.762 1.935-15.472-1.933-34.815-7.736-52.227zm-114.14 152.826c-15.472 7.74-32.883 11.604-54.165 11.604-38.686 0-75.446-19.343-94.79-54.165-1.931-3.87-3.868-7.734-7.738-13.541-5.803-5.802-15.474-13.543-30.948-13.543-13.54 0-25.149 5.807-30.951 13.543-3.87 5.807-5.807 9.671-7.74 13.541-21.28 32.89-56.103 54.165-94.789 54.165-38.691 0-73.513-19.343-94.79-54.165-1.935-3.87-3.87-7.734-7.74-11.603-15.473-17.412-48.363-17.412-61.903-1.937-5.802 5.807-7.735 9.671-9.673 13.542-21.28 32.889-56.098 52.232-94.79 54.164-21.28 0-38.69-3.864-52.233-11.604-36.754-19.344-59.967-58.035-59.967-98.66 0-11.607 1.931-25.15 5.802-36.754v-1.937l79.316-187.64c1.931-3.871 5.803-19.349 30.952-19.349h528.114c9.676 0 27.082 1.936 34.821 21.28l75.447 185.71v3.87c3.869 11.609 5.8 25.15 5.8 36.754.002 40.623-21.278 77.381-58.034 96.725zm25.152 104.461c-17.41 0-32.883 13.542-32.883 32.884v174.105a17.34 17.34 0 0 1-17.412 17.412H207.788a17.336 17.336 0 0 1-17.408-17.412V659.024c0-17.41-13.54-32.89-32.889-32.89-17.41 0-32.884 13.542-32.884 32.89v172.168c0 44.495 36.754 81.249 81.248 81.249h603.56c44.495 0 81.248-36.754 81.248-83.181V655.155c1.936-17.41-11.604-30.952-29.016-30.952zM764.921 397.87H252.28c-17.411 0-32.885-13.542-32.885-32.89 0-17.407 13.54-32.885 32.884-32.885h512.64c17.41 0 32.889 13.541 32.889 32.884.001 19.349-15.478 32.89-32.888 32.89z' fill='%2320A0FF'/%3E%3C/svg%3E"); +} + +%icon-search { + background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cpath d='M771.235 696.266l237.244 237.245a52.979 52.979 0 0 1-74.913 74.913l-237.299-237.19a431.158 431.158 0 1 1 74.914-74.914zm-340.076 58.26a323.368 323.368 0 1 0 0-646.737 323.368 323.368 0 0 0 0 646.737z' fill='%23D3DCE6'/%3E%3C/svg%3E") +} + +%icon-clear { + background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cpath d='M511.3 42.6c-259 0-469 210-469 469s210 469 469 469 469-210 469-469-209.9-469-469-469zm219.2 618.5c19.4 19.4 19.4 51.3 0 70.7-19.4 19.4-51.3 19.4-70.7 0l-149-149-148 148c-19.4 19.4-51.3 19.4-70.7 0-19.4-19.4-19.4-51.3 0-70.7l148-148-148-148c-19.4-19.4-19.4-51.3 0-70.7 19.4-19.4 51.3-19.4 70.7 0l148 148 149-149c19.4-19.4 51.3-19.4 70.7 0 19.4 19.4 19.4 51.3 0 70.7l-149 149 149 149z' fill='%23D3DCE6'/%3E%3C/svg%3E") +} \ No newline at end of file diff --git a/src/assets/style/_store-manager.scss b/src/assets/style/_store-manager.scss new file mode 100644 index 0000000..210ea64 --- /dev/null +++ b/src/assets/style/_store-manager.scss @@ -0,0 +1,110 @@ +// 门店管理 +.storeManager { + // 检索框 + .search { + background: $blue; + height: 1rem; + display: flex; + box-sizing: border-box; + align-items: center; + padding: 0 .2rem; + justify-content: space-between; + // position: fixed; + // top: 0; + // left: 0; + // width: 100%; + // z-index: 100; + .input-group { + flex: 1; + // height: 100%; + font-size: 0; + display: flex; + position: relative; + input { + margin: auto; + font-size: .28rem; + width: 100%; + height: .6rem; + border-radius: .6rem; + border: 1px solid $gray; + box-sizing: border-box; + padding-left: .6rem; + padding-right: .8rem; + color: $black; + outline: none; + font-family: "Microsoft Yahei"; + } + .icon-search { + position: absolute; + left: .14rem; + top: 50%; + transform: translateY(-50%); + // width: .3rem; + // height: .3rem; + svg { + fill: $gray; + width: .32rem; + height: .32rem; + } + } + .icon-clear { + position: absolute; + right:0; + // background: rgba(black, .4); + // height: 100%; + top: 50%; + transform: translateY(-50%); + padding: .2rem .2rem; + svg { + fill: $gray; + width: .32rem; + height: .32rem; + } + } + } + .btn-create, .btn-more { + background: $white; + font-size: .24rem; + border-radius: 50%; + width: .6rem; + height: .6rem; + line-height: .6rem; + text-align: center; + color: $blue; + flex-shrink: 0; + display: flex; + .icon { + margin: auto; + svg { + fill: $blue; + width: .3rem; + height: .3rem; + } + } + } + .btn-create {} + .btn-more { + margin: 0 .2rem; + } + } + .content { + // padding-top: 1rem; + } + .store-item + .store-item { + border-top: 2px solid $gray; + } + .no-thing { + text-align: center; + margin-top: 2rem; + .no-store { + svg { + width: 4rem; + height: 4rem; + } + } + .text { + color: #ccc; + font-size: .32rem; + } + } +} diff --git a/src/assets/style/ddauth.scss b/src/assets/style/ddauth.scss new file mode 100644 index 0000000..0f2f510 --- /dev/null +++ b/src/assets/style/ddauth.scss @@ -0,0 +1,98 @@ +$maincolor: #409EFF; +@keyframes bannerIn { + from { + transform: translate(-50%, -100%); + opacity: .5; + } + to { + transform: translate(-50%, 0); + opacity: 1; + } +} + +@keyframes btnIn { + from { + transform: translate(-50%, 100%); + opacity: .5; + } + to { + transform: translate(-50%, 0); + opacity: 1; + } +} + +// 授权登录 +.auth { + background: #fafafa; + width: 100vw; + height: 100vh; + .banner { + position: fixed; + width: 3.32rem; + height: 1.12rem; + left: 50%; + top: 30%; + transform: translate(-50%, 0); + transform-origin: center center; + background: url(../imgs/dd-bind.png) center center no-repeat; + background-size: 100%; + animation: bannerIn 1s ease-in-out; + } + .btn-getcode { + font-size: .36rem; + position: fixed; + left: 50%; + top: 56%; + transform: translate(-50%, 0); + text-align: center; + background: #409EFF; + color: white; + padding: .2rem .6rem; + border-radius: .2rem; + box-shadow: 0 .05rem .1rem rgba(#ccc, .8), 0 .03rem .1rem rgba(#ccc, .8),0 0 .1rem rgba(#ccc, .8); + border: 2px solid white; + width: 3rem; + animation: btnIn 1s ease-in-out; + } + .bottom-text { + font-size: .2rem; + position: fixed; + bottom: .4rem; + color: #999; + left: 50%; + transform: translateX(-50%); + } +} + +// 创建新用户 +.register { + background: #fafafa; + width: 100vw; + height: 100vh; + // padding: .2rem; + box-sizing: border-box; + .form-wrapper { + padding: 0 .2rem; + background: white; + box-shadow: 0 1px .05rem 0 rgba(0,0,0,.05); + } + h2 { + color: $maincolor; + font-size: .4rem; + text-align: center; + margin: 0; + padding: .5rem 0 .2rem 0; + } + .btn-register { + width: 6.8rem; + margin: 1rem auto 0; + height: .94rem; + color: white; + border-radius: .1rem; + border: 2px solid rgba($maincolor,.8); + background: $maincolor; + font-size: .36rem; + text-align: center; + line-height: .94rem; + } +} diff --git a/src/assets/style/global.scss b/src/assets/style/global.scss new file mode 100644 index 0000000..7bd2677 --- /dev/null +++ b/src/assets/style/global.scss @@ -0,0 +1,53 @@ +@import './colors.scss'; +@import './store-manager.scss'; + +* { + line-height: 1; + font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Heiti SC", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif; +} + +.height100vh-100px { + height: calc(100vh - 1rem); + overflow-y: auto; + // -webkit-overflow-scrolling: touch; +} +.height100vh-180px { + height: calc(100vh - 1.8rem); + overflow-y: auto; + // -webkit-overflow-scrolling: touch; + background: #fafafa; +} +.height100vh-200px { + height: calc(100vh - 2rem); + overflow: hidden; + // -webkit-overflow-scrolling: touch; + background: #fafafa; +} +.padding-bottom-1rem { + padding-bottom: 1rem; +} +.pointerNone { + pointer-events: none; + touch-action: none; +} +.pointerAll { + pointer-events: all; + touch-action: auto; +} + +.code105 { + height: 60vh; + overflow: hidden; + ul { + margin: 0; + padding: 0; + text-align: left; + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 1px solid $gray; + color: $primary; + span { + color: $sliver; + } + } +} diff --git a/src/assets/style/goods-manager.scss b/src/assets/style/goods-manager.scss new file mode 100644 index 0000000..4458d89 --- /dev/null +++ b/src/assets/style/goods-manager.scss @@ -0,0 +1,33 @@ +@import './colors.scss'; +// 门店管理 +.goods-manager { + .header-title { + height: 1rem; + background: $blue; + font-size: .32rem; + text-align: center; + color: white; + line-height: 1rem; + } + .link-cell { + display: flex; + font-size: .32rem; + height: 1rem; + background: white; + align-items: center; + justify-content: space-between; + padding: 0 .2rem; + text-decoration: none; + color: $black; + .icon { + svg { + width: .4rem; + height: .4rem; + fill: $lightSliver; + } + } + } + .link-cell + .link-cell { + border-top: 1px solid $gray; + } +} diff --git a/src/assets/style/order-manager.scss b/src/assets/style/order-manager.scss new file mode 100644 index 0000000..427d12f --- /dev/null +++ b/src/assets/style/order-manager.scss @@ -0,0 +1,138 @@ +@import './colors.scss'; +@import './icon.scss'; +// 订单管理样式 +.order-manager { + font-size: .32rem; + .search { + height: 1rem; + box-sizing: border-box; + padding: .2rem; + background: $blue; + display: flex; + align-items: center; + .icon-store, .icon-search, .icon-clear { + flex: none; + width: .6rem; + height: .6rem; + @extend %icon-store; + background-size: .36rem; + background-repeat: no-repeat; + background-position: center center; + background-color: white; + border-radius: 50%; + } + .icon-search { + @extend %icon-search; + background-size: .32rem; + } + .icon-clear { + @extend %icon-clear; + } + input { + flex: 1; + font-size: .28rem; + color: $black; + outline: none; + font-family: "Microsoft Yahei"; + border: none; + margin-right: .1rem; + } + .input-group { + margin-left: .2rem; + flex: 1; + background: white; + display: flex; + align-items: center; + height: .6rem; + border-radius: .3rem; + border: 1px solid $gray; + } + } + .wrapper { + height: calc(100vh - 2rem); + overflow: hidden; + } + .content { + font-size: .3rem; + } + .item { + text-align: center; + padding: .5rem 0; + color: white; + background: #ccc; + } + .order-list { + padding: .2rem; + color: $black; + border-bottom: 1px solid $gray; + display: flex; + align-items: center; + height: .8rem; + & > * { + flex: none; + } + .order-created { + font-size: .24rem; + margin-left: .1rem; + } + .vendorID { + border-radius: .1rem; + border: 1px solid $gray; + width: .4rem; + height: .4rem; + background-size: 100%; + background-repeat: no-repeat; + background-position: center; + } + .vendor-icon-0 { + background-image: url(../imgs/ic_jd.png); + } + .vendor-icon-1 { + background-image: url(../imgs/ic_mt.png); + } + .vendor-icon-3 { + background-image: url(../imgs/ic_eb.png); + } + .vendor-icon-9 { + background-image: url(../imgs/ic_jx.png); + } + .order-id { + flex: 1; + margin-left: .2rem; + color: $blue; + font-size: .32rem; + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; + } + .order-seq { + width: .5rem; + color: $sliver; + } + .order-status { + width: 1.2rem; + text-align: center; + font-size: .24rem; + background: $lightBlue; + color: white; + padding: .1rem 0; + border-radius: .6rem; + margin-left: .1rem; + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; + } + } + // .refresh { + // position: absolute; + // width: 100%; + // top: 0; + // left: 0; + // z-index: -1; + // text-align: center; + // height: 1rem; + // line-height: 1rem; + // font-size: .32rem; + // color: #ccc; + // } +} diff --git a/src/assets/svg/icon-activity.svg b/src/assets/svg/icon-activity.svg new file mode 100644 index 0000000..e2b0e0d --- /dev/null +++ b/src/assets/svg/icon-activity.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/icon-address.svg b/src/assets/svg/icon-address.svg new file mode 100644 index 0000000..b718473 --- /dev/null +++ b/src/assets/svg/icon-address.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/icon-addresslist.svg b/src/assets/svg/icon-addresslist.svg new file mode 100644 index 0000000..ccfb3ea --- /dev/null +++ b/src/assets/svg/icon-addresslist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/icon-clear.svg b/src/assets/svg/icon-clear.svg new file mode 100644 index 0000000..104cf6f --- /dev/null +++ b/src/assets/svg/icon-clear.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/icon-confirm.svg b/src/assets/svg/icon-confirm.svg new file mode 100644 index 0000000..6c333ee --- /dev/null +++ b/src/assets/svg/icon-confirm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/icon-create.svg b/src/assets/svg/icon-create.svg new file mode 100644 index 0000000..ec4b12c --- /dev/null +++ b/src/assets/svg/icon-create.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/icon-curposition.svg b/src/assets/svg/icon-curposition.svg new file mode 100644 index 0000000..3c8bc1b --- /dev/null +++ b/src/assets/svg/icon-curposition.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/icon-delete.svg b/src/assets/svg/icon-delete.svg new file mode 100644 index 0000000..70cd325 --- /dev/null +++ b/src/assets/svg/icon-delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/icon-down.svg b/src/assets/svg/icon-down.svg new file mode 100644 index 0000000..c381452 --- /dev/null +++ b/src/assets/svg/icon-down.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/icon-go.svg b/src/assets/svg/icon-go.svg new file mode 100644 index 0000000..b5fdd78 --- /dev/null +++ b/src/assets/svg/icon-go.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/icon-goods.svg b/src/assets/svg/icon-goods.svg new file mode 100644 index 0000000..0eeeaf9 --- /dev/null +++ b/src/assets/svg/icon-goods.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/icon-insert.svg b/src/assets/svg/icon-insert.svg new file mode 100644 index 0000000..00f9831 --- /dev/null +++ b/src/assets/svg/icon-insert.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/icon-mini.svg b/src/assets/svg/icon-mini.svg new file mode 100644 index 0000000..661d1ea --- /dev/null +++ b/src/assets/svg/icon-mini.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/icon-modify.svg b/src/assets/svg/icon-modify.svg new file mode 100644 index 0000000..2b27bf1 --- /dev/null +++ b/src/assets/svg/icon-modify.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/icon-more.svg b/src/assets/svg/icon-more.svg new file mode 100644 index 0000000..02b6a0b --- /dev/null +++ b/src/assets/svg/icon-more.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/icon-order.svg b/src/assets/svg/icon-order.svg new file mode 100644 index 0000000..56cc87f --- /dev/null +++ b/src/assets/svg/icon-order.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/icon-public.svg b/src/assets/svg/icon-public.svg new file mode 100644 index 0000000..1e27701 --- /dev/null +++ b/src/assets/svg/icon-public.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/icon-return.svg b/src/assets/svg/icon-return.svg new file mode 100644 index 0000000..c72b9b0 --- /dev/null +++ b/src/assets/svg/icon-return.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/icon-search.svg b/src/assets/svg/icon-search.svg new file mode 100644 index 0000000..8098953 --- /dev/null +++ b/src/assets/svg/icon-search.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/icon-store.svg b/src/assets/svg/icon-store.svg new file mode 100644 index 0000000..545967b --- /dev/null +++ b/src/assets/svg/icon-store.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/icon-update.svg b/src/assets/svg/icon-update.svg new file mode 100644 index 0000000..32e6353 --- /dev/null +++ b/src/assets/svg/icon-update.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/icon-user.svg b/src/assets/svg/icon-user.svg new file mode 100644 index 0000000..6b0ca5a --- /dev/null +++ b/src/assets/svg/icon-user.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/loading.svg b/src/assets/svg/loading.svg new file mode 100644 index 0000000..7c05992 --- /dev/null +++ b/src/assets/svg/loading.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/no-group.svg b/src/assets/svg/no-group.svg new file mode 100644 index 0000000..2434207 --- /dev/null +++ b/src/assets/svg/no-group.svg @@ -0,0 +1 @@ + diff --git a/src/assets/svg/no-store.svg b/src/assets/svg/no-store.svg new file mode 100644 index 0000000..b4cc319 --- /dev/null +++ b/src/assets/svg/no-store.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/BottomBar/NavLink.js b/src/components/BottomBar/NavLink.js new file mode 100644 index 0000000..32c3ac2 --- /dev/null +++ b/src/components/BottomBar/NavLink.js @@ -0,0 +1,16 @@ +import React from 'react'; +import ReactSVG from 'react-svg' +import {NavLink} from 'react-router-dom'; + +/* + to + src + text +*/ + +export default (props) => ( + + + {props.text} + +); diff --git a/src/components/BottomBar/bottom-bar.scss b/src/components/BottomBar/bottom-bar.scss new file mode 100644 index 0000000..897aa47 --- /dev/null +++ b/src/components/BottomBar/bottom-bar.scss @@ -0,0 +1,41 @@ +@import '../../assets/style/colors.scss'; +// 底部菜单 +.bottom-bar { + display: flex; + // justify-content: space-between; + font-size: .2rem; + height: 1rem; + position: fixed; + z-index: 1000; + bottom: 0; + left: 0; + width: 100%; + background: white; + box-sizing: border-box; + border-top: 1px solid $gray; + .nav { + flex: 1; + color: $sliver; + text-decoration: none; + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + padding: 0 .2rem; + .svg { + svg { + fill: $sliver; + width: .5rem; + height: .5rem; + } + } + } + .nav-active { + color: $blue; + .svg { + svg { + fill: $blue; + } + } + } +} diff --git a/src/components/BottomBar/index.js b/src/components/BottomBar/index.js new file mode 100644 index 0000000..b2f27bc --- /dev/null +++ b/src/components/BottomBar/index.js @@ -0,0 +1,21 @@ +import React, { Component } from 'react'; +import './bottom-bar.scss'; +import NavLink from './NavLink'; +import {withRouter} from 'react-router-dom'; +import {IndexList} from '@/router'; + +class BottomBar extends Component { + render() { + return ( +
+ { + IndexList.map((link, index) => ( + + )) + } +
+ ); + } +} + +export default withRouter(BottomBar); diff --git a/src/components/Button/button.scss b/src/components/Button/button.scss new file mode 100644 index 0000000..e7f540a --- /dev/null +++ b/src/components/Button/button.scss @@ -0,0 +1,68 @@ +@import '../../assets/style/colors.scss'; + +$fontSize: .28rem; +.btn { + font-size: $fontSize; + outline: none; + padding: .2rem; + border: 1px solid $gray; + border-radius: .1rem; + color: white; + cursor: pointer; + // box-shadow: 0 0 .05rem rgba($lightGray, .8); + // text-shadow: 1px 1px 1px rgba($black, 1); + box-sizing: border-box; + display: inline-flex; + align-items: center; +} + +.btn-default { + background: white; + color: $black; +} +.btn-primary { + background: $primary; +} +.btn-success { + background: $success; +} +.btn-warning { + background: $warning; +} +.btn-danger { + background: $danger; +} +.btn-disable { + // opacity: .6; + color: $gray; + background: white; + cursor:not-allowed; +} + +.btn-block { + display: flex; + align-items: center; + justify-content: center; + height: .9rem; + width: 100%; +} +.btn-loading { + display: inline-block; + width: $fontSize; + height: $fontSize; + margin-right: .1rem; + box-sizing: border-box; + border: .04rem solid $lightGray; + border-radius: 50%; + border-right-color: transparent; + animation: btnRotate .5s infinite ease-in-out; +} + +@keyframes btnRotate { + from { + transform: rotateZ(0); + } + to { + transform: rotateZ(360deg); + } +} diff --git a/src/components/Button/index.js b/src/components/Button/index.js new file mode 100644 index 0000000..5a6d46f --- /dev/null +++ b/src/components/Button/index.js @@ -0,0 +1,55 @@ +import React, {Component} from 'react'; +import classNames from 'classnames'; +import './button.scss'; + +/* + props: + type: null btn-default + primary + success + warning + danger + disabled boo + block boo + loading boo 是否是loading状态 + extraClass str 额外的样式 + + nativeType 原生type +*/ + +class Button extends Component { + constructor (...args) { + super(...args); + } + onClick (e) { + if (!this.props.loading) { + this.props.onClick && this.props.onClick(e); + } + } + render () { + return ( + + ) + } +} + +export default Button; diff --git a/src/components/Dialog/Dialog.jsx b/src/components/Dialog/Dialog.jsx new file mode 100644 index 0000000..491ce67 --- /dev/null +++ b/src/components/Dialog/Dialog.jsx @@ -0,0 +1,139 @@ +import React, {Component} from 'react'; +import ReactDOM from 'react-dom'; +import './dialog.scss'; +import BScroll from 'better-scroll'; + +import Animated from 'animated/lib/targets/react-dom' + +/* +Dialog2.show('错误', desc, { + // showCancel: , + // cancelText: '', + confirmText: '是的' +}, res => { + console.log(res); +}); +*/ + +class Dialog extends Component { + constructor (...args) { + super(...args); + this.state = { + show: false, + title: '', + message: '', + options: { + showCancel: true, + cancelText: '', + confirmText: '' + }, + ani: new Animated.Value(0) + }; + this.func = null; + this.scroll = null; + } + async componentDidMount () { + this.setState({ + title: this.props.title, + message: this.props.message, + }) + if (this.props.options) this.setState({ + 'options': { + ...this.state.options, + ...this.props.options + } + }) + if (this.props.func) this.func = this.props.func; + // 入场动画 + Animated.timing(this.state.ani, { + toValue: 1, + duration: 400 + }).start(() => { + // console.log('入长了') + const code105Div = document.querySelector('.code105') + console.log(code105Div) + if (code105Div) { + this.scroll = new BScroll(code105Div, { + click: true, + stopPropagation: true, + bounce: { + top: false, + bottom: false + } + }); + } + }); + } + clickMask () { + this.fnClick(null); + } + fnClose () { + this.fnClick(false); + } + fnConfirm () { + this.fnClick(true); + } + fnClick (boo) { + Animated.timing(this.state.ani, { + toValue: 0, + duration: 400 + }).start(async () => { + // await this.setState({show: false}); + // 移除dom + try { + ReactDOM.unmountComponentAtNode(oDiv); + document.body.removeChild(oDiv); + this.func && this.func(boo); + } catch (e) {} + }); + } + render () { + return ( +
+ +
e.stopPropagation()}> +
+ {/* 标题 */} + { + this.state.title && ( +
{this.state.title || '标题'}
+ ) + } +
+
+ {/* 内容 */} +
+
+
+ {/* 底部按钮 */} + { + this.state.options.showCancel && ( +
{this.state.options.cancelText || '取消'}
+ ) + } +
{this.state.options.confirmText || '确定'}
+
+
+
+
+ ) + } +} + +let oDiv = null; + +Dialog.show = (title, message, options, fn) => { + let props = { + show: true, + title, + message, + options, + func: fn + } + + oDiv = document.createElement('div'); + document.body.appendChild(oDiv); + ReactDOM.render(React.createElement(Dialog, props), oDiv); +} + +export default Dialog; diff --git a/src/components/Dialog/dialog.scss b/src/components/Dialog/dialog.scss new file mode 100644 index 0000000..0d7df16 --- /dev/null +++ b/src/components/Dialog/dialog.scss @@ -0,0 +1,76 @@ +@import '../../assets/style/colors.scss'; +// dialog 对话框 +.dialog { + font-size: .32rem; + .mask { + position: fixed; + z-index: 9000; + left: 0; + right: 0; + bottom: 0; + top: 0; + background: rgba(black, .6); + } + .dia-wrapper { + position: fixed; + z-index: 9998; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + background: white; + border-radius: .1rem; + width: 4.5rem; + } + .dia-head { + text-align: center; + font-weight: 500; + padding: .2rem; + color: $extraLightBlack; + } + .dia-body { + padding: .2rem; + text-align: center; + font-size: .3rem; + color: $black; + word-break: break-all; + .message { + line-height: 1.5; + } + } + .dia-footer { + display: flex; + margin-top: .2rem; + border-top: 1px solid $gray; + align-items: center; + } + $btn-height: .8rem; + .btn { + flex: 1; + height: $btn-height; + line-height: $btn-height; + text-align: center; + } + .btn:nth-of-type(2) { + border-left: 1px solid $gray; + } + .btn-cancel { + color: $sliver; + } + .btn-confirm { + color: $blue; + font-weight: 500; + } + .success { + color: $success; + } + .address { + font-size: .26rem; + color: $sliver; + } + .error { + color: $danger; + } + .warning { + color: $warning; + } +} diff --git a/src/components/Dialog/index.js b/src/components/Dialog/index.js new file mode 100644 index 0000000..5ada553 --- /dev/null +++ b/src/components/Dialog/index.js @@ -0,0 +1,26 @@ +import Dialog from './Dialog.jsx'; + +/* +Dialog2.show('错误', desc, { + showCancel: true, + cancelText: '', + confirmText: '是的' +}, res => { + console.log(res); // true false +}); +*/ + +export default { + show (title, message, options, fn) { + Dialog.show(title, message, options, fn); + }, + showErr (e) { + let msg = e + if (Object.prototype.toString(e).indexOf('Error') > -1) { + msg = e.message + } + Dialog.show('错误', msg, { + showCancel: false + }) + } +}; diff --git a/src/components/Dialog2/dialog.scss b/src/components/Dialog2/dialog.scss new file mode 100644 index 0000000..0cefd8d --- /dev/null +++ b/src/components/Dialog2/dialog.scss @@ -0,0 +1,59 @@ +@import '../../assets/style/colors.scss'; +// dialog 对话框 +.dialog { + font-size: .32rem; + .mask { + position: fixed; + z-index: 9000; + left: 0; + right: 0; + bottom: 0; + top: 0; + background: rgba(black, .6); + } + .dia-wrapper { + position: fixed; + z-index: 9998; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + background: white; + border-radius: .1rem; + width: 4.5rem; + } + .dia-head { + text-align: center; + font-weight: 500; + padding: .2rem; + color: $extraLightBlack; + } + .dia-body { + padding: .2rem; + text-align: center; + font-size: .3rem; + color: $black; + } + .dia-footer { + display: flex; + margin-top: .2rem; + border-top: 1px solid $gray; + align-items: center; + } + $btn-height: .8rem; + .btn { + flex: 1; + height: $btn-height; + line-height: $btn-height; + text-align: center; + } + .btn:nth-of-type(2) { + border-left: 1px solid $gray; + } + .btn-cancel { + color: $sliver; + } + .btn-confirm { + color: $blue; + font-weight: 500; + } +} diff --git a/src/components/Dialog2/index.js b/src/components/Dialog2/index.js new file mode 100644 index 0000000..b477262 --- /dev/null +++ b/src/components/Dialog2/index.js @@ -0,0 +1,107 @@ +import React, {Component} from 'react'; +import './dialog.scss'; + +import Animated from 'animated/lib/targets/react-dom' + +/* +this.refs.dialog.show('', '成功注册!', { + // showCancel: false, + confirmText: '知道了' +}, confirm => { + console.log(confirm); +}); +*/ + +class Dialog extends Component { + constructor (...args) { + super(...args); + this.state = { + show: false, + title: '', + message: '', + options: { + showCancel: true, + cancelText: '', + confirmText: '' + }, + ani: new Animated.Value(0) + } + } + async show (title, message, options, func) { + this.setState({ + show: true, + title, + message + }); + if (options) this.setState({ + 'options': { + ...this.state.options, + ...options + } + }); + if (func) this.func = func; + // 入场动画 + Animated.timing(this.state.ani, { + toValue: 1, + duration: 400 + }).start(); + } + clickMask () { + this.fnClose(); + } + fnClose () { + this.func && this.func(false); + Animated.timing(this.state.ani, { + toValue: 0, + duration: 400 + }).start(() => { + this.setState({show: false}); + }); + } + fnConfirm () { + this.func && this.func(true); + Animated.timing(this.state.ani, { + toValue: 0, + duration: 400 + }).start(() => { + this.setState({show: false}); + }); + } + render () { + return ( +
+ { + this.state.show && ( + +
e.stopPropagation()}> +
+ {/* 标题 */} + { + this.state.title && ( +
{this.state.title || '标题'}
+ ) + } +
+
+ {/* 内容 */} +
{this.state.message || '内容'}
+
+
+ {/* 底部按钮 */} + { + this.state.options.showCancel && ( +
{this.state.options.cancelText || '取消'}
+ ) + } +
{this.state.options.confirmText || '确定'}
+
+
+
+ ) + } +
+ ) + } +} + +export default Dialog; diff --git a/src/components/Form.js b/src/components/Form.js new file mode 100644 index 0000000..4fc0ac8 --- /dev/null +++ b/src/components/Form.js @@ -0,0 +1,127 @@ +import React, { Component } from 'react'; +import './form.scss'; +import Toast from '@/components/Toast'; +import fetchJson from '@/utils/fetch'; + +/* + getFormData +*/ + +class Form extends Component { + constructor (...args) { + super(...args); + this.state = { + list: {}, + focus: '', + num: 0 + } + } + async componentDidMount () { + for (let i = 0; i < this.props.fields.length; i++) { + await this.setState({ + list: { + ...this.state.list, + [this.props.fields[i].name]: this.props.fields[i].value + } + }); + } + } + // input focus + fnFocus (name) { + console.log(name); + this.setState({focus: name}); + } + fnBlur () { + console.log('失去焦点'); + // this.setState({focus: ''}); + } + // input 修改 + async fnChange (name, e) { + // console.log(name, e.target.value); + // this[name] = e.target.value; + await this.setState({ + list: { + ...this.state.list, + [name]: e.target.value + } + }) + // console.log(this.state); + } + // 清除 + fnClear (name) { + // console.log('清除' + name); + this.refs[name].value = ''; + this.setState({ + list: { + ...this.state.list, + [name]: '' + } + }) + } + // 获得formData + getFormData () { + let form = new FormData(this.refs.form); + return form; + } + // 发送验证码 + async fnSendCode (name) { + if (!/^\d{11}$/.test(this.state.list[name]) || this.state.num > 0) return false; + let tel = this.state.list[name]; + console.log(tel); + let form = new FormData(); + form.append('authID', tel); + form.append('authToken', this.props.token); + try { + await fetchJson('/v2/auth2/SendVerifyCode', { + method: 'POST', + body: form + }); + this.refs.toast.show('短信发送成功'); + // 开始倒计时 + await this.setState({num: 59}); + clearInterval(this.timer); + this.timer = setInterval(() => { + if (this.state.num === 0) { + clearInterval(this.timer); + } else { + this.setState({num: --this.state.num}); + } + }, 1000); + } catch (e) { + this.refs.toast.show(e); + } + } + render() { + if (!this.props.fields) console.error('Form need fields'); + return ( +
+ { + this.props.fields.length > 0 && ( + this.props.fields.map((field, index) => ( +
+ + + {/* // 清空按钮 */} + { + (this.state.focus === field.name && this.state.list[field.name]) && ( +
+ ) + } + {/* // 其他按钮 */} + { + field.btn && ( +
{field.btn.name}{this.state.num > 0 && '(' + this.state.num + ')'}
+ ) + } +
+ )) + ) + } + +
+ ); + } + +} + +export default Form; diff --git a/src/components/Loading/Loading.jsx b/src/components/Loading/Loading.jsx new file mode 100644 index 0000000..2431dcb --- /dev/null +++ b/src/components/Loading/Loading.jsx @@ -0,0 +1,93 @@ +import React, {Component} from 'react'; +import ReactDOM from 'react-dom'; +import './loading.scss'; +import Animated from 'animated/lib/targets/react-dom'; + +/* + 用法: + // 显示 + Loading.show(); + // 隐藏 + Loading.hide(); +*/ + +class Loading extends Component { + constructor (...args) { + super(...args); + this.state = { + ani: new Animated.Value(1), + loading: false + } + } + componentDidMount () { + // this.disableScroll(); + this.setState({loading: true}) + } + // 锁定滚动 + disableScroll () { + const documentBody = document.body; + if (documentBody) { + documentBody.style.setProperty('overflow', 'hidden'); + } + } + // 允许滚动 + enableScroll () { + const documentBody = document.body; + if (documentBody) { + documentBody.style.removeProperty('overlow'); + } + } + + // show () { + // this.setState({show: true}); + // } + hide () { + // 出场动画 + Animated.timing(this.state.ani, { + toValue: 0, + duration: 400 + }).start(() => { + try { + this.setState({ani: new Animated.Value(1), loading: false}); + ReactDOM.unmountComponentAtNode(oDiv); + document.body.removeChild(oDiv); + } catch (e) {} + // 销毁 + }); + } + render () { + return ( +
+ +
+
+
+ ) + } +} + +let oDiv = null; +let loading = null; + +Loading.show = () => { + let props = {}; + let ele = document.querySelector('.zy-ui-loading'); + if (ele) return false; + oDiv = document.createElement('div'); + oDiv.className = 'zy-ui-loading'; + document.body.appendChild(oDiv); + ReactDOM.render(React.createElement(Loading, props), oDiv, function () { + loading = this; + }); +} + +Loading.hide = () => { + try { + let ele = document.querySelector('.zy-ui-loading'); + if (ele) loading.hide(); + } catch (e) { + console.error(e) + } +} + +export default Loading; diff --git a/src/components/Loading/index.js b/src/components/Loading/index.js new file mode 100644 index 0000000..6ef70cc --- /dev/null +++ b/src/components/Loading/index.js @@ -0,0 +1,16 @@ +import Loading from './Loading.jsx'; + +// Loading +// 显示 +// Loading.show(); +// 隐藏 +// Loading.hide(); + +export default { + show: () => { + Loading.show(); + }, + hide: () => { + Loading.hide(); + } +}; diff --git a/src/components/Loading/loading.scss b/src/components/Loading/loading.scss new file mode 100644 index 0000000..705fa94 --- /dev/null +++ b/src/components/Loading/loading.scss @@ -0,0 +1,41 @@ +@import '../../assets/style/colors.scss'; + +.loading { + .loading-wrapper { + position: fixed; + z-index: 9999; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: rgba(white, .4); + } + $size: .8rem; + .cmp-loading { + position: fixed; + left: 50%; + top: 50%; + // transform: translate(-50%, -50%); + margin-left: -($size / 2); + margin-top: -($size / 2); + border-radius: 50%; + width: $size; + height: $size; + border: .06rem solid $blue; + border-top-color: $lightGray; + border-bottom-color: $lightGray; + background: transparent; + box-sizing: border-box; + animation: dotRotate .7s infinite ease-in-out; + animation-fill-mode: both; + } +} + +@keyframes dotRotate { + from { + transform: rotateZ(0); + } + to { + transform: rotateZ(360deg); + } +} diff --git a/src/components/Loading2/index.js b/src/components/Loading2/index.js new file mode 100644 index 0000000..c24346c --- /dev/null +++ b/src/components/Loading2/index.js @@ -0,0 +1,48 @@ +import React, {Component} from 'react'; +import Animated from 'animated/lib/targets/react-dom'; +import './loading.scss'; + +/* + 用法: + // 显示 + this.refs.loading.show(); + // 隐藏 + this.refs.loading.hide(); +*/ + +class Loading extends Component { + constructor (...args) { + super(...args); + this.state = { + show: false, + ani: new Animated.Value(1) + } + } + show () { + this.setState({show: true}); + } + hide () { + // 出场动画 + Animated.timing(this.state.ani, { + toValue: 0, + duration: 400 + }).start(() => { + this.setState({show: false, ani: new Animated.Value(1)}); + }); + } + render () { + return ( +
+ { + this.state.show && ( + +
+
+ ) + } +
+ ) + } +} + +export default Loading; diff --git a/src/components/Loading2/loading.scss b/src/components/Loading2/loading.scss new file mode 100644 index 0000000..705fa94 --- /dev/null +++ b/src/components/Loading2/loading.scss @@ -0,0 +1,41 @@ +@import '../../assets/style/colors.scss'; + +.loading { + .loading-wrapper { + position: fixed; + z-index: 9999; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: rgba(white, .4); + } + $size: .8rem; + .cmp-loading { + position: fixed; + left: 50%; + top: 50%; + // transform: translate(-50%, -50%); + margin-left: -($size / 2); + margin-top: -($size / 2); + border-radius: 50%; + width: $size; + height: $size; + border: .06rem solid $blue; + border-top-color: $lightGray; + border-bottom-color: $lightGray; + background: transparent; + box-sizing: border-box; + animation: dotRotate .7s infinite ease-in-out; + animation-fill-mode: both; + } +} + +@keyframes dotRotate { + from { + transform: rotateZ(0); + } + to { + transform: rotateZ(360deg); + } +} diff --git a/src/components/Promopt/index.js b/src/components/Promopt/index.js new file mode 100644 index 0000000..89ec1af --- /dev/null +++ b/src/components/Promopt/index.js @@ -0,0 +1,105 @@ +import React, {Component} from 'react'; +import './promopt.scss'; + +import Animated from 'animated/lib/targets/react-dom' + +/* +this.refs.dialog.show('', '成功注册!', { + // showCancel: false, + confirmText: '知道了' +}, confirm => { + console.log(confirm); +}); +*/ + +class Prompt extends Component { + constructor (...args) { + super(...args); + this.state = { + show: false, + title: '', + options: { + showCancel: true, + cancelText: '', + confirmText: '' + }, + ani: new Animated.Value(0) + } + } + async show (title, func) { + this.setState({ + show: true, + title + }); + // if (options) this.setState({ + // 'options': { + // ...this.state.options, + // ...options + // } + // }); + if (func) this.func = func; + // 入场动画 + Animated.timing(this.state.ani, { + toValue: 1, + duration: 400 + }).start(); + } + clickMask () { + this.fnClose(); + } + fnClose () { + this.func && this.func(false); + Animated.timing(this.state.ani, { + toValue: 0, + duration: 400 + }).start(() => { + this.setState({show: false}); + }); + } + fnConfirm () { + this.func && this.func(true); + Animated.timing(this.state.ani, { + toValue: 0, + duration: 400 + }).start(() => { + this.setState({show: false}); + }); + } + render () { + return ( +
+ { + this.state.show && ( + +
e.stopPropagation()}> +
+ {/* 标题 */} + { + this.state.title && ( +
{this.state.title || '标题'}
+ ) + } +
+
+ {/* 内容 */} + {this.props.children} +
+
+ {/* 底部按钮 */} + { + this.state.options.showCancel && ( +
{this.state.options.cancelText || '取消'}
+ ) + } +
{this.state.options.confirmText || '确定'}
+
+
+
+ ) + } +
+ ) + } +} + +export default Prompt; diff --git a/src/components/Promopt/promopt.scss b/src/components/Promopt/promopt.scss new file mode 100644 index 0000000..ca01f66 --- /dev/null +++ b/src/components/Promopt/promopt.scss @@ -0,0 +1,60 @@ +@import '../../assets/style/colors.scss'; +// dialog 对话框 +.promopt { + font-size: .32rem; + .mask { + position: fixed; + z-index: 9000; + left: 0; + right: 0; + bottom: 0; + top: 0; + background: rgba(black, .6); + } + .dia-wrapper { + position: fixed; + z-index: 9998; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + background: white; + border-radius: .1rem; + width: 4.5rem; + } + .dia-head { + text-align: center; + font-weight: 500; + padding: .2rem; + color: $extraLightBlack; + } + .dia-body { + padding: .2rem; + text-align: center; + font-size: .3rem; + color: $black; + } + .dia-footer { + display: flex; + margin-top: .2rem; + border-top: 1px solid $gray; + align-items: center; + } + $btn-height: .8rem; + .btn { + flex: 1; + height: $btn-height; + line-height: $btn-height; + text-align: center; + // width: 50%; + } + .btn:nth-of-type(2) { + border-left: 1px solid $gray; + } + .btn-cancel { + color: $sliver; + } + .btn-confirm2 { + color: $blue; + font-weight: 500; + } +} diff --git a/src/components/Test/Loading.jsx b/src/components/Test/Loading.jsx new file mode 100644 index 0000000..9aefc98 --- /dev/null +++ b/src/components/Test/Loading.jsx @@ -0,0 +1,27 @@ +import React, {Component} from 'react'; +import ReactDOM from 'react-dom'; +import './loading.scss'; + +export default class Loading extends Component { + render () { + let {tip} = this.props; + console.log(tip); + return ( +
+
+
{tip}
+
+
+ ) + } +} + +Loading.show = function showLoading (options) { + let props = options || {}; + let oDiv = document.createElement('div'); + document.body.appendChild(oDiv); + ReactDOM.render(React.createElement(Loading, props), oDiv); +} + +// ReactDOM.unmountComponentAtNode(div); +// document.body.removeChild(div); diff --git a/src/components/Test/index.js b/src/components/Test/index.js new file mode 100644 index 0000000..15a8c49 --- /dev/null +++ b/src/components/Test/index.js @@ -0,0 +1,7 @@ +import Loading from './Loading.jsx'; + +export default { + open (tip = '加载中') { + Loading.show({tip}); + } +}; diff --git a/src/components/Test/loading.scss b/src/components/Test/loading.scss new file mode 100644 index 0000000..91a92ef --- /dev/null +++ b/src/components/Test/loading.scss @@ -0,0 +1,18 @@ +.loading { + .loading-mask { + position: fixed; + left: 0; + top: 0; + bottom: 0; + right: 0; + background: rgba(black, .6); + z-index: 9999; + } + .wrapper { + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + color: white; + } +} diff --git a/src/components/Toast/Toast.jsx b/src/components/Toast/Toast.jsx new file mode 100644 index 0000000..616235d --- /dev/null +++ b/src/components/Toast/Toast.jsx @@ -0,0 +1,75 @@ +import React, {Component} from 'react'; +import ReactDOM from 'react-dom'; +import './toast.scss'; +import Animated from 'animated/lib/targets/react-dom'; + +/* + 用法: + Toast.show('aaa', [timer = 5s]) +*/ + +class Toast extends Component { + constructor (...args) { + super(...args); + this.state = { + text: '', + ani: new Animated.Value(0) + }; + this.timer = null; + } + componentDidMount () { + this.show(this.props.text, this.props.timer); + } + async show (text, timer = 4) { + // 清除定时器 + clearTimeout(this.timer); + this.setState({ + text + }); + // 入场动画 + Animated.timing(this.state.ani, { + toValue: 1, + duration: 400 + }).start(); + this.timer = await setTimeout(() => { + // 出场动画 + Animated.timing(this.state.ani, { + toValue: 0, + duration: 400 + }).start(() => { + this.setState({ + text: '' + }); + ReactDOM.unmountComponentAtNode(oDiv[0]); + document.body.removeChild(oDiv[0]); + // console.log(oDiv); + oDiv.shift(); + clearTimeout(this.timer); + }); + }, timer * 1000); + } + render () { + return ( +
+ + {this.state.text} + +
+ ) + } +} + +let oDiv = []; + +Toast.show = (text, timer) => { + let props = { + text, timer + } + let div = document.createElement('div'); + div.className = 'zy-ui-toast'; + document.body.appendChild(div); + ReactDOM.render(React.createElement(Toast, props), div); + oDiv.push(div); +} + +export default Toast; diff --git a/src/components/Toast/index.js b/src/components/Toast/index.js new file mode 100644 index 0000000..9b2beb1 --- /dev/null +++ b/src/components/Toast/index.js @@ -0,0 +1,7 @@ +import Toast from './Toast'; + +export default { + show (text, timer) { + Toast.show(text, timer); + } +}; diff --git a/src/components/Toast/toast.scss b/src/components/Toast/toast.scss new file mode 100644 index 0000000..38f2545 --- /dev/null +++ b/src/components/Toast/toast.scss @@ -0,0 +1,14 @@ +.zy-toast { + position: fixed; + z-index: 9998; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + max-width: 5rem; + font-size: .28rem; + background: rgba(black, .6); + padding: .2rem; + color: white; + border-radius: .1rem; + box-sizing: border-box; +} diff --git a/src/components/ddauth/Dialog.js b/src/components/ddauth/Dialog.js new file mode 100644 index 0000000..b477262 --- /dev/null +++ b/src/components/ddauth/Dialog.js @@ -0,0 +1,107 @@ +import React, {Component} from 'react'; +import './dialog.scss'; + +import Animated from 'animated/lib/targets/react-dom' + +/* +this.refs.dialog.show('', '成功注册!', { + // showCancel: false, + confirmText: '知道了' +}, confirm => { + console.log(confirm); +}); +*/ + +class Dialog extends Component { + constructor (...args) { + super(...args); + this.state = { + show: false, + title: '', + message: '', + options: { + showCancel: true, + cancelText: '', + confirmText: '' + }, + ani: new Animated.Value(0) + } + } + async show (title, message, options, func) { + this.setState({ + show: true, + title, + message + }); + if (options) this.setState({ + 'options': { + ...this.state.options, + ...options + } + }); + if (func) this.func = func; + // 入场动画 + Animated.timing(this.state.ani, { + toValue: 1, + duration: 400 + }).start(); + } + clickMask () { + this.fnClose(); + } + fnClose () { + this.func && this.func(false); + Animated.timing(this.state.ani, { + toValue: 0, + duration: 400 + }).start(() => { + this.setState({show: false}); + }); + } + fnConfirm () { + this.func && this.func(true); + Animated.timing(this.state.ani, { + toValue: 0, + duration: 400 + }).start(() => { + this.setState({show: false}); + }); + } + render () { + return ( +
+ { + this.state.show && ( + +
e.stopPropagation()}> +
+ {/* 标题 */} + { + this.state.title && ( +
{this.state.title || '标题'}
+ ) + } +
+
+ {/* 内容 */} +
{this.state.message || '内容'}
+
+
+ {/* 底部按钮 */} + { + this.state.options.showCancel && ( +
{this.state.options.cancelText || '取消'}
+ ) + } +
{this.state.options.confirmText || '确定'}
+
+
+
+ ) + } +
+ ) + } +} + +export default Dialog; diff --git a/src/components/ddauth/Form.js b/src/components/ddauth/Form.js new file mode 100644 index 0000000..cc05ecf --- /dev/null +++ b/src/components/ddauth/Form.js @@ -0,0 +1,132 @@ +import React, { Component } from 'react'; +import './form.scss'; +import Toast from './Toast'; +import fetchJson from '@/utils/fetch'; + +/* + getFormData +*/ + +class Form extends Component { + constructor (...args) { + super(...args); + this.state = { + list: {}, + focus: '', + num: 0 + } + } + async componentDidMount () { + for (let i = 0; i < this.props.fields.length; i++) { + await this.setState({ + list: { + ...this.state.list, + [this.props.fields[i].name]: this.props.fields[i].value + } + }); + } + } + // input focus + fnFocus (name) { + console.log(name); + this.setState({focus: name}); + } + fnBlur () { + console.log('失去焦点'); + // this.setState({focus: ''}); + } + // input 修改 + async fnChange (name, e) { + // console.log(name, e.target.value); + // this[name] = e.target.value; + await this.setState({ + list: { + ...this.state.list, + [name]: e.target.value + } + }) + // console.log(this.state); + } + // 清除 + fnClear (name) { + // console.log('清除' + name); + this.refs[name].value = ''; + this.setState({ + list: { + ...this.state.list, + [name]: '' + } + }) + } + // 获得formData + getFormData () { + // let form = new FormData(this.refs.form); + let json = {}; + this.props.fields.forEach(field => { + json[field.name] = this.refs[field.name].value; + }); + + return json; + } + // 发送验证码 + async fnSendCode (name) { + if (!/^\d{11}$/.test(this.state.list[name]) || this.state.num > 0) return false; + let tel = this.state.list[name]; + console.log(tel); + let form = new FormData(); + form.append('authID', tel); + form.append('authToken', this.props.token); + try { + await fetchJson('/v2/auth2/SendVerifyCode', { + method: 'POST', + body: form + }); + this.refs.toast.show('短信发送成功'); + // 开始倒计时 + await this.setState({num: 59}); + clearInterval(this.timer); + this.timer = setInterval(() => { + if (this.state.num === 0) { + clearInterval(this.timer); + } else { + this.setState({num: --this.state.num}); + } + }, 1000); + } catch (e) { + this.refs.toast.show(e); + } + } + render() { + if (!this.props.fields) console.error('Form need fields'); + return ( +
+ { + this.props.fields.length > 0 && ( + this.props.fields.map((field, index) => ( +
+ + + {/* // 清空按钮 */} + { + (this.state.focus === field.name && this.state.list[field.name]) && ( +
+ ) + } + {/* // 其他按钮 */} + { + field.btn && ( +
{field.btn.name}{this.state.num > 0 && '(' + this.state.num + ')'}
+ ) + } +
+ )) + ) + } + +
+ ); + } + +} + +export default Form; diff --git a/src/components/ddauth/Toast.js b/src/components/ddauth/Toast.js new file mode 100644 index 0000000..8bef5b8 --- /dev/null +++ b/src/components/ddauth/Toast.js @@ -0,0 +1,61 @@ +import React, {Component} from 'react'; +import './toast.scss'; +import Animated from 'animated/lib/targets/react-dom'; + +/* + 用法: + this.refs.xxx.show('aaa', [timer = 5s]) +*/ + +class Toast extends Component { + constructor (...args) { + super(...args); + this.state = { + show: false, + text: '', + ani: new Animated.Value(0) + }; + this.timer = null; + } + async show (text, timer = 5) { + // 清除定时器 + clearTimeout(this.timer); + this.setState({ + show: true, + text + }); + // 入场动画 + Animated.timing(this.state.ani, { + toValue: 1, + duration: 400 + }).start(); + this.timer = await setTimeout(() => { + // 出场动画 + Animated.timing(this.state.ani, { + toValue: 0, + duration: 400 + }).start(() => { + this.setState({ + show: false, + text: '' + }); + clearTimeout(this.timer); + }); + }, timer * 1000); + } + render () { + return ( +
+ { + this.state.show && ( + + {this.state.text} + + ) + } +
+ ) + } +} + +export default Toast; diff --git a/src/components/ddauth/dialog.scss b/src/components/ddauth/dialog.scss new file mode 100644 index 0000000..e31adb5 --- /dev/null +++ b/src/components/ddauth/dialog.scss @@ -0,0 +1,60 @@ +$maincolor: #409EFF; +// dialog 对话框 +.dialog { + font-size: .32rem; + .mask { + position: fixed; + z-index: 9000; + left: 0; + right: 0; + bottom: 0; + top: 0; + background: rgba(black, .6); + } + .dia-wrapper { + position: fixed; + z-index: 9998; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + background: white; + border-radius: .1rem; + width: 4.5rem; + } + .dia-head { + // border-bottom: 1px solid $maincolor; + text-align: center; + font-weight: 500; + padding: .2rem; + color: #303133; + } + .dia-body { + padding: .2rem; + text-align: center; + font-size: .3rem; + color: #606266; + } + .dia-footer { + display: flex; + margin-top: .2rem; + border-top: 1px solid #ccc; + align-items: center; + } + $btn-height: .8rem; + .btn { + flex: 1; + height: $btn-height; + line-height: $btn-height; + text-align: center; + } + .btn:nth-of-type(2) { + border-left: 1px solid #ccc; + } + .btn-cancel { + color: #909399; + } + .btn-confirm { + color: $maincolor; + font-weight: 500; + } +} diff --git a/src/components/ddauth/form.scss b/src/components/ddauth/form.scss new file mode 100644 index 0000000..e8c4946 --- /dev/null +++ b/src/components/ddauth/form.scss @@ -0,0 +1,71 @@ +// 表单样式 +$maincolor: #409EFF; +.form { + .form-item { + font-size: .3rem; + box-sizing: border-box; + height: 1rem; + padding: .2rem; + display: flex; + align-items: center; + background: white; + border-bottom: 1px solid #ccc; + position: relative; + } + .icon { + display: inline-block; + width: .36rem; + height: .36rem; + // border: 1px solid; + background-size: 100%; + background-position: center; + background-repeat: no-repeat; + margin-right: .2rem; + flex-shrink: 0; + } + .icon-username { + background-image: url(../../assets/imgs/icon-username.png); + } + .icon-nickname { + background-image: url(../../assets/imgs/icon-nickname2.png); + } + .icon-tel { + background-image: url(../../assets/imgs/icon-tel.png); + } + .icon-code { + background-image: url(../../assets/imgs/icon-code.png); + } + input { + color: #333; + // padding: .1rem; + height: .6rem; + outline: none; + border: none; + width: 100%; + } + input::-webkit-input-placeholder { + // color: black; + color: #ccc; + } + .clear { + // position: absolute; + background: url(../../assets/imgs/icon-clear.png) center center no-repeat; + width: .8rem; + height: .7rem; + // top: 50%; + // right: 0; + // transform: translateY(-50%); + background-size: .36rem; + } + .btn-getcode { + background: $maincolor; + color: white; + padding: .1rem .2rem; + border-radius: .1rem; + white-space: nowrap; + margin-left: .2rem; + } + .disabled { + opacity: .5; + } +} diff --git a/src/components/ddauth/toast.scss b/src/components/ddauth/toast.scss new file mode 100644 index 0000000..94c3e10 --- /dev/null +++ b/src/components/ddauth/toast.scss @@ -0,0 +1,14 @@ +.zy-toast { + position: fixed; + z-index: 9999; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + max-width: 5rem; + font-size: .28rem; + background: rgba(black, .6); + padding: .2rem; + color: white; + border-radius: .1rem; + box-sizing: border-box; +} diff --git a/src/components/form.scss b/src/components/form.scss new file mode 100644 index 0000000..4ccf55a --- /dev/null +++ b/src/components/form.scss @@ -0,0 +1,71 @@ +// 表单样式 +$maincolor: #409EFF; +.form { + .form-item { + font-size: .3rem; + box-sizing: border-box; + height: 1rem; + padding: .2rem; + display: flex; + align-items: center; + background: white; + border-bottom: 1px solid #ccc; + position: relative; + } + .icon { + display: inline-block; + width: .36rem; + height: .36rem; + // border: 1px solid; + background-size: 100%; + background-position: center; + background-repeat: no-repeat; + margin-right: .2rem; + flex-shrink: 0; + } + .icon-username { + background-image: url(../assets/imgs/icon-username.png); + } + .icon-nickname { + background-image: url(../assets/imgs/icon-nickname2.png); + } + .icon-tel { + background-image: url(../assets/imgs/icon-tel.png); + } + .icon-code { + background-image: url(../assets/imgs/icon-code.png); + } + input { + color: #333; + // padding: .1rem; + height: .6rem; + outline: none; + border: none; + width: 100%; + } + input::-webkit-input-placeholder { + // color: black; + color: #ccc; + } + .clear { + // position: absolute; + background: url(../assets/imgs/icon-clear.png) center center no-repeat; + width: .8rem; + height: .7rem; + // top: 50%; + // right: 0; + // transform: translateY(-50%); + background-size: .36rem; + } + .btn-getcode { + background: $maincolor; + color: white; + padding: .1rem .2rem; + border-radius: .1rem; + white-space: nowrap; + margin-left: .2rem; + } + .disabled { + opacity: .5; + } +} diff --git a/src/components/layout/CheckBox.js b/src/components/layout/CheckBox.js new file mode 100644 index 0000000..444fb35 --- /dev/null +++ b/src/components/layout/CheckBox.js @@ -0,0 +1,40 @@ +import React, { Component } from 'react'; +import classNames from 'classnames'; +import './main.scss'; + +/* +current={[]} [-1, 0, 1] +fields={[ + {value: 1, text: '营业中'}, + {value: 0, text: '休息中'}, + {value: -1, text: '禁用中'} +]} +fnClick={this.changeStatuss.bind(this)} changeStatuss(val) +*/ + +class CheckBox extends Component { + fnClick (val) { + // console.log(val); + this.props.fnClick && this.props.fnClick(val); + } + render() { + let fields = this.props.fields || []; + let current = this.props.current || []; + return ( +
+ { + fields.map((field, index) => ( +
{field.text}
+ )) + } +
+ ); + } + +} + +export default CheckBox; diff --git a/src/components/layout/CheckBoxSelf.js b/src/components/layout/CheckBoxSelf.js new file mode 100644 index 0000000..53244f1 --- /dev/null +++ b/src/components/layout/CheckBoxSelf.js @@ -0,0 +1,37 @@ +import React, { Component } from 'react'; +import classNames from 'classnames'; +import './main.scss'; + +/* +current={[]} [-1, 0, 1] +fields={[ + {value: 1, text: '营业中'}, + {value: 0, text: '休息中'}, + {value: -1, text: '禁用中'} +]} +fnClick={this.changeStatuss.bind(this)} changeStatuss(val) +*/ + +class CheckBoxSelf extends Component { + fnClick (val) { + // console.log(val); + this.props.fnClick && this.props.fnClick(val); + } + render() { + let fields = this.props.fields || []; + let current = this.props.current || null; + return ( +
+
+ {this.props.text} +
+
+ ); + } + +} + +export default CheckBoxSelf; diff --git a/src/components/layout/CheckList.js b/src/components/layout/CheckList.js new file mode 100644 index 0000000..9b6e133 --- /dev/null +++ b/src/components/layout/CheckList.js @@ -0,0 +1,113 @@ +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; +import Animated from 'animated/lib/targets/react-dom'; +import classNames from 'classnames'; +import BScroll from 'better-scroll'; +import './main.scss'; +const clientH = document.documentElement.clientHeight; +/* +ref="checklist" +datas={this.props.system.cityLevel2} +nullShow = false 是否显示空 +nullLabel = '' null显示 +map={{value: 'code', label: 'name'}} +current={storeSearch.placeID} +title="城市选择" +fnPick={this.cityPick.bind(this)} + +// 打开 +this.refs.xxx.show() +*/ + +class CheckList extends Component { + constructor (...args) { + super(...args); + this.state = { + show: false, + ani: new Animated.Value(clientH) + }; + this.scroll = null; + } + componentDidMount () { + + } + componentDidUpdate () { + if (this.scroll) { + this.scroll.refresh(); + this.scroll.scrollTo(0, 0); + } else { + if (this.refs.bodyWrapper) { + this.scroll = new BScroll(this.refs.bodyWrapper, { + click: true, + stopPropagation: true, + bounce: { + top: false, + bottom: false + } + }); + } + } + } + // show + show () { + // console.log(this) + this.setState({show: true}); + Animated.timing(this.state.ani, { + toValue: 0, + duration: 300 + }).start(() => { + console.log('start') + }); + } + hide () { + this.setState({show: false, ani: new Animated.Value(clientH)}); + this.scroll = null; + } + // 返回 + fnReturn () { + console.log('返回'); + this.hide(); + } + // 选取 + fnClick (code) { + // console.log(code); + this.props.fnPick && this.props.fnPick(code); + this.hide() + } + render() { + return ReactDOM.createPortal(( +
+ { + this.state.show && ( +
+ e.stopPropagation()}> +
+
返回
+
{this.props.title}
+
+ {/*onTouchMove={this.fnTouchMove.bind(this)} */} +
+
+ { + this.props.nullShow && ( +
{this.props.nullLabel}
+ ) + } + { + this.props.datas.length > 0 && this.props.datas.map((data, index) => ( +
{data[this.props.map.label]}
+ )) + } +
+
+
+
+ ) + } +
+ ), document.getElementById('root')); + } + +} + +export default CheckList; diff --git a/src/components/layout/KeyWordSearch.js b/src/components/layout/KeyWordSearch.js new file mode 100644 index 0000000..62f13ed --- /dev/null +++ b/src/components/layout/KeyWordSearch.js @@ -0,0 +1,38 @@ +import React, { Component } from 'react'; +import './main.scss'; +import classNames from 'classnames'; + +import SearchStore from './SearchStore'; + +class KeyWordSearch extends Component { + constructor (...args) { + super(...args); + this.state = { + name: '', + id: null + }; + } + showPick = () => { + this.refs.storePick.show(); + } + pickStore = async (val) => { + // console.log(val); + let {id, name} = val; + await this.setState({ + name: `${name}-${id}`, + id + }); + this.props.fnPick && this.props.fnPick(id); + } + render() { + return ( +
+
{this.state.name ? this.state.name : this.props.placeholder}
+ +
+ ); + } + +} + +export default KeyWordSearch; diff --git a/src/components/layout/PickInput.js b/src/components/layout/PickInput.js new file mode 100644 index 0000000..ea6dde0 --- /dev/null +++ b/src/components/layout/PickInput.js @@ -0,0 +1,60 @@ +import React, {Component} from 'react'; +import './main.scss'; +import ReactSVG from 'react-svg'; + +import CheckList from '@/components/layout/CheckList'; +import SVGDown from '@/assets/svg/icon-down.svg'; + +class PickInput extends Component { + constructor (...args) { + super(...args); + this.state = {}; + } + // 选取后 + fnPick (val) { + // console.log(val); + this.props.fnPick && this.props.fnPick(val); + } + // 弹出选择窗口 + showList () { + this.refs.checklist.show(); + } + // 输入框改变 + fnChange = e => { + // console.log(e.target.value); + this.props.fnChange && this.props.fnChange(e.target.value); + } + // 选取输入框 + render () { + // 显示文字 + let text = this.props.datas.find(item => item[this.props.map['value']] === this.props.current); + return ( +
+ {/* 选择框 */} +
+ {text[this.props.map['label']]} + +
+ {/* 输入框 */} + + {/* 操作按钮 */} + { + this.props.fnConfirm && ( +
{this.props.fnConfirm()}}>获取
+ ) + } + {/* 弹出窗口 */} + +
+ ); + } +} + +export default PickInput; diff --git a/src/components/layout/PopupPage.js b/src/components/layout/PopupPage.js new file mode 100644 index 0000000..2adabe3 --- /dev/null +++ b/src/components/layout/PopupPage.js @@ -0,0 +1,145 @@ +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; +import './main.scss'; +import ReactSVG from 'react-svg'; +import Animated from 'animated/lib/targets/react-dom'; +import classNames from 'classnames'; +import BScroll from 'better-scroll'; + +import SVGReturn from '@/assets/svg/icon-return.svg'; +// import SVGConfirm from '@/assets/svg/icon-confirm.svg'; + +const screenW = document.body.clientWidth; + +/* +title="更多检索条件" +fnReturn={this.props.fnReturn.bind(this)} +fnPrevConfirm={fn} ruturn true or false 确认前 +fnConfirm={this.props.fnConfirm.bind(this)} +*/ + +class PopupPage extends Component { + constructor (...args) { + super(...args); + this.state = { + ani: new Animated.Value(screenW) + }; + this.isMain = !this.props.fnReturn && !this.props.fnConfirm; + this.scroll = null; + } + componentDidMount () { + // 入场动画 + Animated.timing(this.state.ani, { + toValue: 0, + duration: 300 + }).start(() => { + // 增加滚动 + // this.scroll = new BScroll(this.refs.toBottom, { + // click: true, + // stopPropagation: true, + // bounce: { + // top: false, + // bottom: false + // } + // }) + }); + } + componentDidUpdate () { + if (this.scroll) { + this.scroll.refresh(); + } else { + if (this.refs.toBottom) { + this.scroll = new BScroll(this.refs.toBottom, { + click: true, + stopPropagation: true, + bounce: { + top: false, + bottom: false + } + }); + } + } + } + // 返回按钮 + fnReturn () { + // 出场动画 + Animated.timing(this.state.ani, { + toValue: -screenW, + duration: 300 + }).start(() => { + this.props.fnReturn(); + }); + } + // 确认按钮 + async fnConfirm () { + if (this.props.fnPrevConfirm) { + let res = await this.props.fnPrevConfirm(); + if (res) { + Animated.timing(this.state.ani, { + toValue: -screenW, + duration: 300 + }).start(() => { + this.props.fnConfirm(); + }); + } + } else { + Animated.timing(this.state.ani, { + toValue: -screenW, + duration: 300 + }).start(() => { + this.props.fnConfirm(); + }); + } + } + // 滚动到底部 + toBottom () { + // console.log() + // this.refs.toBottom.scrollTop = 100; + } + fnTouchMove (e) { + console.log(e); + e.preventDefault(); + e.stopPropagation(); + return false; + } + render() { + return ReactDOM.createPortal(( +
+ +
+ {/* 返回按钮 */} + { + this.props.fnReturn && ( +
+ +
+ ) + } +
+ { + this.props.SVG && + } + {this.props.title} +
+ {/* 确定按钮 */} + { + this.props.fnConfirm && ( +
+ {/* */} + 确定 +
+ ) + } +
+
+
+ {this.props.children} +
+
+
+
+ ), document.getElementById('root')); + } +} + +export default PopupPage; diff --git a/src/components/layout/PopupPage2.js b/src/components/layout/PopupPage2.js new file mode 100644 index 0000000..ac48126 --- /dev/null +++ b/src/components/layout/PopupPage2.js @@ -0,0 +1,81 @@ +import React, { Component } from 'react'; +import './main.scss'; +import ReactSVG from 'react-svg' +import classNames from 'classnames'; + +import SVGReturn from '@/assets/svg/icon-return.svg'; +// import SVGConfirm from '@/assets/svg/icon-confirm.svg'; + +/* +title="更多检索条件" +fnReturn={this.props.fnReturn.bind(this)} +fnPrevConfirm={fn} ruturn true or false 确认前 +fnConfirm={this.props.fnConfirm.bind(this)} +*/ + +class PopupPage extends Component { + constructor (...args) { + super(...args); + this.state = {}; + this.isMain = !this.props.fnReturn && !this.props.fnConfirm; + } + // 返回按钮 + fnReturn () { + this.props.fnReturn(); + } + // 确认按钮 + async fnConfirm () { + if (this.props.fnPrevConfirm) { + let res = await this.props.fnPrevConfirm(); + if (res) { + this.props.fnConfirm(); + } + } else { + this.props.fnConfirm(); + } + } + // 滚动到底部 + toBottom () { + // console.log() + // this.refs.toBottom.scrollTop = 100; + } + render() { + return ( +
+
+
+ {/* 返回按钮 */} + { + this.props.fnReturn && ( +
+ +
+ ) + } +
+ { + this.props.SVG && + } + {this.props.title} +
+ {/* 确定按钮 */} + { + this.props.fnConfirm && ( +
+ {/* */} + 确定 +
+ ) + } +
+
+ {this.props.children} +
+
+
+ ); + } + +} + +export default PopupPage; diff --git a/src/components/layout/Radio.js b/src/components/layout/Radio.js new file mode 100644 index 0000000..18f9aa7 --- /dev/null +++ b/src/components/layout/Radio.js @@ -0,0 +1,36 @@ +import React, { Component } from 'react'; +import classNames from 'classnames'; +import './main.scss'; + +// 单选组件 +/* +fields={[ + {value: 0, text: '不限定'}, + {value: 1, text: '已绑定'}, + {value: -1, text: '未绑定'} +]} +id={} +current={storeSearch.vendorStoreConds['0']} +fnClick={} return val id +*/ + +class Radio extends Component { + fnClick (val) { + // console.log(val); + this.props.fnClick && this.props.fnClick(val, this.props.id); + } + render() { + return ( +
+ { + this.props.fields && this.props.fields.map((field, index) => ( +
{field.text}
+ )) + } +
+ ); + } + +} + +export default Radio; diff --git a/src/components/layout/SearchAddress.js b/src/components/layout/SearchAddress.js new file mode 100644 index 0000000..ac5e88e --- /dev/null +++ b/src/components/layout/SearchAddress.js @@ -0,0 +1,237 @@ +import React, { Component } from 'react'; +import Animated from 'animated/lib/targets/react-dom'; +import fetchJsonp from 'fetch-jsonp'; +import fetchJson from '@/utils/fetch'; +import './main.scss'; +import classNames from 'classnames'; +import BScroll from 'better-scroll'; +import * as dd from 'dingtalk-jsapi'; + +import Loading from '@/components/Loading'; +import ReactSVG from 'react-svg' +import SVGAddress from '@/assets/svg/icon-address.svg'; +import SVGAddressList from '@/assets/svg/icon-addresslist.svg'; +import SVGMYPOSITION from '@/assets/svg/icon-curposition.svg'; + +const clientH = document.documentElement.clientHeight; +/* +region 市名 +ref="SearchAddress" +fnPick={this.fn.bind(this)} + +// 打开 +this.refs.xxx.show() +*/ + +class SearchAddress extends Component { + constructor (...args) { + super(...args); + this.state = { + show: false, + ani: new Animated.Value(clientH), + keyword: '', + addressList: [], + lat: '', + lng: '' + }; + this.scroll = null; + } + async componentDidMount () { + console.log('地图 mounted'); + await this.getAddress(); + } + componentDidUpdate () { + console.log('update'); + if (this.scroll) { + this.scroll.refresh(); + this.scroll.scrollTo(0, 0); + } else { + if (this.refs.scrollList) { + this.scroll = new BScroll(this.refs.scrollList, { + click: true, + stopPropagation: true, + bounce: { + top: false, + bottom: false + } + }); + } + } + } + async posiAddress () { + let {result} = await (await fetchJsonp(`https://apis.map.qq.com/ws/geocoder/v1/?output=jsonp&key=ZGGBZ-RUWLQ-XY25J-GD2AV-YPT2T-6EFA6&location=${this.state.lat},${this.state.lng}&get_poi=1`)).json() + // console.log('datasdfdsfdsfd', result); + if (result) { + this.setState({addressList: result.pois}); + } + } + // 腾讯地图查找地址关键字 + async getAddress () { + // let {data} = await (await fetchJsonp(`https://apis.map.qq.com/ws/place/v1/suggestion/?output=jsonp&key=ZGGBZ-RUWLQ-XY25J-GD2AV-YPT2T-6EFA6&keyword=${this.state.keyword}®ion=${this.props.region}&policy=1®ion_fix=1`)).json() + let {data} = await (await fetchJsonp(`https://apis.map.qq.com/ws/place/v1/search/?output=jsonp&key=ZGGBZ-RUWLQ-XY25J-GD2AV-YPT2T-6EFA6&keyword=${encodeURI(this.state.keyword)}&boundary=region(${this.props.region},0)`)).json() + if (data) { + this.setState({addressList: data}); + } + // address: "四川省成都市青羊区童子街50号" + // location: {lat, lng} + // title: '童子街' + } + // 输入关键词 + async handleKeyword (e) { + await this.setState({keyword: e.target.value}); + await this.getAddress(this.state.keyword); + } + // show + async show () { + await this.setState({show: true}); + Animated.timing(this.state.ani, { + toValue: 0, + duration: 300 + }).start(); + this.refs.scrollList.addEventListener('scroll', this.handleBlur.bind(this)); + // if (dd.ios || dd.android) { + // dd.device.geolocation.get({ + // targetAccuracy: 200, + // coordinate: 1, + // onSuccess: res => { + // let {longitude, latitude} = res; + // this.setState({ + // lat: latitude, + // lng: longitude + // }); + // }, + // onFail: err => { + // this.setState({ + // lat: JSON.stringify(err) + // }); + // } + // }); + // } + } + hide () { + this.setState({show: false, ani: new Animated.Value(clientH)}); + this.refs.scrollList.removeEventListener('scroll', this.handleBlur.bind(this)); + this.scroll = null; + } + // 滚动失去焦点 + handleBlur () { + this.refs.keyword.blur(); + } + // 选取地址 + fnClick (address) { + // console.log(address); + this.props.fnPick && this.props.fnPick(address); + this.hide(); + } + // 返回 + fnReturn () { + console.log('返回'); + this.hide(); + } + // 获取当前定位 + async getCurPosition () { + // 清除关键字,失去焦点 + this.refs.keyword.value = ''; + this.setState({keyword: ''}); + this.refs.keyword.blur(); + console.log('获取当前定位'); + let config = { + agentId: process.env.NODE_ENV !== 'production' ? '241047291' : '239461075', // 241047291 239461075 + corpId: 'ding7ab5687f3784a8db', + timeStamp: Math.round(new Date().getTime() / 1000), + nonceStr: 'rosy' + new Date().getTime(), + signature: '', // 接口获取 + jsApiList: ['device.geolocation.get'] + } + const url = window.location.origin + window.location.pathname; + console.log(config); + + // 通过京西接口后去signature + let signature = await fetchJson(`/v2/ddapi/Sign?url=${url}&nonceStr=${config.nonceStr}&timeStamp=${config.timeStamp}`); + console.log('signature', signature); + config.signature = signature; + // 通过接口获取鉴权 + if (dd.ios || dd.android) { + dd.ready(() => { + dd.config({ + ...config, + }); + dd.ready(() => { + Loading.show(); + dd.device.geolocation.get({ + targetAccuracy: 200, + coordinate: 1, + onSuccess: async res => { + let {longitude, latitude} = res; + await this.setState({ + lat: latitude, + lng: longitude + }); + // 通过经纬度获取地址列表 + await this.posiAddress(); + Loading.hide(); + }, + onFail: err => { + console.log(JSON.stringify(err)); + dd.device.notification.alert({ + message: JSON.stringify(err), + title: '错误' + }) + Loading.hide(); + } + }); + }); + dd.error(err => { + console.log(JSON.stringify(err)); + dd.device.notification.alert({ + message: JSON.stringify(err), + title: '错误' + }) + }); + }) + } + } + render() { + return ( +
+ { + this.state.show && ( +
+ e.stopPropagation()}> +
+
返回
+
+ + +
+
+ +
+
+
+
+ { + this.state.addressList.length > 0 && this.state.addressList.map(address => ( +
+ +
+
{address.title}
+
{address.address}
+
+
选择
+
+ )) + } +
+
+
+
+ ) + } +
+ ); + } + +} + +export default SearchAddress; diff --git a/src/components/layout/SearchStore.js b/src/components/layout/SearchStore.js new file mode 100644 index 0000000..248deb6 --- /dev/null +++ b/src/components/layout/SearchStore.js @@ -0,0 +1,161 @@ +import React, { Component } from 'react'; +import Animated from 'animated/lib/targets/react-dom'; +import fetchJson from '@/utils/fetch'; +import BScroll from 'better-scroll'; +import './main.scss'; +import classNames from 'classnames'; +import {debounce} from '@/utils' + +import ReactSVG from 'react-svg' +import SVGAddress from '@/assets/svg/icon-store.svg'; +// import SVGAddressList from '@/assets/svg/icon-addresslist.svg'; + +const clientH = document.documentElement.clientHeight; +/* +region 市名 +ref="SearchAddress" +fnPick={this.fn.bind(this)} + +// 打开 +this.refs.xxx.show() +*/ + +class SearchStore extends Component { + constructor (...args) { + super(...args); + this.state = { + show: false, + ani: new Animated.Value(clientH), + storeList: [], + keyword: '' + }; + this.doSearch = debounce(this.doSearch, 1000) + } + componentDidUpdate () { + if (this.scroll) { + this.scroll.refresh(); + this.scroll.scrollTo(0, 0); + } else { + if (this.refs.scrollList) { + this.scroll = new BScroll(this.refs.scrollList, { + click: true, + stopPropagation: true, + bounce: { + top: false, + bottom: false + } + }); + } + } + } + // 查询门店 + async getAPI (keyword) { + try { + let {stores} = await fetchJson(`/v2/store/GetStores?keyword=${this.state.keyword}&pageSize=10`); + console.log(stores) + if (stores) { + this.setState({storeList: stores}); + } else { + this.setState({storeList: []}); + } + } catch (e) { + this.setState({storeList: []}); + } + } + // 输入关键词 + async handleKeyword (e) { + // let _this = this + // debounce(async (e) => { + // console.log(e.target.value) + // await _this.setState({keyword: e.target.value}); + // await _this.getAPI(_this.state.keyword); + // }, 1000) + this.doSearch(e.target.value) + } + doSearch = async (value) => { + await this.setState({keyword: value}); + await this.getAPI(this.state.keyword); + } + // show + async show () { + await this.setState({show: true, storeList: []}); + Animated.timing(this.state.ani, { + toValue: 0, + duration: 300 + }).start(); + this.refs.scrollList.addEventListener('scroll', this.handleBlur.bind(this)); + // this.refs.keyword.focus(); + await this.getAPI(this.state.keyword); + } + hide () { + this.setState({show: false, ani: new Animated.Value(clientH)}); + this.refs.scrollList.removeEventListener('scroll', this.handleBlur.bind(this)); + this.scroll = null; + } + // 滚动失去焦点 + handleBlur () { + this.refs.keyword.blur(); + } + // 选取地址 + fnClick (item) { + // console.log(item); + this.props.fnPick && this.props.fnPick(item); + this.hide(); + } + // 返回 + fnReturn () { + console.log('返回'); + this.hide(); + } + // 清除 + fnClear = () => { + this.props.fnClear && this.props.fnClear() + this.hide() + } + render() { + return ( +
+ { + this.state.show && ( +
+ e.stopPropagation()}> +
+
返回
+
+ + +
+ { + this.props.fnClear && ( +
清除选择
+ ) + } +
+
+
+ { + this.state.storeList.length > 0 && this.state.storeList.map(store => ( +
+ +
+
{store.name}
+
ID:{store.id} {store.cityName}-{store.districtName}
+
+
选择
+
+ //
111
+ )) + } +
+
+
+
+ ) + } +
+ ); + } + +} + +export default SearchStore; diff --git a/src/components/layout/SearchUser.js b/src/components/layout/SearchUser.js new file mode 100644 index 0000000..be1bf28 --- /dev/null +++ b/src/components/layout/SearchUser.js @@ -0,0 +1,140 @@ +import React, { Component } from 'react'; +import Animated from 'animated/lib/targets/react-dom'; +import fetchJson from '@/utils/fetch'; +import BScroll from 'better-scroll'; +import './main.scss'; +import classNames from 'classnames'; + +import ReactSVG from 'react-svg' +import SVGAddress from '@/assets/svg/icon-user.svg'; +// import SVGAddressList from '@/assets/svg/icon-addresslist.svg'; + +const clientH = document.documentElement.clientHeight; +/* +region 市名 +ref="SearchAddress" +fnPick={this.fn.bind(this)} + +// 打开 +this.refs.xxx.show() +*/ + +class SearchUser extends Component { + constructor (...args) { + super(...args); + this.state = { + show: false, + ani: new Animated.Value(clientH), + userList: [], + keyword: '' + }; + } + componentDidUpdate () { + if (this.scroll) { + this.scroll.refresh(); + this.scroll.scrollTo(0, 0); + } else { + if (this.refs.scrollList) { + this.scroll = new BScroll(this.refs.scrollList, { + click: true, + stopPropagation: true, + bounce: { + top: false, + bottom: false + } + }); + } + } + } + // 查询门店 + async getAPI (keyword) { + try { + let {data} = await fetchJson(`/v2/user2/GetUsers?keyword=${keyword}&userType=14&offset=0&pageSize=20`); + console.log('用户列表', data) + if (data) { + this.setState({userList: data}); + } else { + this.setState({userList: []}); + } + } catch (e) { + this.setState({userList: []}); + } + } + // 输入关键词 + async handleKeyword (e) { + await this.setState({keyword: e.target.value}); + await this.getAPI(this.state.keyword); + } + // show + async show () { + await this.setState({show: true, userList: []}); + Animated.timing(this.state.ani, { + toValue: 0, + duration: 300 + }).start(); + this.refs.scrollList.addEventListener('scroll', this.handleBlur.bind(this)); + // this.refs.keyword.focus(); + await this.getAPI(this.state.keyword); + } + hide () { + this.setState({show: false, ani: new Animated.Value(clientH)}); + this.refs.scrollList.removeEventListener('scroll', this.handleBlur.bind(this)); + this.scroll = null; + } + // 滚动失去焦点 + handleBlur () { + this.refs.keyword.blur(); + } + // 选取地址 + fnClick (item) { + // console.log(item); + this.props.fnPick && this.props.fnPick(item); + this.hide(); + } + // 返回 + fnReturn () { + console.log('返回'); + this.hide(); + } + render() { + return ( +
+ { + this.state.show && ( +
+ e.stopPropagation()}> +
+
返回
+
+ + +
+
+
+
+ { + this.state.userList.length > 0 && this.state.userList.map(user => ( +
+ +
+
{user.name}
+
tel:{user.mobile}
+
+
选择
+
+ //
111
+ )) + } +
+
+
+
+ ) + } +
+ ); + } + +} + +export default SearchUser; diff --git a/src/components/layout/Tabs.js b/src/components/layout/Tabs.js new file mode 100644 index 0000000..eba7692 --- /dev/null +++ b/src/components/layout/Tabs.js @@ -0,0 +1,21 @@ +import React, { Component } from 'react'; +import classNames from 'classnames'; + +class Tabs extends Component { + render() { + return ( + + ); + } + +} + +export default Tabs; diff --git a/src/components/layout/main.scss b/src/components/layout/main.scss new file mode 100644 index 0000000..48b2d63 --- /dev/null +++ b/src/components/layout/main.scss @@ -0,0 +1,436 @@ +// 弹出窗口 +@import '../../assets/style/colors.scss'; + +.popup-page, .popup-page2 { + .popup-wrapper { + position: fixed; + z-index: 2000; + left: 0; + top: 0; + bottom: 0; + right: 0; + background: white; + } + .popup-wrapper-main { + bottom: 1rem; + } + + .popup-menu { + height: 1rem; + background: $blue; + } + .popup-content { + // position: absolute; + box-sizing: border-box; + width: 100%; + // top: 1rem; + height: calc(100vh - 1rem); + // overflow-y: auto; + overflow: hidden; + // -webkit-overflow-scrolling: touch; + background: #fafafa; + // touch-action: none; + // pointer-events: none; + } + .popup-content-main { + height: calc(100% - 1rem); + } + .popup-menu { + display: flex; + justify-content: space-between; + align-items: center; + // padding: 0 .2rem; + } + .menu-center { + justify-content: center; + } + .popup-title { + color: white; + font-size: .32rem; + padding: 0 .2rem; + .svg { + display: inline-block; + margin-right: .1rem; + vertical-align: -0.06rem; + svg { + fill: white; + width: .34rem; + height: .34rem; + } + } + } + .btn-return, .btn-confirm { + color: $gray; + font-size: .28rem; + // padding: .1rem .4rem; + height: 100%; + width: 1.6rem; + // border: 1px solid white; + border-radius: .1rem; + display: flex; + .icon { + margin: auto 0; + svg { + width: .5rem; + height: .5rem; + fill: white; + } + } + } + .btn-return { + .icon { + margin-left: .1rem; + } + } + .btn-confirm { + justify-content: flex-end; + .icon { + margin-right: .1rem; + } + .btn-confirm-text { + margin: auto 0; + margin-right: .2rem; + color: white; + font-size: .32rem; + // text-decoration: underline; + // font-weight: bold; + background: $success; + padding: .14rem .3rem; + border-radius: .1rem; + border: 1px solid white; + // box-shadow: 0 0 .05rem rgba($gray, 1); + } + } +} + +.popup-page2 { + .popup-wrapper { + position: static; + } + .popup-content-main { + height: calc(100% - 2rem); + } +} + +// 单选 多选 +.zy-ui-radio, .zy-ui-checkbox { + display: flex; + font-size: .28rem; + // border-radius: .1rem; + flex-flow: row wrap; + .zy-ui-radio-btn, .zy-ui-checkbox-btn { + background: $gray; + color: white; + padding: .18rem; + transition: all .3s ease-in-out; + // &:first-of-type { + // border-top-left-radius: .1rem; + // border-bottom-left-radius: .1rem; + // } + // &:last-of-type { + // border-top-right-radius: .1rem; + // border-bottom-right-radius: .1rem; + // } + } + .zy-ui-radio-btn-active { + background: $blue; + } + .zy-ui-checkbox-btn-active { + background: $success; + } + .zy-ui-checkbox-check-true { + display: inline-block; + width: .4rem; + text-align: center; + color: white; + &:before { + content: "✔\fe0e"; + color: white; + } + } + .zy-ui-checkbox-check-false { + display: inline-block; + width: .4rem; + text-align: center; + color: white; + &:before { + content: "✘"; + color: white; + } + } +} + +// checkList 列表单选 +.zy-ui-checklist { + .zy-ui-checklist-mask { + position: fixed; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: rgba(black, .4); + z-index: 3000; + } + .zy-ui-checklist-wrapper { + background: white; + position: absolute; + bottom: 0; + width: 100%; + height: 80%; + border-top-left-radius: .2rem; + border-top-right-radius: .2rem; + padding: .2rem; + box-sizing: border-box; + } + .zy-ui-checklist-header { + font-size: .32rem; + height: .8rem; + box-sizing: border-box; + border-bottom: 1px solid $blue; + } + .zy-ui-checklist-return { + position: absolute; + line-height: .8rem; + // background: red; + width: 1.5rem; + color: $sliver; + font-size: .3rem; + z-index: 1; + } + .zy-ui-checklist-title { + line-height: .8rem; + text-align: center; + color: $blue; + } + .zy-ui-checklist-body { + font-size: .3rem; + height: calc(100% - .8rem); + overflow: hidden; + // -webkit-overflow-scrolling: touch; + & > div { + display: flex; + flex-flow: row wrap; + } + .zy-ui-checklist-item { + text-align: center; + padding: .2rem; + border-radius: .5rem; + width: 50%; + box-sizing: border-box; + } + .zy-ui-checklist-item-active { + background: $lightBlue; + color: white; + } + } + .zy-ui-address-header { + font-size: .32rem; + height: .8rem; + box-sizing: border-box; + border-bottom: 1px solid $sliver; + // width: 100%; + .zy-ui-checklist-return { + width: 1.2rem; + // background: red; + } + .zy-ui-checklist-position { + position: absolute; + // background: red; + width: 1rem; + color: $sliver; + font-size: .3rem; + z-index: 1; + top: .38rem; + right: .2rem; + text-align: center; + .my-position { + svg { + fill: $sliver; + width: .36rem; + height: .36rem; + } + } + } + .zy-ui-address-input { + text-align: center; + // background: green; + margin-left: 1.2rem; + height: 100%; + // width: 5rem; + position: relative; + } + input { + font-size: .32rem; + position: absolute; + left: 2.24rem; + top: 50%; + transform: translate(-50%, -50%); + padding: .1rem .2rem; + padding-left: .8rem; + outline: none; + border-radius: .1rem; + border: 1px solid $sliver; + width: 3.6rem; + } + .svg { + position: absolute; + left: .2rem; + top: 50%; + transform: translateY(-50%); + svg { + fill: $sliver; + width: .36rem; + height: .36rem; + } + } + } + .store-pick { + display: flex; + align-items: center; + .zy-ui-address-header { + position: static; + } + .zy-ui-address-input { + flex: 1; + margin-left: 0; + } + .zy-ui-checklist-return { + position: static; + flex:none; + } + .zy-ui-checklist-clear { + flex: none; + line-height: .8rem; + // background: red; + width: 1.5rem; + color: $sliver; + font-size: .3rem; + text-align: right; + } + } + .zy-ui-address-body { + height: calc(100% - .8rem); + overflow: hidden; + // -webkit-overflow-scrolling: touch; + } + .address-item { + font-size: .32rem; + display: flex; + align-items: center; + border-bottom: 1px solid $gray; + padding: .2rem; + .svg { + width: .6rem; + flex-shrink: 0; + svg { + fill: $success; + width: .4rem; + height: .4rem; + } + } + .address-content { + flex: 1; + .content1 {} + .content2 { + margin-top: .1rem; + color: $sliver; + font-size: .24rem; + } + } + .pick-text { + flex-shrink: 0; + color: $blue; + width: .8rem; + text-align: right; + } + } +} + +// 地址列表 +.search-address { + +} + +// Tabs +.zy-ui-tabs { + margin: 0; + padding: 0; + font-size: .32rem; + display: flex; + justify-content: space-around; + background: white; + box-shadow: 0 0 .05rem $gray; + position: relative; + z-index: 1; + .zy-ui-tabs-item { + list-style: none; + height: .8rem; + line-height: .8rem; + padding: 0 .2rem; + box-sizing: border-box; + color: $black; + transition: all .3s; + } + .zy-ui-tabs-item-active { + border-bottom: .04rem solid $blue; + color: $blue; + font-weight: bold; + } +} + +// 选择输入框 +.zy-ui-pick-input { + display: flex; + align-items: center; + box-sizing: border-box; + width: 100%; + .zy-ui-pick-btn { + display: flex; + color: $sliver; + width: 2rem; + flex-shrink: 0; + .zy-ui-pick-icon { + svg { + width: .32rem; + height: .32rem; + fill: $sliver; + } + } + } + .zy-ui-pick-cmpinput { + color: $black; + border: 1px solid $gray; + padding: .1rem; + border-radius: .1rem; + outline: none; + width: 100%; + flex: 1; + } + .zy-ui-pick-confirmbtn { + flex-shrink: 0; + margin-left: .2rem; + padding: .14rem .3rem; + border-radius: .1rem; + background: $primary; + color: white; + } +} + +.zy-ui-keywordsearch { + border: 1px solid $gray; + padding: .1rem .2rem; + border-radius: .1rem; + width: 100%; + box-sizing: border-box; + .placeholder { + color: $sliver; + // box-sizing: border-box; + width: 100%; + // white-space:nowrap; + // overflow:hidden; + // text-overflow:ellipsis; + } + .name { + color: $black; + } +} diff --git a/src/components/pageCmp/CmpModifyBase.js b/src/components/pageCmp/CmpModifyBase.js new file mode 100644 index 0000000..e0de8a9 --- /dev/null +++ b/src/components/pageCmp/CmpModifyBase.js @@ -0,0 +1,525 @@ +import React, { Component } from 'react'; +import './cmp-modify.scss'; +import {connect} from 'react-redux'; +import fetchJson from '@/utils/fetch'; +import Loading from '@/components/Loading'; +import {isNum, isEmpty, isTel, isTel2} from '@/utils/regExp'; +import {businessHours, dealOpenTime} from '@/utils/projectTools'; +import Dialog from '@/components/Dialog'; +import CheckList from '@/components/layout/CheckList'; +import {mapPlaces} from '@/utils/mapData'; +import ReactSVG from 'react-svg' +import SVGGo from '@/assets/svg/icon-go.svg'; +import Toast from '@/components/Toast'; +import BScroll from 'better-scroll'; +import {formatDate} from '@/utils/tools' + +// import PopupPage from '@/components/layout/PopupPage'; +import Input from './Input'; +import Radio from '@/components/layout/Radio'; +import SearchAddress from '@/components/layout/SearchAddress'; + +class CmpModifyBase extends Component { + constructor (...args) { + super(...args); + this.state = { + districtData: [], + keyword: '', + // printerType: null, + printerInfo: [], + printerVendorInfoArr: [], + autoEnableAt: 0, + autoTime: [ + { + label: '将在明天自动营业', + value: 0 + }, + { + label: '将在后天自动营业', + value: 1 + } + ] + }; + this.scroll = null; + this.timeList = businessHours(); + this.uniArr = ['status', 'openTime1', 'closeTime1', 'openTime2', 'closeTime2', 'autoEnableAt']; + this.showTestPrint = this.props.shop.printerVendorID !== 0; + } + async componentDidMount () { + await this.setState(prev => { + return { + ...prev, + ...this.props.shop + }; + }); + // await this.getStore(); + console.log(this.state) + await this.getDistrict(); + // 打印厂商 + let {printerVendorInfo} = this.props.system.cms; + // [ + // {value: 0, label: '无'}, + // {value: 200, label: '飞鹅'}, + // {value: 201, label: '外卖管家'} + // ] + let printerVendorInfoArr = [ + {value: 0, label: '无'} + ]; + for (let attr in printerVendorInfo) { + printerVendorInfoArr.push({ + value: parseInt(attr, 10), + label: printerVendorInfo[attr][0] + }); + } + this.setState({printerVendorInfoArr}); + // 设置打印机信息 + this.setState({ + printerInfo: printerVendorInfo[this.state.printerVendorID] || [] + }); + this.scroll = new BScroll(this.refs.baseWrapper, { + click: true, + stopPropagation: true, + bounce: { + top: false, + bottom: false + } + }); + } + // 获取区信息 + async getDistrict () { + Loading.show(); + try { + // 请求城市列表 level = 2 + let res = await fetchJson('/v2/cms/GetPlaces?parentCode=' + this.state.cityCode); + console.log('区信息', mapPlaces(res)); + await this.setState({districtData: mapPlaces(res)}); + } catch (e) {} finally { + Loading.hide(); + } + } + // 修改属性 + async handleModify (key, val) { + console.log(key, val); + if (this.uniArr.some(item => item.indexOf(key) !== -1)) { + // 单独请求接口 + await this.apiTimeAndStatus(key, val); + } + if (key === 'autoEnableAt') { + let autoEnableAt = val ? formatDate(+new Date() + 2 * 24 * 3600 * 1000, 'YYYY-MM-DD') : formatDate(+new Date() + 1 * 24 * 3600 * 1000, 'YYYY-MM-DD') + this.setState({status: 0, autoEnableAt}) + } else { + await this.setState({[key]: val}); + if (key === 'status' && val !== 0) { + this.setState({autoEnableAt: null}) + } + } + // 网络打印机 + if (key === 'printerVendorID') { + console.log('key == printerVendorID'); + // 清空sn和key + this.setState({ + printerSN: '', + printerKey: '' + }); + this.showTestPrint = false // 切换打印机,隐藏测试打印按钮 + this.refs.printerSN && this.refs.printerSN.fnClear(); + this.refs.printerKey && this.refs.printerKey.fnClear(); + if (val === 0) { + this.setState({ + printerInfo: [ + {value: 0, label: '无'} + ] + }); + } else { + let {printerVendorInfo} = this.props.system.cms; + this.setState({ + printerInfo: printerVendorInfo[val] + }); + } + } + if (key === 'cityCode') { + await this.getDistrict(); + await this.setState({districtCode: this.state.districtData[0].code}); + } + if (key === 'openTime1' && val === 0) this.setState({closeTime1: 0}); + if (key === 'openTime2' && val === 0) this.setState({closeTime2: 0}); + if (key === 'closeTime1' && val === 0) this.setState({openTime1: 0}); + if (key === 'closeTime2' && val === 0) this.setState({openTime2: 0}); + } + // 单独请求接口 + async apiTimeAndStatus (key, val) { + try { + Loading.show(); + let json = {}; + if (this.uniArr.some(item => item.indexOf(key) !== -1)) { + if (key === 'openTime1' && val === 0) json['closeTime1'] = 0 + if (key === 'openTime2' && val === 0) json['closeTime2'] = 0 + if (key === 'closeTime1' && val === 0) json['openTime1'] = 0 + if (key === 'closeTime2' && val === 0) json['openTime2'] = 0 + } + if (key === 'autoEnableAt') { + json.status = 0 + json[key] = val ? formatDate(+new Date() + 2 * 24 * 3600 * 1000, 'YYYY-MM-DD') : formatDate(+new Date() + 1 * 24 * 3600 * 1000, 'YYYY-MM-DD') + } else { + json[key] = val; + } + let form = new FormData(); + form.append('storeID', this.state.id); + form.append('payload', JSON.stringify(json)); + let res = await fetchJson('/v2/store/UpdateStore', { + method: 'PUT', + body: form + }); + console.log(res); + Toast.show('修改成功'); + } catch (e) { + Dialog.show('错误', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } + // 点击开始时间 + fnOpenTime (type) { + if (type === 1) { + this.refs.openTime1.show(); + } else { + this.refs.openTime2.show(); + } + } + // 点击结束时间 + fnCloseTime (type) { + if (type === 1) { + this.refs.closeTime1.show(); + } else { + this.refs.closeTime2.show(); + } + } + // 点击打印机类型 + fnPrinterVendorID () { + this.refs.printerVendorID.show(); + } + // 处理打印机类型 + dealPrinterVendorID (type) { + if (type === 0) { + return '无'; + } else { + let {printerVendorInfo} = this.props.system.cms; + return printerVendorInfo[type][0]; + } + } + // 点击市选择 + async fnCityPick () { + this.refs.cityCode.show(); + } + // 点击区选择 + fnDistrictPick () { + this.refs.districtCode.show(); + } + // 接入地图 + showMap () { + this.refs.SearchAddress.show(); + } + // 选取地址 + addressPick (address) { + console.log(address); + this.setState({address: address.address || address.title, lat: address.location.lat, lng: address.location.lng}); + } + // 删除按钮 + fnDelete () { + Dialog.show('确认', `是否删除 ${this.state.name}`, {}, res => { + // console.log(res); + if (res) { + // 开始删除 + } + }) + } + // 测试打印按钮 + async handleTestPrint () { + console.log('测试打印'); + try { + let form = new FormData(); + form.append('vendorOrderID', 'test'); + form.append('vendorID', this.state.id); + let res = await fetchJson('/v2/order/PrintOrder', { + method: 'PUT', + body: form + }); + console.log('测试打印', res); + if (res.printerStatus === 2) { + Toast.show('测试打印发送成功'); + } else { + Toast.show('打印机状态错误'); + } + } catch (e) { + console.log(e); + } finally {} + } + // 确认前 + async fnSubmit () { + console.log(this.state); + // 存放错误消息 + let arr = []; + if (!isNum(this.state.id)) arr.push('京西门店ID必须是纯数字'); + if (!isEmpty(this.state.name)) arr.push('店铺名称不能为空'); + if (!isTel(this.state.tel1)) arr.push('店长手机不合法'); + if (!this.state.address) arr.push('请输入门店详细地址'); + if (this.state.deliveryRangeType === 3 && !isNum(this.state.deliveryRange)) arr.push('配送半径不合法'); + if (this.state.printerVendorID !== 0 && !isEmpty(this.state.printerSN)) arr.push('请填写' + this.state.printerInfo[1]) + if ((this.state.printerVendorID !== 0 && this.state.printerVendorID !== 202) && !isEmpty(this.state.printerKey)) arr.push('请填写' + this.state.printerInfo[2]) + + if (arr.length > 0) { + Dialog.show('错误', `${arr.join('
')}
`, { + showCancel: false, + confirmText: '好的' + }) + return false; + } else { + // 开始修改门店 + try { + Loading.show(); + let form = new FormData(); + let json = JSON.parse(JSON.stringify(this.state)); + json.id = Number(json.id); + // 清除无用数据 + delete json.districtData; + delete json.keyword; + delete json.createdAt; + delete json.updatedAt; + delete json.lastOperator; + delete json.deletedAt; + delete json.openTime2; + delete json.closeTime2; + delete json.idCardFront; + delete json.idCardBack; + delete json.idCardHand; + delete json.licence; + delete json.licenceCode; + delete json.StoreMaps; + delete json.CourierMaps; + delete json.getVendorStore; + // 单独请求接口 + delete json.status; + delete json.openTime1; + delete json.closeTime1; + delete json.openTime2; + delete json.closeTime2; + form.append('storeID', json.id); + form.append('payload', JSON.stringify(json)); + let res = await fetchJson('/v2/store/UpdateStore', { + method: 'PUT', + body: form + }); + console.log(res); + Dialog.show('成功', `${json.name} 修改成功`, {showCancel: false, confirmText: '知道了'}); + return true; + } catch (e) { + Dialog.show('错误', `${e}`, {showCancel: false}); + return false; + } finally { + Loading.hide(); + } + } + } + // 临时休息 + fnAutoOpen () { + this.refs.pickAutoOpen.show() + } + render() { + let cityData = this.props.system.cityLevel2.find(item => item.code === this.state.cityCode); + // 城市名称 + let cityName = cityData ? cityData.name : '未知' + let districtData = this.state.districtData.find(item => item.code === this.state.districtCode); + // 区名称 + let districtName = districtData ? districtData.name : '未知' + return ( +
+
+ { + this.state.id && ( +
+
基本信息
+ {/* 京西门店ID */} +
+
京西门店ID:
+
{this.state.id}
+
+ {/* 店铺名称 */} +
+
店铺名称:
+ +
+ {/* 店长手机 */} +
+
店长手机:
+ +
+ {/* tel2 */} +
+
tel2:
+ +
+ {/* 营业时间 */} +
+
营业时间段1:
+
+
{dealOpenTime(this.state.openTime1)}
+ +
{dealOpenTime(this.state.closeTime1)}
+
+
+
+
营业时间段2:
+
+
{dealOpenTime(this.state.openTime2)}
+ +
{dealOpenTime(this.state.closeTime2)}
+
+
+ {/* 营业状态 */} +
+
营业状态:
+
+ +
+
+ {/* 临时休息 */} +
+
临时休息:
+ { + this.state.autoEnableAt ? ( +
+ 将在 {formatDate(this.state.autoEnableAt, 'YYYY-MM-DD')} 自动营业 +
+ ) : ( +
+ 选择自动营业时间 +
+ ) + } +
+ {/* 价格审核 */} + {/*
+
价格审核:
+
+ +
+
*/} + {/* 禁用网络打印 */} +
+
禁用网络打印机:
+
+ +
+
+ {/* 打印机类型 */} +
+
打印机品牌:
+
{this.dealPrinterVendorID(this.state.printerVendorID)}
+ { + this.showTestPrint && ( +
测试打印
+ ) + } +
+ {/* 网络打印机SN */} + { + (this.state.printerVendorID !== 0 && this.state.printerVendorInfoArr.length > 0) && ( +
+
{this.state.printerInfo[1]}
+ +
+ ) + } + {/* 网络打印机KEY */} + { + (this.state.printerVendorID !== 0 && this.state.printerInfo[2] !== '不填' && this.state.printerVendorInfoArr.length > 0) && ( +
+
{this.state.printerInfo[2]}
+ +
+ ) + } +
地址及配送范围
+ {/* 所在城市 */} +
+
所在城市:
+
+
{cityName}
+
{districtName}
+
+
+ + {/* 详细地址 */} +
+
详细地址:
+
{this.state.address || '请填写详细地址'}
+ +
+ + {/* 配送范围 */} +
+
配送范围:
+ { + this.state.deliveryRangeType === 3 ? ( +
+ + +
+
+ ) : ( +
+ 规划范围 + {/* */} +
+ ) + } +
+ {/* 配送类型 */} +
+
配送类型:
+
{this.props.system.cms.storeDeliveryType && this.props.system.cms.storeDeliveryType[this.state.deliveryType]}
+
+
+ {/*
删除门店
*/} +
修改信息
+
+ {/*
*/} +
+ ) + } +
+ {/* 时间checkList */} + + + + + + {/* 自动营业时间选择 */} + + {/* 市、区选择 */} + + + {/* 详细地址 */} + +
+ ); + } + +} + +export default connect((state, props) => Object.assign({}, props, state), {})(CmpModifyBase); diff --git a/src/components/pageCmp/CmpModifyGroup.js b/src/components/pageCmp/CmpModifyGroup.js new file mode 100644 index 0000000..ce658ff --- /dev/null +++ b/src/components/pageCmp/CmpModifyGroup.js @@ -0,0 +1,324 @@ +import React, { Component } from 'react'; +import './cmp-modify.scss'; +// import {isTel} from '@/utils/regExp'; +// import classNames from 'classnames'; +import ReactSVG from 'react-svg'; +import fetchJson from '@/utils/fetch'; +import BScroll from 'better-scroll'; + +// import SVGModify from '@/assets/svg/icon-modify.svg'; +import SVGDelete from '@/assets/svg/icon-delete.svg'; +// import SVGPublic from '@/assets/svg/icon-public.svg'; +// import SVGMini from '@/assets/svg/icon-mini.svg'; +import SVGNoGroup from '@/assets/svg/no-group.svg'; + +import Dialog from '@/components/Dialog'; +import Loading from '@/components/Loading'; + +import Promopt from '@/components/Promopt'; +// import Input from './Input'; +import Toast from '@/components/Toast'; +import SearchUser from '@/components/layout/SearchUser'; + +class CmpModifyGroup extends Component { + constructor (...args) { + super(...args); + this.state = { + tel: '' + }; + this.scroll = null; + } + componentDidMount () { + this.scroll = new BScroll(this.refs.groupWrapper, { + click: true, + stopPropagation: true, + bounce: { + top: false, + bottom: false + } + }); + } + // 打开对话框 + // showPromopt (options) { + // let text = ''; + // if (options.type === 'addGroup') text = '添加分组'; + // if (options.type === 'addMembers') text = '添加组员'; + // this.refs.promopt.show(text, async res => { + // if (res) { + // if (!isTel(this.state.tel)) { + // Toast.show('请输入正确的11位手机号', 2); + // } else { + // // 开始执行 + // if (options.type === 'addGroup') { + // // 添加分组 + // await this.reqBindGroup(this.state.tel, options.storeID); + // } else if (options.type === 'addMembers') { + // // 添加组员 + // await this.reqaddMembers(options.parentMobile, this.state.tel); + // } else if (options.type === 'changeMobile') { + // // 修改号码 + // await this.reqChangeMobile(options.curMobile, this.state.tel); + // } + // } + // } + // }); + // } + // 手机号变化 + // async handleModify (key, val) { + // // this.setState + // console.log(key, val); + // await this.setState({[key]: val.trim()}); + // } + // 绑定分组 + // async reqBindGroup (mobile, storeID) { + // try { + // Loading.show(); + // let form = new FormData(); + // form.append('mobile', mobile); + // form.append('storeID', storeID); + // await fetchJson('/v2/user/TmpBindMobile2Store', { + // method: 'PUT', + // body: form + // }); + // Toast.show('添加分组成功'); + // this.props.update && this.props.update(); + // } catch (e) { + // Dialog.show('错误', `${e}`, {showCancel: false}); + // } finally { + // Loading.hide(); + // } + // } + // // 添加组员 + // async reqaddMembers (parentMobile, mobile) { + // try { + // Loading.show(); + // let form = new FormData(); + // form.append('mobile', mobile); + // form.append('parentMobile', parentMobile); + // await fetchJson('/v2/user/TmpAddMobile2Mobile', { + // method: 'PUT', + // body: form + // }); + // Toast.show('添加组员成功'); + // this.props.update && this.props.update(); + // } catch (e) { + // Dialog.show('错误', `${e}`, {showCancel: false}); + // } finally { + // Loading.hide(); + // } + // } + // // 修改号码 + // async reqChangeMobile (curMobile, expectedMobile) { + // try { + // Loading.show(); + // let form = new FormData(); + // form.append('curMobile', curMobile); + // form.append('expectedMobile', expectedMobile); + // await fetchJson('/v2/user/TmpChangeMobile', { + // method: 'PUT', + // body: form + // }); + // Toast.show('修改号码成功'); + // this.props.update && this.props.update(); + // } catch (e) { + // Dialog.show('错误', `${e}`, {showCancel: false}); + // } finally { + // Loading.hide(); + // } + // } + // 添加组员 + addUser = () => { + this.refs.searchUser.show(); + } + // 解绑操作 + async fnUnBind (user) { + console.log(user); + Dialog.show('警告', `是否删除 ${user.name}`, {}, async res => { + if (res) { + try { + console.log(this.props.storeID); + // let form = new FormData(); + // form.append('mobile', tel); + // Loading.show(); + // await fetchJson('/v2/user/TmpUnbindMobile', { + // method: 'PUT', + // body: form + // }); + await fetchJson(`/v2/user2/DeleteUsers4Role?roleName=StoreBoss&storeID=${this.props.storeID}&userIDs=${JSON.stringify([user.userID])}`, { + method: 'DELETE' + }); + Toast.show('删除成功'); + this.props.update && this.props.update(); + } catch (e) { + Dialog.show('错误', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } + }); + } + // 选择用户 + handlePick = async (user) => { + console.log('选择的用户', user); + try { + Loading.show(); + let form = new FormData(); + form.append('roleName', 'StoreBoss'); + form.append('storeID', this.props.storeID); + form.append('userIDs', JSON.stringify([user.userID])); + await fetchJson('/v2/user2/AddUsers4Role', { + method: 'POST', + body: form + }) + Toast.show('添加成功'); + this.props.update && this.props.update(); + } catch (e) { + console.error(e); + Dialog.show('错误', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } + // 添加分组 + // bindGroup () { + // // console.log(this.props.storeID); + // this.showPromopt({ + // type: 'addGroup', + // storeID: this.props.storeID + // }); + // } + // 添加组员 + // bindMembers (parentMobile) { + // this.showPromopt({ + // type: 'addMembers', + // parentMobile + // }); + // } + // 修改号码 + // modifyTel (curTel) { + // console.log(curTel); + // this.showPromopt({ + // type: 'changeMobile', + // curMobile: curTel + // }); + // } + // 查询用户 + async getOneUser (mobile) { + try { + let {totalCount, data} = await fetchJson(`/v2/user2/GetUsers?userType=0&keyword=${mobile}`) + return totalCount ? data[0] : null + } catch (e) { + throw e + } + } + // 注册 + async handleRegister (mobile) { + try { + let res = fetchJson(`/v2/user2/RegisterUser`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: `payload=${JSON.stringify({ + userID2: mobile, + mobile, + name: mobile + })}` + }) + return res + } catch (e) { + throw e + } + } + // 注册形式添加用户 + registerUser = () => { + this.refs.promopt.show('请输入手机号', async res => { + if (res) { + const mobile = this.refs.telInput.value.trim() + if (/^\d{11}$/.test(mobile)) { + console.log(mobile) + try { + Loading.show() + let user = await this.getOneUser(mobile) + if (user) { + // 用户存在,直接绑定 + console.log(user) + await this.handlePick(user) + } else { + // 用户不存在,注册 + await this.handleRegister(mobile) + let newUser = await this.getOneUser(mobile) + await this.handlePick(newUser) + } + } catch (e) { + Dialog.show('错误', `${e}`, {showCancel: false}); + } finally { + Loading.hide() + } + } else { + Toast.show('手机格式错误') + } + } + }) + } + render() { + return ( +
+ {/* 添加分组按钮 */} +
+
添加用户
+
注册用户
+
+ {/* 分组显示 */} +
+
+ { + this.props.users.length > 0 && this.props.users.map(user => ( +
+
+
+ {/* 昵称 */} +
{user.name || '无昵称'}
+ {/* 手机号 */} +
tel:{user.mobile}
+ {/* 绑定标识 */} + {/*
+ + +
*/} +
+
+ {/* 操作 */} +
+ +
+ {/*
+ +
*/} +
+
+
+ )) + } + {/* 没有分组 */} +
+ { + this.props.users.length === 0 && ( +
+ +
该门店还没有分组
+
+ ) + } +
+ + + + +
+ ); + } + +} + +export default CmpModifyGroup; diff --git a/src/components/pageCmp/CmpModifyMaps.js b/src/components/pageCmp/CmpModifyMaps.js new file mode 100644 index 0000000..dab69fa --- /dev/null +++ b/src/components/pageCmp/CmpModifyMaps.js @@ -0,0 +1,90 @@ +import React, { Component } from 'react'; +import './cmp-modify.scss'; +// import fetchJson from '@/utils/fetch'; +// import Loading from '@/components/Loading'; +// import Dialog from '@/components/Dialog'; + +import VendorCell from './VendorCell'; + +class CmpModifyMaps extends Component { + constructor (...args) { + super(...args); + this.state = { + // StoreMaps: '', + // CourierMaps: '' + }; + } + async componentDidMount () { + await this.setState({...this.props.shop}); + console.log(this.state); + // await this.getVendorMaps(); + } + componentWillReceiveProps (newProps) { + console.log('new Props', newProps); + this.setState({...newProps.shop}); + } + // 获取平台门店绑定情况 + // async getVendorMaps () { + // try { + // Loading.show(); + // let res = await fetchJson('/v2/store/GetStoreVendorMaps?storeID=' + this.props.shop.id); + // console.log('平台门店绑定情况', res); + // } catch (e) { + // Dialog.show('错误', e, {showCancel: false}); + // } finally { + // Loading.hide(); + // } + // } + render() { + return ( +
+ { + this.state.id && ( +
+
平台门店绑定情况
+ {/* 京东 */} + + {/* 美团 */} + + {/* 饿百 */} + + {/* 微盟 */} + {/* */} +
专送门店绑定情况
+ {/* 达达 */} + + {/* 美团 */} + +
+ ) + } +
+ ); + } +} + +export default CmpModifyMaps; diff --git a/src/components/pageCmp/CreateStore.js b/src/components/pageCmp/CreateStore.js new file mode 100644 index 0000000..dfde2a3 --- /dev/null +++ b/src/components/pageCmp/CreateStore.js @@ -0,0 +1,530 @@ +import React, { Component } from 'react'; +import {connect} from 'react-redux'; +import fetchJson from '@/utils/fetch'; +import Loading from '@/components/Loading'; +import './create-store.scss'; +import {isNum, isEmpty, isTel, isTel2} from '@/utils/regExp'; +import {dealOpenTime, businessHours} from '@/utils/projectTools'; +import Dialog from '@/components/Dialog'; +import CheckList from '@/components/layout/CheckList'; +import {mapPlaces} from '@/utils/mapData'; +import ReactSVG from 'react-svg' +import SVGGo from '@/assets/svg/icon-go.svg'; +import classNames from 'classnames'; +import BScroll from 'better-scroll'; + +import PopupPage from '@/components/layout/PopupPage'; +import Input from './Input'; +import Radio from '@/components/layout/Radio'; +import SearchAddress from '@/components/layout/SearchAddress'; +import PickInput from '@/components/layout/PickInput'; + +class CreateStore extends Component { + constructor (...args) { + super(...args); + this.state = { + id: 0, + name: '', + tel1: '', + tel2: '', + openTime1: 800, + closeTime1: 1900, + openTime2: 0, + closeTime2: 0, + status: -1, + changePriceType: 0, + cityCode: 510100, + districtCode: null, + address: '', + deliveryRangeType: 3, + deliveryRange: '3000', + lng: '', + lat: '', + brandID:9,//品牌默认无 9 + payPercentage:20,//结算比例 + // 区数据 + districtData: [], + keyword: '', + // 获取平台数据 + vendorID: `0,${this.props.system.vendorOrgCode[0][0]}`, + vendorStoreID: '', + printerVendorID: 0, + printerSN: '', + printerKey: '', + printerDisabled: 0, + printerInfo: [], + printerVendorInfoArr: [] + } + this.scroll = null; + this.timeList = businessHours(); + } + async componentDidMount () { + await this.getDistrict(); + // 打印厂商 + let {printerVendorInfo} = this.props.system.cms; + console.log('printerVendorInfo', printerVendorInfo); + // [ + // {value: 0, label: '无'}, + // {value: 200, label: '飞鹅'}, + // {value: 201, label: '外卖管家'} + // ] + let printerVendorInfoArr = [ + {value: 0, label: '无'} + ]; + for (let attr in printerVendorInfo) { + printerVendorInfoArr.push({ + value: parseInt(attr, 10), + label: printerVendorInfo[attr][0] + }); + } + this.setState({printerVendorInfoArr}); + } + componentDidUpdate () { + if (this.scroll) { + this.scroll.refresh(); + } else { + if (this.refs.createStoreWrapper) { + this.scroll = new BScroll(this.refs.createStoreWrapper, { + click: true, + stopPropagation: true, + bounce: { + top: false, + bottom: false + } + }); + } + } + } + // 请求次级数据 + async getDistrict () { + Loading.show(); + try { + // 请求城市列表 level = 2 + let res = await fetchJson('/v2/cms/GetPlaces?parentCode=' + this.state.cityCode); + console.log('区信息', mapPlaces(res)); + await this.setState({districtData: mapPlaces(res)}); + await this.setState({districtCode: this.state.districtData[0].code}); + } catch (e) {} finally { + Loading.hide(); + } + } + // async getBrandList () { + // Loading.show(); + // try { + // // 请求城市列表 level = 2 + // let res = await fetchJson('/v2/store/GetBrands'); + // console.log('品牌', res); + // } catch (e) {} finally { + // Loading.hide(); + // } + // } + // 修改属性 + async handleModify (key, val) { + console.log(key, val); + console.log('this.state.printerInfo', this.state.printerInfo); + await this.setState({[key]: val}); + if (key === 'printerVendorID') { + this.setState({ + printerSN: '', + printerKey: '' + }); + this.refs.printerSN && this.refs.printerSN.fnClear(); + this.refs.printerKey && this.refs.printerKey.fnClear(); + if (val === 0) { + this.setState({ + printerInfo: [ + {value: 0, label: '无'} + ] + }); + } else { + let {printerVendorInfo} = this.props.system.cms; + this.setState({ + printerInfo: printerVendorInfo[val] + }); + } + } + if (key === 'cityCode') { + await this.getDistrict(); + } + if (key === 'openTime1' && val === 0) this.setState({closeTime1: 0}); + if (key === 'openTime2' && val === 0) this.setState({closeTime2: 0}); + if (key === 'closeTime1' && val === 0) this.setState({openTime1: 0}); + if (key === 'closeTime2' && val === 0) this.setState({openTime2: 0}); + } + // 点击开始时间 + fnOpenTime (type) { + if (type === 1) { + this.refs.openTime1.show(); + } else { + this.refs.openTime2.show(); + } + } + fnBrandChoose(){ + this.refs.openTime2.show(); + } + // 点击结束时间 + fnCloseTime (type) { + if (type === 1) { + this.refs.closeTime1.show(); + } else { + this.refs.closeTime2.show(); + } + } + // 点击打印机类型 + fnPrinterVendorID () { + this.refs.printerVendorID.show(); + } + // 处理打印机类型 + dealPrinterVendorID (type) { + // console.log(type); + if (type === 0) { + return '无'; + } else { + let {printerVendorInfo} = this.props.system.cms; + return printerVendorInfo[type][0]; + } + } + // 点击市选择 + async fnCityPick () { + this.refs.cityCode.show(); + } + // 点击区选择 + fnDistrictPick () { + this.refs.districtCode.show(); + } + // 接入地图 + showMap () { + this.refs.SearchAddress.show(); + } + // 选取地址 + addressPick (address) { + console.log(address); + this.setState({address: address.address || address.title, lat: address.location.lat, lng: address.location.lng}); + } + // 点击半径 + rClick () { + // console.log('点击半径', this.refs.PopupPage.toBottom()) + } + // 校验 + fnCheckData () { + let arr = []; + if (!isNum(this.state.id)) arr.push('京西门店ID必须是纯数字'); + if (!isEmpty(this.state.name)) arr.push('店铺名称不能为空'); + if (!isTel(this.state.tel1)) arr.push('店长手机不合法'); + if (!this.state.address) arr.push('请输入门店详细地址'); + if (this.state.deliveryRangeType === 3 && !isNum(this.state.deliveryRange)) arr.push('配送半径不合法'); + if (this.state.printerVendorID !== 0 && !isEmpty(this.state.printerSN)) arr.push('请填写' + this.state.printerInfo[1]) + if ((this.state.printerVendorID !== 0 && this.state.printerVendorID !== 202) && !isEmpty(this.state.printerKey)) arr.push('请填写' + this.state.printerInfo[2]) + if (arr.length > 0) { + Dialog.show('错误', `${arr.join('
')}
`, { + showCancel: false, + confirmText: '好的' + }) + return false; + } else { + return true; + } + } + // 确认前 + async prevConfirm () { + console.log(this.state); + if (this.fnCheckData()) { + // 开始创建门店 + if (Number(this.state.id) !== 0) { + Dialog.show('注意', `京西门店ID正常都应该自动生成(京西门店ID为0时),您确认必须要设置成${this.state.id}?`, {}, async (res) => { + if (res) { + let res = await this.apiCreateStore(); + return res; + } + }) + } else { + let res = await this.apiCreateStore(); + return res; + } + } else {} + } + // 创建门店接口 + async apiCreateStore () { + try { + Loading.show(); + let form = new FormData(); + let json = JSON.parse(JSON.stringify(this.state)); + json.id = Number(json.id); + delete json.districtData; + delete json.keyword; + form.append('payload', JSON.stringify(json)); + let res = await fetchJson('/v2/store/CreateStore', { + method: 'POST', + body: form + }); + console.log(res); + Dialog.show('成功', `${json.name} 创建成功`, {showCancel: false, confirmText: '知道了'}); + return true; + } catch (e) { + Dialog.show('错误', `${e}`, {showCancel: false}); + return false; + } finally { + Loading.hide(); + } + } + // vendorID 修改 + handleVendorID = val => { + console.log(val) + this.setState({vendorID: val}); + } + // vendorStoreID 修改 + handleVendorStoreID = val => { + this.setState({vendorStoreID: val}); + } + // 获取远端门店数据 + handleGetVendorStore = async () => { + console.log(this.state.vendorID, this.state.vendorStoreID); + let store = await this.getVendorStore(); + console.log(store); + if (!store) return false; + await this.setState({ + // id: store.id, + name: store.name, + tel1: store.tel1, + tel2: store.tel2, + openTime1: store.openTime1, + closeTime1: store.closeTime1, + status: store.status, + changePriceType: store.changePriceType, + cityCode: store.cityCode, + districtCode: store.districtCode, + address: store.address, + // deliveryRangeType: 3, + // deliveryRange: store.deliveryRangeType === 3, + lng: store.lng, + lat: store.lat, + }); + // 单独处理配送范围 + if (store.deliveryRangeType === 3) this.setState({deliveryRange: store.deliveryRange}); + console.log(this.state) + this.refs.name.checkData(); + this.refs.deliveryRange.checkData(); + this.refs.tel1.checkData(); + this.refs.tel2.checkData(); + } + // 获取远端门店信息 + async getVendorStore () { + try { + Loading.show(); + let res = await fetchJson(`/v2/store/GetVendorStore?vendorStoreID=${this.state.vendorStoreID}&vendorID=${this.state.vendorID.split(',')[0]}&vendorOrgCode=${this.state.vendorID.split(',')[1]}`); + // console.log(res); + // 重新拉取区信息 + let district = await fetchJson('/v2/cms/GetPlaces?parentCode=' + res.cityCode); + console.log('区信息', mapPlaces(district)); + await this.setState({districtData: mapPlaces(district)}); + // await this.setState({districtCode: this.state.districtData[0].code}); + return res; + } catch (e) { + Dialog.show('错误', `${e}`, { + showCancel: false, + confirmText: '好的' + }); + } finally { + Loading.hide(); + } + } + dealVendorOrgCode = () => { + let vendorName = this.props.system.cms.vendorName || {}; + const {vendorOrgCode} = this.props.system + console.log(vendorOrgCode) + let arr = [] + for (let key in vendorOrgCode) { + (vendorOrgCode[key] || []).forEach(item => { + arr.push({ + id: `${key},${item}`, + name: vendorName[key].substring(0, 1) + item + }) + }) + } + return arr + // [ + // {id: 0, name: vendorName['0']}, + // {id: 1, name: vendorName['1']}, + // {id: 3, name: vendorName['3']} + // ] + } + render () { + let cityData = this.props.system.cityLevel2.find(item => item.code === this.state.cityCode); + // 城市名称 + let cityName = cityData ? cityData.name : '未知' + let districtData = this.state.districtData.find(item => item.code === this.state.districtCode); + // 区名称 + let districtName = districtData ? districtData.name : '未知'; + return ( +
+ +
+
+ {/* 从平台拉取数据 */} +
从平台获取数据
+
+ +
+
基本信息
+ {/* 京西门店ID */} +
+
京西门店ID:
+ +
0表示自动生成缺省ID, 特殊情况可手动设置
+
+ {/* 店铺名称 */} +
+
店铺名称:
+ +
+ {/* 店长手机 */} +
+
店长手机:
+ +
+ {/* tel2 */} +
+
tel2:
+ +
+ {/* 营业时间 */} +
+
营业时间段1:
+
+
{dealOpenTime(this.state.openTime1)}
+ +
{dealOpenTime(this.state.closeTime1)}
+
+
+
+
营业时间段2:
+
+
{dealOpenTime(this.state.openTime2)}
+ +
{dealOpenTime(this.state.closeTime2)}
+
+
+ + {/* 营业状态 */} +
+
营业状态:
+
+ +
+
+ {/* 价格审核 */} + {/*
+
价格审核:
+
+ +
+
*/} + {/* 禁用网络打印 */} +
+
禁用网络打印机:
+
+ +
+
+ {/* 打印机类型 */} +
+
打印机品牌:
+
{this.dealPrinterVendorID(this.state.printerVendorID)}
+
+
+ {/* 网络打印机SN */} + { + this.state.printerVendorID !== 0 && ( +
+
{this.state.printerInfo[1]}
+ +
+ ) + } + {/* 网络打印机KEY */} + { + (this.state.printerVendorID !== 0 && this.state.printerInfo[2] !== '不填') && ( +
+
{this.state.printerInfo[2]}
+ +
+ ) + } +
地址及配送范围
+ {/* 所在城市 */} +
+
所在城市:
+
+
{cityName}
+
{districtName}
+
+
+ + + {/* 详细地址 */} +
+
详细地址:
+
{this.state.address || '请填写详细地址'}
+ +
+ {/* 配送范围 */} +
+
配送范围:
+
+ + +
+
+
+
+
+
+ {/* 时间checkList */} + + + + + + {/* 市、区选择 */} + + + {/* 详细地址 */} + +
+
+ ); + } + +} + +export default connect((state, props) => Object.assign({}, props, state), {})(CreateStore); diff --git a/src/components/pageCmp/Input.js b/src/components/pageCmp/Input.js new file mode 100644 index 0000000..57fa0fb --- /dev/null +++ b/src/components/pageCmp/Input.js @@ -0,0 +1,94 @@ +import React, { Component } from 'react'; +import classNames from 'classnames'; +// import Toast from '@/components/Toast'; + +/* + check={isNum} 校验函数 + value={this.state.id} 默认值 + placeholder="" + errText="京西门店ID必须是纯数字" 报错提示 + fnBlur={this.idBlur.bind(this, key)} 失去焦点处理函数 可以为空 +*/ + +class Input extends Component { + constructor (...args) { + super(...args); + this.state = { + wrongShow: false + }; + } + componentDidMount () { + // console.log('校······验', this.props.value) + // 校验一次defaultValue + this.props.check && this.setState({wrongShow: !this.props.check(this.props.value)}); + } + // 当props或者state变化时才更新 + // shouldComponentUpdate (nextProps, nextState) { + // // console.log(JSON.stringify(nextProps) !== JSON.stringify(this.props) || JSON.stringify(nextState) !== JSON.stringify(this.state)) + // return JSON.stringify(nextProps) !== JSON.stringify(this.props) || JSON.stringify(nextState) !== JSON.stringify(this.state); + // } + // 更新触发校验 + // componentDidUpdate () { + // this.props.check && this.setState({wrongShow: !this.props.check(this.props.value)}); + // } + // id变化 + storeIDChange (e) { + // console.log(e.target.value); + this.props.check && this.setState({wrongShow: !this.props.check(e.target.value.trim())}); + + if (this.props.check) { + this.setState({wrongShow: !this.props.check(e.target.value.trim())}); + if (this.props.check(e.target.value.trim())) { + this.props.fnBlur && this.props.fnBlur(e.target.value.trim()); + } else { + this.setState({wrongShow: true}); + this.props.fnBlur && this.props.fnBlur(e.target.value.trim()); + } + } + this.props.fnBlur && this.props.fnBlur(e.target.value.trim()); + } + // id失去焦点 + storeIDBlue (e) { + // console.log(e.target.value) + // !isNum(e.target.value) ? Toast.show('京西门店ID需要纯数字') : this. + if (this.props.check) { + if (this.props.check(e.target.value.trim())) { + this.props.fnBlur && this.props.fnBlur(e.target.value.trim()); + } else { + this.setState({wrongShow: true}); + this.props.fnBlur && this.props.fnBlur(e.target.value.trim()); + + // Toast.show(this.props.errText); + } + } else { + this.props.fnBlur && this.props.fnBlur(e.target.value.trim()); + } + } + // 校验 + checkData () { + // console.log('校验'); + this.refs.input.value = this.props.value; + this.props.check && this.setState({wrongShow: !this.props.check(this.props.value)}); + } + // 清除 + fnClear () { + this.refs.input.value = ''; + } + render() { + return ( + + ); + } + +} + +export default Input; diff --git a/src/components/pageCmp/ModifyStore.js b/src/components/pageCmp/ModifyStore.js new file mode 100644 index 0000000..f1abd2c --- /dev/null +++ b/src/components/pageCmp/ModifyStore.js @@ -0,0 +1,141 @@ +import React, { Component } from 'react'; +import './modify-store.scss'; +import fetchJson from '@/utils/fetch'; + +import SVGStore from '@/assets/svg/icon-store.svg'; +import PopupPage from '@/components/layout/PopupPage'; +import Dialog from '@/components/Dialog'; +import Loading from '@/components/Loading'; +import Tabs from '@/components/layout/Tabs'; +// import BScroll from 'better-scroll'; +import CmpModifyBase from '@/components/pageCmp/CmpModifyBase' +import CmpModifyMaps from '@/components/pageCmp/CmpModifyMaps' +import CmpModifyGroup from '@/components/pageCmp/CmpModifyGroup' + +class ModifyStore extends Component { + constructor (...args) { + super(...args); + this.state = { + shop: {}, + users: [], + current: 0 + }; + // this.scroll = null; + } + async componentDidMount () { + await this.getStore(); + await this.getStoreUsers(); + // 增加滚动 + // this.scroll = new BScroll(this.refs.modifyWrapper, { + // // click: true, + // bounce: { + // top: false, + // bottom: false + // } + // }); + } + // 获取门店数据 + async getStore () { + try { + Loading.show(); + let {stores} = await fetchJson('/v2/store/GetStores?storeID=' + this.props.shop.id); + console.log('当前门店', stores[0]); + await this.setState({shop: stores[0]}); + } catch (e) { + Dialog.show('错误', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } + // 获取分组数据 + async getStoreUsers () { + try { + Loading.show(); + let res = await fetchJson('/v2/user2/GetRoleUserList?roleName=StoreBoss&storeID=' + this.props.shop.id); + if (res) { + let users = await this.apiGetUserList(res); + this.setState({users}); + } else { + this.setState({users: []}); + } + } catch (e) { + Dialog.show('错误', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } + // 获取所有用户列表 + apiGetUserList = async (userIDs = [], pageSize = 50) => { + try { + let {data} = await fetchJson(`/v2/user2/GetUsers?userIDs=${JSON.stringify(userIDs)}&userType=14&offset=0&pageSize=50`) + // console.log('获取的用户', totalCount, data) + console.log('用户信息', data); + return data || [] + } catch (e) { + console.error(e) + Dialog.show('错误', `${e}`, {showCancel: false}); + } + } + // tabs切换 + async fnSwitch (index) { + // console.log(index); + this.setState({current: index}); + if (index !== 2) { + await this.getStore(); + } else { + await this.getStoreUsers(); + } + } + componentDidUpdate () { + console.log('updated') + // this.scroll = new BScroll(this.refs.modifyWrapper, { + // // click: true, + // // stopPropagation: true, + // bounce: { + // top: false, + // bottom: false + // } + // }); + // this.scroll && this.scroll.refresh(); + } + render() { + return ( +
+ + + { + this.state.current === 0 && this.state.shop.id &&
+ +
+ } + { + this.state.current === 1 && this.state.shop.id &&
+ +
+ } + { + this.state.current === 2 && this.state.shop.id &&
+ +
+ } +
+
+ ); + } + +} + +export default ModifyStore; diff --git a/src/components/pageCmp/MoreSearch.js b/src/components/pageCmp/MoreSearch.js new file mode 100644 index 0000000..6b49b2d --- /dev/null +++ b/src/components/pageCmp/MoreSearch.js @@ -0,0 +1,206 @@ +import React, { Component } from 'react'; +import PopupPage from '@/components/layout/PopupPage'; +import {connect} from 'react-redux'; +import {setStoreSearch} from '@/store/actions'; +import './more-search.scss'; +import ReactSVG from 'react-svg' +import SVGGo from '@/assets/svg/icon-go.svg'; + +import Radio from '@/components/layout/Radio'; +import CheckBox from '@/components/layout/CheckBox'; +import CheckList from '@/components/layout/CheckList'; + +class MoreSearch extends Component { + constructor (...args) { + super(...args); + this.state = {}; + this.vendorList = [ + {id: 0, name: '京东'}, + {id: 1, name: '美团'}, + {id: 3, name: '饿百'}, + {id: 9, name: '京西'}, + // {id: 11, name: '微盟'} + ]; + this.courierList = [ + {id: 101, name: '达达'}, + {id: 102, name: '美团'} + ]; + this.fields = [ + {value: 0, text: '不限定'}, + {value: 1, text: '已绑定'}, + {value: -1, text: '未绑定'} + ]; + this.fieldsStore = [ + {value: '0', text: '不限定'}, + {value: '1', text: '已绑定'}, + {value: '-1', text: '未绑定'} + ]; + } + async componentDidMount () { + await this.setState({ + ...this.props.search.storeSearch + }); + // console.log(this.props.system.vendorOrgCode) + } + // 平台门店绑定情况切换 + vendorStoreCondsClick (val, id) { + console.log(val, id); + this.setState({ + vendorStoreConds: { + ...this.state.vendorStoreConds, + [id]: val + } + }); + } + // 平台门店绑定情况切换 + courierStoreCondsClick (val, id) { + // console.log(val, id); + this.setState({ + courierStoreConds: { + ...this.state.courierStoreConds, + [id]: val + } + }); + } + // 点击确定 + async fnConfirm () { + console.log(this.state); + await this.props.setStoreSearch(this.state) + this.props.fnConfirm && this.props.fnConfirm(); + } + // 营业状态修改 + changeStatuss (val) { + // console.log(val); + let arr = Object.assign([], this.state.statuss); + // 查找元素 + let n = arr.indexOf(val); + if (n === -1) { + // 没找到,添加 + arr.push(val); + } else { + // 找到了,去除 + if (arr.length !== 1) arr.splice(n, 1); + } + this.setState({ + statuss: arr + }); + } + // 城市改变 + changeCity () { + console.log('城市改变'); + this.refs.checklist.show(); + } + // 选择城市 + cityPick (val) { + console.log(val); + this.setState({placeID: Number(val)}); + } + dealVendorOrgCode = (list, vendor) => { + let arr = JSON.parse(JSON.stringify(list)) + const {vendorOrgCode} = this.props.system + // console.log(arr, vendor, vendorOrgCode, ) + let account = vendorOrgCode[vendor.id] + if (account && account.length > 1) { + account.forEach(item => { + arr.push({ + value: item, + text: item + }) + }) + } + return arr + } + // 渲染 + render() { + let storeSearch = this.state; + let placeName = storeSearch.placeID ? this.props.system.cityLevel2.find(item => item.code === storeSearch.placeID).name : '全国'; + return ( +
+ { + // 存在才渲染 + storeSearch.vendorStoreConds && ( + + {/* 城市 */} +
+
城市:
+
{placeName}
+ +
+ {/* 营业状态 */} +
+
状态:
+
+ +
+
+ {/* 平台门店绑定情况 */} +
平台门店绑定情况
+ {/* cell */} + { + this.vendorList.map((item, index) => ( +
+
{item.name}:
+
+ +
+
+ )) + } + {/* cell */} + {/* 专送门店绑定情况 */} +
专送门店绑定情况
+ { + this.courierList.map((item, index) => ( +
+
{item.name}:
+
+ +
+
+ )) + } +
+ ) + } + {/* checkList */} + +
+ ); + } + +} + +export default connect((state, props) => Object.assign({}, props, state), {setStoreSearch})(MoreSearch); diff --git a/src/components/pageCmp/StoreCell.js b/src/components/pageCmp/StoreCell.js new file mode 100644 index 0000000..b929783 --- /dev/null +++ b/src/components/pageCmp/StoreCell.js @@ -0,0 +1,39 @@ +import React, { Component } from 'react'; +import classNames from 'classnames' +import './store-cell.scss'; +import {connect} from 'react-redux'; + +class StoreCell extends Component { + constructor (...args) { + super(...args); + this.storeStatus = { + '-1': '长期休息', + '0': '临时休息', + '1': '营业', + '-2': '禁用' + } + } + render() { + let store = this.props.shop; + // 门店所在城市 + let cityData = this.props.system.cityLevel2.find(item => item.code === store.cityCode); + let cityName = cityData ? cityData.name : '未知' + return ( +
+
+ {/*
{store.id}
*/} +
+ {store.name} + ({cityName}) + +
+ {/* 营业状态 */} +
{this.storeStatus[store.status]}
+
+
+ ); + } + +} + +export default connect((state, props) => Object.assign({}, props, state), {})(StoreCell); diff --git a/src/components/pageCmp/VendorCell.js b/src/components/pageCmp/VendorCell.js new file mode 100644 index 0000000..f86ccba --- /dev/null +++ b/src/components/pageCmp/VendorCell.js @@ -0,0 +1,462 @@ +import React, {Component} from 'react'; +import classNames from 'classnames'; +import {connect} from 'react-redux'; +import ReactSVG from 'react-svg'; +import {isNum} from '@/utils/regExp'; +import Loading from '@/components/Loading'; +import fetchJson from '@/utils/fetch'; + +import SVGInsert from '@/assets/svg/icon-insert.svg'; +import SVGDelete from '@/assets/svg/icon-delete.svg'; +import SVGUpdate from '@/assets/svg/icon-update.svg'; + +import PopupPage from '@/components/layout/PopupPage'; +import CheckBoxSelf from '@/components/layout/CheckBoxSelf'; +import Radio from '@/components/layout/Radio'; +import Input from '@/components/pageCmp/Input'; +import Dialog from '@/components/Dialog'; +import CheckList from '@/components/layout/CheckList'; + +/* + shop={this.state} + plant={{name: 'StoreMaps', id: 1}} +*/ + +class VendorCell extends Component { + constructor (...args) { + super(...args); + let vendorOrgCodeArr = this.props.system.vendorOrgCode[this.props.plant.id] + this.reset = { + vendorStoreID: '', + status: 1, // 开关店状态 + vendorID: this.props.plant.id, + autoPickup: 1, // 是否自动拣货 + deliveryCompetition: 1, // 是否配送竞争 + pricePercentage: 100, + isSync: 1, // 是否同步 + popupShow: false, + isEditor: false, + vendorOrgCode: this.props.plant.id < 100 ? vendorOrgCodeArr[0] : '' + }; + this.state = { + vendorStoreID: '', + status: 1, // 开关店状态 + vendorID: this.props.plant.id, + autoPickup: 1, // 是否自动拣货 + deliveryCompetition: 1, // 是否配送竞争 + pricePercentage: 100, + isSync: 1, // 是否同步 + popupShow: false, + isEditor: false, + vendorOrgCode: this.props.plant.id < 100 ? vendorOrgCodeArr[0] : '' + }; + this.vendorOrgCodeList = [] + if (vendorOrgCodeArr) { + vendorOrgCodeArr.forEach(item => { + this.vendorOrgCodeList.push({ + code: item, + name: item + }) + }) + } + } + componentDidMount () { + console.log('vendorOrgCode', this.props.system.vendorOrgCode, this.props.plant.id) + } + // 远程查询平台门店信息 + async searchVendorStore () { + try { + Loading.show(); + let res = await fetchJson(`/v2/store/GetVendorStore?vendorStoreID=${this.state.vendorStoreID}&vendorID=${this.state.vendorID}&vendorOrgCode=${this.state.vendorOrgCode}`); + console.log(res); + Dialog.show('平台门店查询成功', `

${res.cityName}${res.districtName}${res.name}

${res.address}

`, {showCancel: false}); + } catch (e) { + // console.error(e); + Dialog.show('查询错误', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } + // 获取平台门店绑定情况 + async getVendorMaps () { + try { + Loading.show(); + let res = await fetchJson('/v2/store/GetStoreVendorMaps?storeID=' + this.props.shop.id + '&vendorID=' + this.state.vendorID); + await this.setState({ + ...this.state, + ...res[0] + }); + console.log('平台门店绑定情况', res[0]); + + } catch (e) { + Dialog.show('错误', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } + // 获取专送门店绑定情况 + async getCourierMaps () { + try { + Loading.show(); + let res = await fetchJson('/v2/store/GetStoreCourierMaps?storeID=' + this.props.shop.id + '&vendorID=' + this.state.vendorID); + await this.setState({ + ...this.state, + ...res[0] + }); + console.log('平台门店绑定情况', res[0]); + + } catch (e) { + Dialog.show('错误', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } + // 绑定 + fnBind () { + console.log('绑定'); + this.setState({popupShow: true}); + } + // 删除绑定 + async fnDelete () { + console.log('删除'); + Dialog.show('警告', `是否解除${this.props.shop.name}-${this.props.system.cms.vendorName[this.props.plant.id]}的绑定`, {}, async (res) => { + if (res) { + try { + Loading.show(); + if (this.state.vendorID < 100) { + await fetchJson(`/v2/store/DeleteStoreVendorMap?storeID=${this.props.shop.id}&vendorID=${this.state.vendorID}`, {method: 'DELETE'}); + } else { + await fetchJson(`/v2/store/DeleteStoreCourierMap?storeID=${this.props.shop.id}&vendorID=${this.state.vendorID}`, {method: 'DELETE'}); + } + Dialog.show('成功', `解绑成功`, {showCancel: false}); + // 更新数据 + this.props.update && this.props.update(); + } catch (e) { + Dialog.show('解绑失败', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } + }) + } + // 点击编辑 + async fnUpdate () { + console.log('编辑'); + if (this.state.vendorID < 100) { + // 平台门店绑定 + await this.getVendorMaps(); + } else { + // 专送门店绑定 + await this.getCourierMaps(); + } + console.log(this.state); + this.setState({popupShow: true, isEditor: true}); + } + // 弹出框返回 + fnReturn () { + // this.setState({popupShow: false, isEditor: false}); + console.log('重置数据'); + this.setState({...this.reset}); + } + // 弹出框验证 新增、编辑 + async fnPrevConfirm () { + console.log(this.state); + // 存放错误消息 + let arr = []; + if (!isNum(this.state.vendorStoreID)) arr.push('平台门店ID必须是纯数字'); + if (!isNum(this.state.pricePercentage)) arr.push('平台调价必须是纯数字'); + if (arr.length > 0) { + Dialog.show('错误', `${arr.join('
')}
`, { + showCancel: false, + confirmText: '好的' + }) + return false; + } else { + // 请求接口绑定门店 + console.log('开始保存'); + try { + Loading.show(); + let form = new FormData(); + form.append('storeID', this.props.shop.id); + form.append('vendorID', this.state.vendorID); + if (this.state.isEditor) { + // ----- 编辑 ----- + if (this.state.vendorID < 100) { + form.append('vendorOrgCode', this.state.vendorOrgCode) + // 平台编辑 + let json = { + autoPickup: this.state.autoPickup, + deliveryCompetition: this.state.deliveryCompetition, + isSync: this.state.isSync, + pricePercentage: this.state.pricePercentage, + status: this.state.status, + syncStatus: this.state.syncStatus, + vendorStoreID: this.state.vendorStoreID, + vendorOrgCode: this.state.vendorOrgCode + } + form.append('payload', JSON.stringify(json)); + await fetchJson('/v2/store/UpdateStoreVendorMap', { + method: 'PUT', + body: form + }); + } else { + // 专送编辑 + let json = { + vendorStoreID: this.state.vendorStoreID, + status: this.state.status + } + form.append('payload', JSON.stringify(json)); + await fetchJson('/v2/store/UpdateStoreCourierMap', { + method: 'PUT', + body: form + }); + } + + // console.log(res); + Dialog.show('成功', `修改成功`, {showCancel: false}); + return true; + } else { + // ----- 新增 ----- + if (this.state.vendorID < 100) { + form.append('vendorOrgCode', this.state.vendorOrgCode) + // 新增平台 + let json = { + vendorStoreID: this.state.vendorStoreID, + status: this.state.status, + autoPickup: this.state.Pickup, + deliveryCompetition: this.state.deliveryCompetition, + pricePercentage: Number(this.state.pricePercentage), + isSync: this.state.isSync, + vendorOrgCode: this.state.vendorOrgCode + } + form.append('payload', JSON.stringify(json)); + await fetchJson('/v2/store/AddStoreVendorMap', { + method: 'POST', + body: form + }); + } else { + // 新增专送 + let json = { + vendorStoreID: this.state.vendorStoreID, + status: this.state.status + } + form.append('payload', JSON.stringify(json)); + await fetchJson('/v2/store/AddStoreCourierMap', { + method: 'POST', + body: form + }); + } + + Dialog.show('成功', `绑定成功`, {showCancel: false}); + return true; + } + } catch (e) { + Dialog.show('绑定失败', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } + } + // 弹出框确定 + fnConfirm () { + // this.setState({popupShow: false, isEditor: false}); + this.setState({...this.reset}); + // 更新数据 + this.props.update && this.props.update(); + } + // 修改 + handleModify (key, val) { + console.log(key, val); + this.setState({[key]: val}); + } + // 切换账号 + fnOrgCodePick = () => { + this.refs.orgCode.show(); + } + // 修改账号 + render () { + let plantData = null; + if (this.props.shop[this.props.plant.name]) { + plantData = this.props.shop[this.props.plant.name].find(item => item.vendorID === this.props.plant.id); + } + let venderName = this.props.system.cms.vendorName ? this.props.system.cms.vendorName[this.props.plant.id] : ''; + return ( +
+
{venderName}:
+
{plantData ? plantData.vendorStoreID : '未绑定'}
+ {/* 添加绑定 */} + { + !plantData && ( +
+ +
+ ) + } + {/* 删除绑定 */} + { + plantData && ( +
+ +
+ ) + } + {/* 修改绑定 */} + { + plantData && ( +
+ +
+ ) + } + {/* 添加编辑弹出框 */} + { + this.state.popupShow && ( + +
正在操作 {this.props.shop.name} {this.state.isEditor ? '编辑' : '绑定'} {venderName}
+ {/* 平台status */} + { + this.state.isEditor && this.state.vendorID < 100 && ( +
+
平台状态:
+
+ +
+
+ ) + } + {/* 平台账号 */} + { + !this.state.isEditor && this.state.vendorID < 100 && ( +
+
平台账号:
+
+
{this.state.vendorOrgCode}
+
+
+ ) + } + {/* 平台门店ID */} +
+
平台门店ID:
+ { + this.state.isEditor ? ( +
{this.state.vendorStoreID}
+ ) : ( + + ) + } + { + this.state.vendorID < 100 ? ( +
查询
+ ) : '' + } +
+ { + this.state.vendorID < 100 ? ( +
+ {/* 基本设置 */} +
+
基本设置:
+
+ + +
+
+ {/* 平台调价 */} +
+
平台调价:
+ +
%
+
+ {/* 同步状态 */} + {/* { + !this.state.isEditor && ( + + ) + } */} +
+
同步状态:
+
+ { + !this.state.isEditor && ( + + ) + } + { + this.state.isEditor && this.state.isSync ? ( + 同步 + ) : ( + + ) + } +
+
+ {/* 配送类型 */} + { + this.state.isEditor && ( +
+
快递类型:
+
+ {this.props.system.cms.storeDeliveryType[this.state.deliveryType]} +
+
+ ) + } +
+ ) : ( +
+ {/* 专送status */} +
状态:
+
+ +
+
+ ) + } + {/* 账号选择 */} + +
+ ) + } +
+ ); + } +}; + +export default connect((state, props) => Object.assign({}, props, state), {})(VendorCell); diff --git a/src/components/pageCmp/cmp-modify.scss b/src/components/pageCmp/cmp-modify.scss new file mode 100644 index 0000000..6887058 --- /dev/null +++ b/src/components/pageCmp/cmp-modify.scss @@ -0,0 +1,498 @@ +@import '../../assets/style/colors.scss'; // 修改门店基础信息 + +.cmp-modify-base { + .modify-base-wrapper { + height: calc(100vh - 1.8rem); + overflow: hidden; + } + .create-cell { + font-size: 0.32rem; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.1rem 0.2rem; + background: white; + border-bottom: 1px solid $gray; + min-height: 0.9rem; + box-sizing: border-box; + + .icon { + flex-shrink: 0; + + svg { + width: 0.3rem; + height: 0.3rem; + fill: $sliver; + } + } + + .cell-text { + width: 100%; + color: $sliver; + } + + .cell-label { + width: 2rem; + color: $sliver; + flex-shrink: 0; + } + + .cell-value { + color: $black; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + + & > * { + flex-shrink: 0; + } + } + .rangeInput { + width: 100%; + margin: 0 0.1rem; + } + .suffix { + color: $sliver; + } + + .cell-input { + flex: 1; + color: $black; + // border: 1px solid $danger; + border: none; + padding: 0.1rem; + border-radius: 0.1rem; + outline: none; + } + + .danger { + border: 0.04rem dotted $danger; + } + + .cell-time1 { + flex: 1; + display: flex; + justify-content: space-around; + align-items: center; + color: $sliver; + + .time { + background: $lightGray; + color: $black; + padding: 0.1rem; + border-radius: 0.1rem; + width: 1.4rem; + text-align: center; + } + } + + .cell-city { + flex: 1; + display: flex; + justify-content: space-around; + align-items: center; + + .area { + color: white; + padding: 0.14rem 0.4rem; + border-radius: 0.1rem; + + &:nth-of-type(1) { + background: $blue; + } + + &:nth-of-type(2) { + background: $success; + } + } + } + } + + .auto-open { + justify-content: flex-start; + .auto-open-text { + color: $lightBlue; + } + .auto-open-button { + background: $success; + margin-left: .1rem; + padding: 0.14rem 0.4rem; + color: white; + border-radius: .1rem; + } + } + + .printer-cell { + justify-content: flex-start; + .printer { + // flex: 1; + background: $success; + margin-left: .1rem; + padding: 0.14rem 0.4rem; + color: white; + border-radius: .1rem; + } + .test-print { + margin-left: .4rem; + color: $blue; + // font-size: .28rem; + border: 1px solid $blue; + padding: 0.14rem 0.2rem; + border-radius: .1rem; + box-sizing: border-box; + } + } + .header-title { + font-size: 0.3rem; + padding: 0.2rem 0.4rem; + color: $lightBlack; + border-bottom: 1px solid $gray; + } + + .btn-group { + display: flex; + margin-top: 0.5rem; + box-shadow: 0 0 0.05rem $extraLightBlack; + } + + .delete-btn, + .modify-btn { + flex: 1; + background: $blue; + color: white; + font-size: 0.32rem; + height: 1rem; + line-height: 1rem; + text-align: center; + // border-top: 1px solid $extraLightBlack; + } + + .delete-btn { + background: $danger; + } +} +// 修改平台绑定 + +.cmp-modify-maps { + .create-cell { + font-size: 0.32rem; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.1rem 0.2rem; + background: white; + border-bottom: 1px solid $gray; + min-height: 0.9rem; + box-sizing: border-box; + .cell-input { + flex: 1; + color: $black; + // border: 1px solid $danger; + border: none; + padding: 0.1rem; + border-radius: 0.1rem; + outline: none; + } + .danger { + border: 0.04rem dotted $danger; + } + .suffix-btn { + color: white; + background: $blue; + height: .6rem; + line-height: .6rem; + padding: 0 .4rem; + border-radius: .1rem; + flex-shrink: 0; + } + .suffix-text { + color: $sliver; + display: inline-block; + width: 3rem; + } + .rangeInput { + width: 100%; + margin: 0 0.1rem; + } + .cell-text { + width: 100%; + color: $black; + } + .cell-vendorStoreID-text { + color: $sliver; + width: 100%; + padding-left: .1rem; + } + .cell-label { + width: 2rem; + color: $sliver; + flex-shrink: 0; + } + .cell-value, .cell-value-checkbox { + color: $black; + flex: 1; + display: flex; + align-items: center; + // justify-content: center; + + & > * { + flex-shrink: 0; + } + .base-setting { + margin: 0 .1rem; + } + } + .cell-value-checkbox { + padding-left: .1rem; + } + .no-bind { + color: $extraLightBlack; + } + // 关店 + .status-0 { + color: $danger; + } + // 开店 + .status-1 { + color: $primary; + } + } + .btn-insert, .btn-delete, .btn-update { + flex-shrink: 0; + background: #ccc; + margin: 0 .1rem; + width: .64rem; + height: .64rem; + display: flex; + border-radius: .1rem; + box-shadow: 0 0 .05rem $sliver; + .svg { + margin: auto; + svg { + width: .32rem; + height: .32rem; + fill: white; + } + } + } + .btn-insert { + background: $success; + } + .btn-delete { + background: $danger; + } + .btn-update { + background: $primary; + } + .success { + color: $success; + } + .btn-wrapper { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + } + .orgCode { + // flex: 1; + color: $black; + // border: 1px solid $danger; + border: none; + padding: 0.12rem; + border-radius: 0.1rem; + outline: none; + background: $primary; + width: 2rem; + text-align: center; + color: white; + } + + .header-title { + font-size: 0.3rem; + padding: 0.2rem 0.4rem; + color: $lightBlack; + border-bottom: 1px solid $gray; + } +} + +// 分组管理 +.cmp-modify-group { + .btn-group { + display: flex; + } + .btn-addGroup, .btn-reg { + flex: 1; + font-size: .32rem; + height: .9rem; + line-height: .9rem; + background: $primary; + color: white; + text-align: center; + } + .btn-reg { + background: $success; + } + .tel-input { + width: 100%; + font-size: .32rem; + margin: .1rem 0; + outline: none; + border: 1px solid $sliver; + padding: .1rem .2rem; + box-sizing: border-box; + border-radius: .1rem; + } + .tel-wrong { + border-color: $danger; + } + .cell-input { + // flex: 1; + color: $black; + border: 2px solid $success !important; + border: none; + padding: 0.1rem; + border-radius: 0.1rem; + outline: none; + width: 100%; + box-sizing: border-box; + } + + .danger { + border: 2px dashed $danger !important; + } + // 组员卡片 + .card-wrapper { + height: calc(100vh - 2.7rem); + overflow: hidden; + // overflow-y: auto; + // -webkit-overflow-scrolling: touch; + // padding: .4rem 0 ; + // padding-bottom: 1rem; + // padding: .2rem; + & > div { + padding: .2rem; + } + } + .card { + margin-bottom: .4rem; + box-shadow: 0 1px 1px rgba(black, .2), + 0 8px 0 -3px white, + 0 9px 1px -3px rgba(black, .2), + 0 16px 0 -6px white, + 0 17px 2px -6px rgba(black, .2); + border-radius: .1rem; + overflow: hidden; + border: 1px solid rgba($blue, .4); + // border-bottom: none; + } + + .card-group, .card-group-member { + display: flex; + align-items: center; + padding: .2rem; + background: linear-gradient(to right, rgba($blue, .4), white); + justify-content: space-between; + .card-left { + font-size: .32rem; + color: $black; + .tel { + margin-top: .05rem; + } + .bind-id { + display: flex; + margin-top: .1rem; + .no-bind { + margin-right: .1rem; + svg { + width: .36rem; + height: .36rem; + fill: white; + padding: .05rem; + background: $gray; + } + } + .is-bind { + svg { + background: $success; + border-radius: .05rem; + } + } + } + } + .card-right { + margin-left: .2rem; + display: flex; + align-items: center; + .btn-editor, .btn-delete { + width: .64rem; + height: .64rem; + border-radius: .1rem; + box-shadow: 0 0 .05rem rgba($sliver, 1); + box-sizing: border-box; + border: .02rem solid white; + display: flex; + align-items: center; + justify-content: center; + .svg { + font-size: .32rem; + svg { + width: .32rem; + height: .32rem; + fill: white; + } + } + } + .btn-editor { + background: $blue; + } + .btn-delete { + margin-right: .1rem; + background: $danger; + } + } + } + .card-member-info { + font-size: .28rem; + background: white; + text-align: center; + height: .8rem; + display: flex; + align-items: center; + justify-content: center; + span { + color: $lightSliver; + margin-right: .2rem; + } + .btn-add-members { + color: white; + background: $success; + padding: .14rem .2rem; + border-radius: .1rem; + } + } + .card-group-member { + background: white; + margin-bottom: 0; + padding: .2rem .2rem .2rem .8rem; + border-top: 1px solid rgba($blue, .4); + .card-left { + color: $extraLightBlack; + font-size: .28rem; + } + } + .no-thing { + text-align: center; + margin-top: 1rem; + .no-store { + svg { + width: 4rem; + height: 4rem; + } + } + .text { + color: #ccc; + font-size: .32rem; + } + } +} diff --git a/src/components/pageCmp/create-store.scss b/src/components/pageCmp/create-store.scss new file mode 100644 index 0000000..b79da30 --- /dev/null +++ b/src/components/pageCmp/create-store.scss @@ -0,0 +1,128 @@ +// 创建门店 +@import '../../assets/style/colors.scss'; + +.create-store { + padding-bottom: 1rem; + .create-store-wrapper { + height: calc(100vh - 1rem); + overflow: hidden; + } + .create-cell { + font-size: .32rem; + display: flex; + justify-content: space-between; + align-items: center; + padding: .1rem .2rem; + background: white; + border-bottom: 1px solid $gray; + min-height: .9rem; + box-sizing: border-box; + .icon { + flex-shrink: 0; + svg { + width: .3rem; + height: .3rem; + fill: $sliver; + } + } + .cell-label { + width: 2rem; + color: $sliver; + flex-shrink: 0; + } + .cell-value { + color: $black; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + & > * { + flex-shrink: 0; + } + } + .cell-input { + flex: 1; + color: $black; + // border: 1px solid $danger; + border: none; + padding: .1rem; + border-radius: .1rem; + outline: none; + } + .danger { + border: .04rem dotted $danger; + } + .cell-time1 { + flex: 1; + display: flex; + justify-content: space-around; + align-items: center; + color: $sliver; + .time { + background: $lightGray; + color: $black; + padding: .1rem; + border-radius: .1rem; + width: 1.4rem; + text-align: center; + } + } + .cell-city { + flex: 1; + display: flex; + justify-content: space-around; + align-items: center; + .area { + color: white; + padding: .14rem .4rem; + border-radius: .1rem; + &:nth-of-type(1) { + background: $blue; + } + &:nth-of-type(2) { + background: $success; + } + } + } + .rangeInput { + width: 100%; + margin: 0 .1rem; + } + .suffix { + color: $sliver; + } + } + + .printer-cell { + justify-content: flex-start; + .printer { + // flex: 1; + background: $success; + margin-left: .1rem; + padding: 0.14rem 0.4rem; + color: white; + border-radius: .1rem; + } + } + .has-suffix { + .cell-input { + // background: red; + flex: 1; + width: 2.6rem; + } + } + .suffix2 { + flex: 1; + // flex-shrink: 0; + // width: 4rem; + font-size: .18rem; + line-height: 1.5; + color: $primary; + } + .header-title { + font-size: .3rem; + padding: .2rem .4rem; + color: $lightBlack; + border-bottom: 1px solid $gray; + } +} diff --git a/src/components/pageCmp/modify-store.scss b/src/components/pageCmp/modify-store.scss new file mode 100644 index 0000000..4b4dd31 --- /dev/null +++ b/src/components/pageCmp/modify-store.scss @@ -0,0 +1,13 @@ +// 修改门店 +@import '../../assets/style/colors.scss'; + +// 基础信息修改 +.modify-store { + .modify-wrapper { + // margin-top: .05rem; + height: calc(100vh - 1.8rem); + // overflow-y: auto; + overflow: hidden; + // -webkit-overflow-scrolling: touch; + } +} diff --git a/src/components/pageCmp/more-search.scss b/src/components/pageCmp/more-search.scss new file mode 100644 index 0000000..0746b7e --- /dev/null +++ b/src/components/pageCmp/more-search.scss @@ -0,0 +1,40 @@ +// 更多检索条件 +@import '../../assets/style/colors.scss'; + +.more-search { + .search-cell { + font-size: .32rem; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 .4rem; + background: white; + border-bottom: 1px solid $gray; + min-height: 1rem; + .icon { + flex-shrink: 0; + svg { + width: .3rem; + height: .3rem; + fill: $sliver; + } + } + } + .search-cell + .search-cell { + } + .cell-label { + width: 1.8rem; + color: $sliver; + flex-shrink: 0; + } + .cell-value { + color: $black; + flex: 1; + } + .header-title { + font-size: .3rem; + padding: .2rem .4rem; + color: $lightBlack; + border-bottom: 1px solid $gray; + } +} diff --git a/src/components/pageCmp/store-cell.scss b/src/components/pageCmp/store-cell.scss new file mode 100644 index 0000000..8c93528 --- /dev/null +++ b/src/components/pageCmp/store-cell.scss @@ -0,0 +1,49 @@ +// 门店单元 +@import '../../assets/style/colors.scss'; + +.store-item { + // padding: 0 .2rem; + .store-cell { + padding: .4rem .2rem; + display: flex; + justify-content: space-between; + // height: .6rem; + align-items: center; + background: white; + line-height: 1; + } + .store-id { + font-size: .28rem; + color: $sliver; + margin-right: .2rem; + } + .store-name { + font-size: .32rem; + color: $black; + flex: 1; + } + .store-status { + font-size: .24rem; + padding: .08rem .12rem; + border-radius: .3rem; + color: white; + margin-left: .2rem; + flex-shrink: 0; + } + .status-jy { + background: $danger; + color: white; + } + .status-xx { + background: $gray; + } + .status-yy { + background: $success; + color: white; + } + .city { + font-size: .24rem; + margin-left: .1rem; + color: $sliver; + } +} diff --git a/src/components/zy-ui用法.txt b/src/components/zy-ui用法.txt new file mode 100644 index 0000000..f69d0c4 --- /dev/null +++ b/src/components/zy-ui用法.txt @@ -0,0 +1,109 @@ +/* + 移动端组件使用方法 +*/ + +// ----- Dialog ----- +Dialog.show('标题', '内容', { + showCancel: '是否显示取消按钮 默认是', + cancelText: '取消按钮名称', + confirmText: '确定按钮名称' +}, res => { + console.log(res); // true false +}); + +// ----- Loading ----- +// 显示 +Loading.show(); +// 隐藏 +Loading.hide(); + +// ----- Toast ----- + +Toast.show('aaa', [timer = 5]) + +// ----- 弹出框PopupPage PopupPage2无动画----- +title="更多检索条件" +fnReturn={this.props.fnReturn.bind(this)} +fnPrevConfirm={fn} ruturn true or false 确认前 +fnConfirm={this.props.fnConfirm.bind(this)} +SVG + + +// ----- 单选组件 ----- +/* +fields={[ + {value: 0, text: '不限定'}, + {value: 1, text: '已绑定'}, + {value: -1, text: '未绑定'} +]} +id={} 对象名 +current={storeSearch.vendorStoreConds['0']} +fnClick={} return val id +*/ + +// ----- 多选组件 ----- +current={[]} [-1, 0, 1] +fields={[ + {value: 1, text: '营业中'}, + {value: 0, text: '休息中'}, + {value: -1, text: '禁用中'} +]} +fnClick={this.changeStatuss.bind(this)} changeStatuss(val) + +// ----- 自选组件CheckBoxSelf ----- +current={this.state.autoPickup} +text="自动拣货" +fields={[0, 1]} 假,真 +fnClick={this.changeStatuss.bind(this)} changeStatuss(val) + +// ----- 列表 ----- +ref="checklist" +datas={this.props.system.cityLevel2} +map={{value: 'code', label: 'name'}} +nullShow = false 是否显示空 +nullLabel = '' null显示 +current={storeSearch.placeID} +title="城市选择" +fnPick={this.cityPick.bind(this)} + +this.refs.xxx.show() + +// ----- 创建门店input ----- + +check={isNum} 校验函数 +value={this.state.id} 默认值 +placeholder="" +errText="京西门店ID必须是纯数字" 报错提示 +fnBlur={this.idBlur.bind(this)} 失去焦点处理函数 可以为空 + +// ----- 地址选择组件 ----- +region 市名 +ref="SearchAddress" +fnPick={this.fn.bind(this)} +// 打开 +this.refs.xxx.show() + +// ----- 选择输入框 ----- +pick input button + +title="选择平台" // 选择面板标题 +placeholder="请输入平台门店ID" +datas={[ + {id: 0, name: vendorName['0']}, + {id: 1, name: vendorName['1']}, + {id: 3, name: vendorName['3']} +]} +map={{value: 'id', label: 'name'}} +current={this.state.vendorID} // 当前选择 +fnPick={this.handleVendorID} // 选择后处理函数,val +fnChange={this.handleVendorStoreID} // 输入框改变处理函数,val +fnConfirm={this.handleGetVendorStore} // 是否有确认按钮,且执行何种函数 + +// ----- 输入筛选 ----- +placeholder="请选择门店" fnPick={this.pickSrcStore} + +// ----- 门店选择组件 ----- +ref="SearchAddress" +fnPick={this.fn.bind(this)} +// 打开 +this.refs.xxx.show() diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..dae0d32 --- /dev/null +++ b/src/config.js @@ -0,0 +1,50 @@ +// 是否调试模式 +export const DEBUG = true && process.env.NODE_ENV === 'development'; + +export const apiEdition = process.env.NODE_ENV === 'development' ? 'v1' : 'v2' + +export const temToken = 'TOKEN.V2.1439B3E07D3911EA881A525400E86DC0.20210329-092532.ddstaff.A7DD6E11902D11EB851E525400E86DC0.[18160030913]'; + +export const debugPath = '/'; + +// 门店管理权限 +// 运营map +export const yunyingMap = { + '18048531223': '石锋', + '13684045763': '周扬', + '18160030913': '苏尹岚', + '19136159097': '熊馨', + '18982250714': '赵敏夫', + '18780171617': '田沁鑫', + '15708459454': '肖秋', + '18383211225': '官晓燕', + '17338665724': '罗子翼', + '19982595610': '李如微', + '18080119588': '蒋晨阳', + '13608076295': '张佳琳', + '13708196093': '顾子杭', + '15208271238': '王恒文', + '17380734342': '漆云', + '18328080405': '肖娜娜', + '18782186576': '侯洁', + '18583684218': '严小康', + '15983243001': '杨海艳', + '18282536808': '李焘', + '18280930035': '周俊影', + '18252169947': '马力萍', + '18630465684': '杨桂华', + '15620911796': '杨怡怡', + '13198121812': '任涛', + '15892879280': '王人峰', + '18160011502': '吕晓倩', + '18781962924': '李芳', + '15928865396': '何佳梦', + '15520828601': '余奇天', + '18280085346': '郑智洋' +} + +// 负责人组员映射 +export const memberMap = { + '15123535138': '13206278888', // 谭萍:余承文 + '13541860634': '13752897800', // 梅娟:张建平 +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..d397ec1 --- /dev/null +++ b/src/index.js @@ -0,0 +1,22 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import 'normalize.css'; +import '@/assets/style/global.scss'; +import store from '@/store'; +import {Provider} from 'react-redux'; +// import {Provider as KeepAliveProvider, KeepAlive} from 'react-keep-alive' +import {HashRouter as Router, Route} from 'react-router-dom'; +import App from './App'; +import VConsole from 'vconsole'; + +if (process.env.NODE_ENV === 'development') { + new VConsole() +} + +ReactDOM.render(( + + + + + +), document.getElementById('root')); diff --git a/src/pages/ActivityManager.js b/src/pages/ActivityManager.js new file mode 100644 index 0000000..47d7243 --- /dev/null +++ b/src/pages/ActivityManager.js @@ -0,0 +1,15 @@ +import React, { Component } from 'react'; + +class ActivityManager extends Component { + + render() { + return ( +
+ 活动管理 +
+ ); + } + +} + +export default ActivityManager; diff --git a/src/pages/DDAuth.js b/src/pages/DDAuth.js new file mode 100644 index 0000000..cb37571 --- /dev/null +++ b/src/pages/DDAuth.js @@ -0,0 +1,115 @@ +import React, {Component} from 'react'; +// import * as dd from 'dingtalk-jsapi'; +import {runtime} from 'dingtalk-jsapi'; +import fetchJson from '@/utils/fetch'; +import {connect} from 'react-redux'; +import {setToken, setTel, setNickName, setLogin, setCMS, setCityLevel2, setVendorOrgCode} from '@/store/actions'; +import {mapPlaces} from '@/utils/mapData'; +import {DEBUG} from '@/config'; +import {setCookie} from '@/utils/tools'; + +import '@/assets/style/ddauth.scss'; +import Toast from '@/components/ddauth/Toast'; +import Loading from '@/components/Loading'; + +// import {getQueryString} from '@/utils'; + +class DDAuth extends Component { + constructor (...args) { + super(...args); + this.state = {}; + } + // corpId: 'ding7ab5687f3784a8db', // 企业id dingpx4hcf55zb4ubewg + fnBind () { + console.log(window.location.host) + runtime.permission.requestAuthCode({ + corpId: 'ding7ab5687f3784a8db', + onSuccess: async res => { + console.log('钉钉接口', res); + let {code} = res; + // this.setState({code}); + await this.fnLogin(code); + }, + onFail: err => { + console.error(err); + let {errorMessage} = err; + this.refs.toast.show(errorMessage); + } + }) + } + async fnLogin (code) { + try { + Loading.show(); + let form = new FormData(); + form.append('authType', 'ddstaff'); + form.append('authSecret', code); + let res = await fetchJson('/v2/auth2/Login', { + method: 'POST', + body: form + }); + console.log('login', res); + let {tokenType, token, authBindInfo} = res; + if (tokenType === 2) { + console.log('注册'); + // 走注册 + let {mobile, name} = authBindInfo.userHint; + console.log(tokenType, token, mobile, name); + await this.props.setToken(token); + await this.props.setTel(mobile); + await this.props.setNickName(name); + this.props.history.push('/register', {}); + } else { + console.log('已注册'); + // 登录成功 + // this.refs.toast.show('您已完成绑定'); + let {mobile, name} = authBindInfo.userHint; + console.log(token, mobile, name); + await this.props.setToken(token); + await setCookie('token', token, 7); + await this.props.setTel(mobile); + await this.props.setNickName(name); + await this.props.setLogin(true); + await this.getBase(); + this.props.history.replace('/', {}); + } + } catch (e) { + console.error(e); + this.refs.toast.show(e); + } finally { + Loading.hide(); + } + } + async getBase () { + try { + Loading.show(); + // 请求CMS + let {metaData} = await fetchJson('/v2/cms/GetServiceInfo'); + // 请求城市列表 level = 2 + let res = await fetchJson('/v2/cms/GetPlaces?level=2'); + let vendorOrgCode = await fetchJson('/v2/cms/GetVendorOrgCodeInfo') + this.props.setVendorOrgCode(vendorOrgCode) + // console.log(metaData); + this.props.setCMS(metaData); + this.props.setCityLevel2(mapPlaces(res)); + console.log('Places', mapPlaces(res)); + console.log('CMS', this.props.system.cms); + } catch (e) {} finally { + Loading.hide(); + } + } + render () { + return ( +
+
{DEBUG ? '调试模式' : ''}
+
登录
京西菜市
+
成都若溪科技有限公司
+ + +
+ ) + } +} + +export default connect((state, props) => Object.assign({}, props, state), { + setToken, setTel, setNickName, setLogin, setCMS, setCityLevel2, setVendorOrgCode +})(DDAuth); diff --git a/src/pages/GoodsManager.js b/src/pages/GoodsManager.js new file mode 100644 index 0000000..e579b15 --- /dev/null +++ b/src/pages/GoodsManager.js @@ -0,0 +1,41 @@ +import React, { Component } from 'react'; +import '@/assets/style/goods-manager.scss'; +import {Link} from 'react-router-dom'; +import {goodsManager} from '@/router'; +import ReactSVG from 'react-svg'; +import * as dd from 'dingtalk-jsapi'; + +import SVGGo from '@/assets/svg/icon-go.svg'; +import PopupPage from '@/components/layout/PopupPage2'; + +class GoodsManager extends Component { + componentDidMount () { + document.title = '商品管理'; + if (dd.ios || dd.android) { + dd.biz.navigation.setTitle({title: '商品管理'}); + } + } + + render() { + // 获得路径 + return ( +
+ +
+ { + goodsManager.map((link, index) => ( + + {link.text} + + + )) + } +
+
+
+ ); + } + +} + +export default GoodsManager; diff --git a/src/pages/GoodsManagerPages/DeletePlatGoods.js b/src/pages/GoodsManagerPages/DeletePlatGoods.js new file mode 100644 index 0000000..025de83 --- /dev/null +++ b/src/pages/GoodsManagerPages/DeletePlatGoods.js @@ -0,0 +1,34 @@ +import React, { Component } from 'react'; +import PopupPage from '@/components/layout/PopupPage2'; +import './goods-manager-page.scss'; + +class DeletePlatGoods extends Component { + constructor (...args) { + super(...args); + this.state = {}; + } + // 返回按钮 + fnReturn = () => { + this.props.history.goBack(); + } + // 点击确定 + fnConfirm () {} + render () { + return ( +
+ + {/* 同步到平台 */} + {/* 同步门店 */} + {/* 不等待处理结果 */} + {/* 单个失败继续 */} + +
+ ); + } +} + +export default DeletePlatGoods; diff --git a/src/pages/GoodsManagerPages/JxToJx.js b/src/pages/GoodsManagerPages/JxToJx.js new file mode 100644 index 0000000..d02519a --- /dev/null +++ b/src/pages/GoodsManagerPages/JxToJx.js @@ -0,0 +1,132 @@ +import React, { Component } from 'react'; +import PopupPage from '@/components/layout/PopupPage2'; +import './goods-manager-page.scss'; +// import {isNum} from '@/utils/regExp'; +import classNames from 'classnames'; +import Dialog from '@/components/Dialog'; +import Loading from '@/components/Loading'; +import fetchJson from '@/utils/fetch'; + +import KeyWordSearch from '@/components/layout/KeyWordSearch'; + +class JxToJx extends Component { + constructor (...args) { + super(...args); + this.state = { + fromStoreID: null, + toStoreID: null, + copyMode: 'fresh', // fresh全部清除后复制 update只复制指定商品 + pricePercentage: 100, + skuIDs: [], + pricePercentageWrong: false + }; + } + // 点击返回 + fnReturn = () => { + this.props.history.goBack(); + } + // 选择源门店 + pickSrcStore = (id) => { + console.log(id); + this.setState({fromStoreID: id}); + } + // 选择目标门店 + pickTarStore = (id) => { + console.log(id); + this.setState({toStoreID: id}); + } + // 价格百分比修改 + fnChange (e) { + console.log(e.target.value); + let num = Number(e.target.value); + if (num >= 50 && num <= 150) { + this.setState({pricePercentage: num, pricePercentageWrong: false}); + } else { + this.setState({pricePercentage: num, pricePercentageWrong: true}); + } + } + // 提交 + async fnConfirm () { + let arr = []; + if (!this.state.fromStoreID) arr.push('请选择源门店'); + if (!this.state.toStoreID) arr.push('请选择目标门店'); + if (this.state.pricePercentage < 50 || this.state.pricePercentage > 150) arr.push('价格百分比只能在50-150之间'); + if (arr.length > 0) { + // 有错误 + Dialog.show('错误', `${arr.join('
')}
`, { + showCancel: false, + confirmText: '好的' + }) + } else { + // 数据成功 + Dialog.show('提示', '是否进行商品复制操作', {}, async (res) => { + if (res) { + try { + Loading.show(); + let form = new FormData(); + form.append('fromStoreID', this.state.fromStoreID); + form.append('toStoreID', this.state.toStoreID); + form.append('copyMode', this.state.copyMode); + form.append('pricePercentage', this.state.pricePercentage); + await fetchJson('/v2/store/sku/CopyStoreSkus', { + method: 'POST', + body: form + }); + Dialog.show('成功', `复制成功,若要同步到平台,请到[商品管理]-[京西门店商品同步]中进行操作`, {showCancel: false, confirmText: '知道了'}); + } catch (e) { + Dialog.show('错误', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } + }) + } + // console.log('confirm'); + } + // 跳转到同步平台页面 + goPlat () { + this.props.history.replace('/jxtoplat', {}); + } + render() { + return ( +
+ + {/* 源门店 */} +
+
源门店:
+
+ +
+
+ {/* 目标门店 */} +
+
目标门店:
+
+ +
+
+ {/* 拷贝模式 */} + {/* 价格百分比 */} +
+
价格百分比:
+
+ +
%
+
+
+
+

注意: 该功能为京西门店本地复制,并不会同步到平台,如果要同步到平台,请到【商品管理】-【京西门店商品同步】中操作。

+
+ {/* 选择类目复制 */} +
+
+ ); + } + +} + +export default JxToJx; diff --git a/src/pages/GoodsManagerPages/JxToPlat.js b/src/pages/GoodsManagerPages/JxToPlat.js new file mode 100644 index 0000000..2d35028 --- /dev/null +++ b/src/pages/GoodsManagerPages/JxToPlat.js @@ -0,0 +1,152 @@ +import React, { Component } from 'react'; +import PopupPage from '@/components/layout/PopupPage2'; +import './goods-manager-page.scss'; +import Dialog from '@/components/Dialog'; +import Loading from '@/components/Loading'; +import fetchJson from '@/utils/fetch'; + +import KeyWordSearch from '@/components/layout/KeyWordSearch'; +import CheckBoxSelf from '@/components/layout/CheckBoxSelf'; + +class JxToPlat extends Component { + constructor (...args) { + super(...args); + this.state = { + jd: 0, // 0, + mt: 0, // 1, + eb: 0, // 3, + wm: 0, // 11, + vendorIDs: [], + storeIDs: [], + isAsync: 1, // 是否异步 + isForce: 0, + isContinueWhenError: 1 // 单个失败继续 + }; + } + // 返回按钮 + fnReturn = () => { + this.props.history.goBack(); + } + // 平台修改 + changeVendorIDs (key, val) { + this.setState({[key]: val}); + } + // 选择门店 + pickSrcStore (val) { + console.log(val); + this.setState({storeIDs: [val]}); + } + // 是否异步 + changeIsAsync (key, val) { + this.setState({[key]: val}); + } + // 失败是否继续 + changeIsContinueWhenError (key, val) { + this.setState({[key]: val}); + } + // 点击确定 + async fnConfirm () { + console.log(this.state); + // 处理数据 + let vendorIDs = []; + if (this.state.jd) vendorIDs.push(0); + if (this.state.mt) vendorIDs.push(1); + if (this.state.eb) vendorIDs.push(3); + if (this.state.wm) vendorIDs.push(11); + let arr = []; + if (vendorIDs.length === 0) arr.push('至少选择一个平台同步'); + if (this.state.storeIDs.length === 0) arr.push('请选择一个要同步的门店'); + + if (arr.length > 0) { + // 开始报错 + Dialog.show('错误', `${arr.join('
')}
`, { + showCancel: false, + confirmText: '好的' + }) + } else { + Dialog.show('提示', '是否进行同步操作', {}, async res => { + if (res) { + // 开始同步 + let form = new FormData(); + form.append('storeIDs', JSON.stringify(this.state.storeIDs)); + form.append('vendorIDs', JSON.stringify(vendorIDs)); + form.append('isAsync', Boolean(this.state.isAsync)); + form.append('isForce', Boolean(this.state.isForce)); + form.append('isContinueWhenError', Boolean(this.state.isContinueWhenError)); + try { + Loading.show(); + let res = await fetchJson('/v2/store/sku/SyncStoresSkus', { + method: 'PUT', + body: form + }); + console.log(res); + if (res === '0' || !res) { + // 空操作 + Dialog.show('警告', `空操作,请检查该门店是否绑定平台门店`, {showCancel: false, confrimText: '知道了'}); + } else { + if (this.state.isAsync) { + // 异步成功 + Dialog.show('成功', '提交成功, 请留意钉钉通知, 查看处理结果', {showCancel: false, confrimText: '好的'}) + } else { + // 同步成功 + Dialog.show('成功', '同步成功', {showCancel: false, confrimText: '好的'}) + } + } + } catch (e) { + Dialog.show('错误', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } else {} + }) + } + } + render () { + return ( +
+ + {/* 同步到平台 */} +
+
同步到平台:
+
+ + + + {/* */} +
+
+ {/* 同步门店 */} +
+
同步门店:
+
+ +
+
+ {/* 不等待处理结果 */} + {/* 单个失败继续 */} +
+
选项:
+
+ + + +
+
+ {/* 说明 */} +
+

说明: 该功能是把京西门店商品同步到各个平台。

+

注意1: 勾选【不等待处理结果】是提交到服务器后台处理,处理的成功与失败结果会以钉钉消息的形式进行推送;不勾选【不等待处理结果】提交后会进入loading状态,直到服务器返回成功与失败结果为止。

+

注意2: 勾选【单个失败继续】,当处理过程中遇到失败商品,会跳过此商品,继续同步后面的商品;不勾选【单个失败继续】,当处理过程中遇到失败商品,会进行错误提示,并且不再继续同步后面的商品

+
+
+
+ ); + } + +} + +export default JxToPlat; diff --git a/src/pages/GoodsManagerPages/ResetGoods.js b/src/pages/GoodsManagerPages/ResetGoods.js new file mode 100644 index 0000000..119c6b0 --- /dev/null +++ b/src/pages/GoodsManagerPages/ResetGoods.js @@ -0,0 +1,149 @@ +import React, { Component } from 'react'; +import PopupPage from '@/components/layout/PopupPage2'; +import './goods-manager-page.scss'; +import Dialog from '@/components/Dialog'; +import Loading from '@/components/Loading'; +import fetchJson from '@/utils/fetch'; + +import KeyWordSearch from '@/components/layout/KeyWordSearch'; +import CheckBoxSelf from '@/components/layout/CheckBoxSelf'; + +class ResetGoods extends Component { + constructor (...args) { + super(...args); + this.state = { + jd: 0, // 0, + mt: 0, // 1, + eb: 0, // 3, + wm: 0, // 11, + vendorIDs: [], + storeIDs: [], + isAsync: 1, // 是否异步 + isContinueWhenError: 1 // 单个失败继续 + }; + } + // 返回按钮 + fnReturn = () => { + this.props.history.goBack(); + } + // 平台修改 + changeVendorIDs (key, val) { + this.setState({[key]: val}); + } + // 选择门店 + pickSrcStore (val) { + console.log(val); + this.setState({storeIDs: [val]}); + } + // 是否异步 + changeIsAsync (key, val) { + this.setState({[key]: val}); + } + // 失败是否继续 + changeIsContinueWhenError (key, val) { + this.setState({[key]: val}); + } + // 点击确定 + async fnConfirm () { + console.log(this.state); + // 处理数据 + let vendorIDs = []; + if (this.state.jd) vendorIDs.push(0); + if (this.state.mt) vendorIDs.push(1); + if (this.state.eb) vendorIDs.push(3); + if (this.state.wm) vendorIDs.push(11); + let arr = []; + if (vendorIDs.length === 0) arr.push('至少选择一个平台'); + if (this.state.storeIDs.length === 0) arr.push('请选择一个门店'); + + if (arr.length > 0) { + // 开始报错 + Dialog.show('错误', `${arr.join('
')}
`, { + showCancel: false, + confirmText: '好的' + }) + } else { + Dialog.show('提示', '是否进行初始化操作', {}, async res => { + if (res) { + // 开始同步 + let form = new FormData(); + form.append('storeIDs', JSON.stringify(this.state.storeIDs)); + form.append('vendorIDs', JSON.stringify(vendorIDs)); + form.append('isAsync', Boolean(this.state.isAsync)); + form.append('isContinueWhenError', Boolean(this.state.isContinueWhenError)); + try { + Loading.show(); + let res = await fetchJson('/v2/sync/FullSyncStoresSkus', { + method: 'PUT', + body: form + }); + console.log(res); + if (res === '0' || !res) { + // 空操作 + Dialog.show('警告', `空操作,请检查该门店是否绑定平台门店`, {showCancel: false, confrimText: '知道了'}); + } else { + if (this.state.isAsync) { + // 异步成功 + Dialog.show('成功', '提交成功, 请留意钉钉通知, 查看处理结果', {showCancel: false, confrimText: '好的'}) + } else { + // 同步成功 + Dialog.show('成功', '初始化成功', {showCancel: false, confrimText: '好的'}) + } + } + } catch (e) { + Dialog.show('错误', `${e}`, {showCancel: false}); + } finally { + Loading.hide(); + } + } else {} + }) + } + } + render () { + return ( +
+ + {/* 同步到平台 */} +
+
选择平台:
+
+ + + + {/* */} +
+
+ {/* 同步门店 */} +
+
选择门店:
+
+ +
+
+ {/* 不等待处理结果 */} + {/* 单个失败继续 */} +
+
选项:
+
+ + +
+
+ {/* 说明 */} +
+

说明: 该功能是先清空对应平台门店商品,再从该京西门店把商品同步到平台。

+

注意1: 勾选【不等待处理结果】是提交到服务器后台处理,处理的成功与失败结果会以钉钉消息的形式进行推送;不勾选【不等待处理结果】提交后会进入loading状态,直到服务器返回成功与失败结果为止。

+

注意2: 勾选【单个失败继续】,当处理过程中遇到失败商品,会跳过此商品,继续同步后面的商品;不勾选【单个失败继续】,当处理过程中遇到失败商品,会进行错误提示,并且不再继续同步后面的商品

+
+
+
+ ); + } + +} + +export default ResetGoods; diff --git a/src/pages/GoodsManagerPages/WmToJx.js b/src/pages/GoodsManagerPages/WmToJx.js new file mode 100644 index 0000000..01fbd67 --- /dev/null +++ b/src/pages/GoodsManagerPages/WmToJx.js @@ -0,0 +1,81 @@ +import React, { Component } from 'react'; +import PopupPage from '@/components/layout/PopupPage2'; +import './goods-manager-page.scss'; +// import classNames from 'classnames'; +import Toast from '@/components/Toast/index.js' +import Dialog from '@/components/Dialog'; +import Loading from '@/components/Loading'; +import fetchJson from '@/utils/fetch'; + +import KeyWordSearch from '@/components/layout/KeyWordSearch'; + +class WmToJx extends Component { + constructor (...args) { + super(...args); + this.state = { + storeIDs: [] + }; + } + // 点击返回 + fnReturn = () => { + this.props.history.goBack(); + } + // 选择目标门店 + pickTarStore = (id) => { + console.log(id); + this.setState({storeIDs: [id]}); + } + + handleCopy = async () => { + const {storeIDs} = this.state + try { + Loading.show() + if (storeIDs.length === 0) { + Toast.show('没有选择目标门店') + return false + } + console.log(storeIDs) + let res = await fetchJson('/v2/yonghui/UpdateJxPriceByWeimob', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: `storeIDs=${JSON.stringify(storeIDs)}&isAsync=true&isContinueWhenError=true` + }) + console.log(res) + Dialog.show('成功', '上传成功,请留意钉钉消息关注完成情况', { + showCancel: false + }) + } catch (e) { + console.error(e) + Dialog.showErr(e) + } finally { + Loading.hide() + } + } + + render() { + return ( + +
+ {/* 目标门店 */} +
+
目标门店:
+
+ +
+
+
+
开始复制
+
+
+
+ ); + } + +} + +export default WmToJx; diff --git a/src/pages/GoodsManagerPages/aduit-manager.js b/src/pages/GoodsManagerPages/aduit-manager.js new file mode 100644 index 0000000..0c7b92a --- /dev/null +++ b/src/pages/GoodsManagerPages/aduit-manager.js @@ -0,0 +1,205 @@ +import React, { Component } from 'react'; +import './aduit-manager.scss' +import Radio from '@/components/layout/Radio.js' +import Loading from '@/components/Loading'; +import {formatDate} from '@/utils/tools.js' +import fetchJson from '@/utils/fetch'; +import Dialog from '@/components/Dialog'; +import BScroll from 'better-scroll'; +import Promopt from '@/components/Promopt'; +import Toast from '@/components/Toast'; + +class AuditManager extends Component { + constructor(props) { + super(props); + this.state = { + status: 0, + list: [], + current: {} + } + this.scroll = null + } + + async componentDidMount () { + this.scroll = new BScroll(this.refs.wrapper, { + click: true, + // stopPropagation: true, + // 下拉回弹关闭 + bounce: { + top: false, + bottom: false + }, + // 下拉刷新 + // pullDownRefresh: { + // threshold: 100, + // stop: 100 + // }, + // 上拉加载 + // pullUpLoad: { + // threshold: 200 + // } + }) + await this.getAuditList() + } + + async getAuditList () { + try { + Loading.show() + this.scroll && this.scroll.scrollTo(0, 0, 0) + const query = `applyTimeStart=${formatDate(new Date() - 1000 * 60 * 60 * 24 * 7, 'YYYY-MM-DD')}+00:00:00&applyTimeEnd=${formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss')}&types=[1,2]&offset=0&pageSize=-1&statuss=${JSON.stringify([this.state.status])}` + const {totalCount, data} = await fetchJson('/v2/store/sku/GetStoreSkuAudit?' + query) + console.log(totalCount, data) + this.setState({ + list: data || [] + }) + } catch (e) { + console.error(e) + this.setState({ + list: [] + }) + Dialog.showErr(e) + } finally { + Loading.hide() + } + } + + async changeStatus (val) { + await this.setState({status: val, list: []}) + await this.getAuditList() + } + + async openDia (type, data) { + console.log(type, data) + await this.setState({ + current: data + }) + + await this.refs.promopt.show( + type === 1 ? '批准价格' : '拒绝理由', + async (confirm) => { + if (confirm) { + console.log(this.refs.Input.value) + const text = this.refs.Input.value.trim() + const data1 = {} + const data2 = {} + if (type === 1) { + // 批准 + data1.auditPrice = Math.floor(text * 100) + } else { + data2.remark = text + } + const payload = JSON.stringify([ + { + storeID: data.storeID, + nameID: data.nameID, + ...data1, + ...data2 + } + ]) + try { + Loading.show() + await fetchJson('/v2/store/sku/StoreSkuPriceAudit', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: `status=${type}&isAsync=false&isContinueWhenError=true&payload=${payload}` + }) + Toast.show('操作成功') + await this.getAuditList() + } catch (e) { + Dialog.showErr(e) + } finally { + Loading.hide() + } + } + } + ) + if (type === 1) { + this.refs.Input.value = (data.unitPrice / 100).toFixed(2) + } else { + this.refs.Input.value = '报价过高' + } + } + + render() { + const {status, list} = this.state + return ( +
+
+ +
+
+
+ { + list.map((audit, index) => ( +
+
({audit.payPercentage}){audit.storeName}({audit.storeID})
+
{audit.prefix ? '[' + audit.prefix + ']' : ''} {audit.skuName} / {audit.unit}
+
+ { + audit.type === 1 ? ( +
+ 修改价格: + ¥{(audit.originPrice / 100).toFixed(2)} + → + ¥{(audit.unitPrice / 100).toFixed(2)} + (全国中位价¥{(audit.midUnitPrice / 100).toFixed(2)}) +
+ ) : ( +
+ 关注商品: + ¥{(audit.unitPrice / 100).toFixed(2)} + (全国中位价¥{(audit.midUnitPrice / 100).toFixed(2)}) +
+ ) + } +
+ {/* 操作面板 */} + { + status === 0 && ( +
+
拒绝
+
批准
+
+ ) + } + { + status === 1 && ( +
+ 已批准 ¥{(audit.auditPrice / 100).toFixed(2)}({audit.name}) +
+ ) + } + { + status === -1 && ( +
+ 已拒绝 理由: {audit.remark}({audit.name}) +
+ ) + } +
+ )) + } +
+
+ + + +
+ ); + } +} + +export default AuditManager; diff --git a/src/pages/GoodsManagerPages/aduit-manager.scss b/src/pages/GoodsManagerPages/aduit-manager.scss new file mode 100644 index 0000000..adb995e --- /dev/null +++ b/src/pages/GoodsManagerPages/aduit-manager.scss @@ -0,0 +1,94 @@ +@import '@/assets/style/_colors.scss'; + +.audit-manager { + .top { + height: 1rem; + border-bottom: 1px solid $blue; + display: flex; + justify-content: center; + align-items: center; + } + .zy-ui-radio { + flex: 1; + display: flex; + } + .zy-ui-radio-btn { + flex: 1; + text-align: center; + height: 1rem; + line-height: 1rem; + padding: 0; + } + .wrapper { + height: calc(100vh - 1rem); + overflow: hidden; + } + .content { + font-size: .32rem; + padding: .2rem; + } + .audit-item { + padding: .2rem 0; + } + + .storeID { + color: $extraLightBlack; + font-size: .28rem; + margin-bottom: .1rem; + } + .skuName { + color: $blue; + margin-bottom: .1rem; + // text-align: center; + } + .change { + // text-align: center; + color: #333; + margin-bottom: .1rem; + .small { + font-size: .24rem; + color: #999; + } + } + .status0 { + // color: $warning; + display: flex; + align-items: center; + justify-content: space-around; + padding-top: .1rem; + .btn { + border: 1px solid $gray; + font-size: .26rem; + height: .6rem; + display: flex; + align-items: center; + justify-content: center; + width: 2rem; + border-radius: .05rem; + box-shadow: 0 .02rem .04rem rgba($gray, 1); + color: white; + } + .refuse { + background: $danger; + } + .agree { + background: $success; + } + } + .status1 { + color: $success; + } + .status-1 { + color: $danger; + } + + .audit-item + .audit-item { + border-top: 1px solid $gray; + } + .input { + box-sizing: border-box; + padding: .1rem .2rem; + border-radius: .05rem; + border: 1px solid $danger; + } +} diff --git a/src/pages/GoodsManagerPages/download-wm-order.js b/src/pages/GoodsManagerPages/download-wm-order.js new file mode 100644 index 0000000..c5b4305 --- /dev/null +++ b/src/pages/GoodsManagerPages/download-wm-order.js @@ -0,0 +1,266 @@ +import React, { Component } from 'react'; +import PopupPage from '@/components/layout/PopupPage2'; +import './goods-manager-page.scss'; +import fetchJson from '@/utils/fetch'; +import Toast from '@/components/Toast/index.js' +import Loading from '@/components/Loading'; +import Dialog from '@/components/Dialog'; +import {formatDate} from '@/utils/tools.js' +import * as dd from 'dingtalk-jsapi'; +import BScroll from 'better-scroll'; + +class DownloadWMOrder extends Component { + constructor (props) { + super(props) + this.state = { + // fromDate: '', + toDate: '', + url: '', + orderList: [] + } + this.scroll = null + } + + async componentDidMount () { + await this.setState({ + // fromDate: formatDate(new Date() - 24 * 3600 * 1000, 'YYYY-MM-DD'), + toDate: formatDate(new Date(), 'YYYY-MM-DD') + }) + this.handleSearch() + // 先移除meta + // let metas = document.querySelectorAll('meta') + // console.log(metas) + // document.getElementsByTagName('head')[0].removeChild(metas[1]) + + // var dpr = window.devicePixelRatio; + // var meta = document.createElement('meta'); + // var scale = 2 / dpr; + // meta.setAttribute('name', 'viewport'); + // meta.setAttribute('content', 'width=device-width, user-scalable=yes, initial-scale=' + scale + ', minimum-scale=' + scale); + // document.getElementsByTagName('head')[0].appendChild(meta); + // // 动态设置的缩放大小会影响布局视口的尺寸 + // function resize() { + // var deviceWidth = document.documentElement.clientWidth; + // document.documentElement.style.fontSize = (deviceWidth / 7.5) +'px'; + // } + // resize(); + // window.onresize = resize; + } + + componentDidUpdate () { + if (this.scroll) { + this.scroll.refresh(); + this.scroll.scrollTo(0, 0); + } else { + if (this.refs.scrollList) { + this.scroll = new BScroll(this.refs.scrollList, { + click: true, + stopPropagation: true, + bounce: { + top: false, + bottom: false + } + }); + } + } + } + + componentWillUnmount () { + // 先移除meta + // let metas = document.querySelectorAll('meta') + // console.log(metas) + // document.getElementsByTagName('head')[0].removeChild(metas[1]) + + // var dpr = window.devicePixelRatio; + // var meta = document.createElement('meta'); + // var scale = 2 / dpr; + // meta.setAttribute('name', 'viewport'); + // meta.setAttribute('content', 'width=device-width, user-scalable=no, initial-scale=' + scale + + // ', maximum-scale=' + scale + ', minimum-scale=' + scale); + // document.getElementsByTagName('head')[0].appendChild(meta); + // // 动态设置的缩放大小会影响布局视口的尺寸 + // function resize() { + // var deviceWidth = document.documentElement.clientWidth; + // document.documentElement.style.fontSize = (deviceWidth / 7.5) +'px'; + // } + // resize(); + // window.onresize = resize; + } + + // 点击返回 + fnReturn = () => { + this.props.history.goBack(); + } + + handlePickDate (key) { + console.log(key) + dd.biz.util.datepicker({ + format: 'yyyy-MM-dd', + value: this.state[key], + onSuccess : async ({value}) => { + //onSuccess将在点击完成之后回调 + /*{ + value: "2015-02-10" + } + */ + await this.setState({ + [key]: value + }) + + this.handleSearch() + } + }) + } + + handleSearch = async () => { + try { + const {toDate} = this.state + // if (((+new Date(toDate)) - (+new Date(fromDate))) < 0) { + // Toast.show('起止日期范围错误') + // return false + // } + // this.setState({url: ''}) + Loading.show() + this.setState({orderList: []}) + // let res = await new Promise((resolve) => { + // setTimeout(() => { + // let data = [] + // for (let i = 0; i < 30; i++) { + // data.push({ + // orderID: '12392392390', + // name: '顺利打开', + // tel: '13011112222' + // }) + // } + // resolve(data) + // }, 1000) + // }) + let res = await fetchJson('/v2/yonghui/GetWeimobOrders', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: `fromTime=${toDate + ' 00:00:00'}&toTime=${toDate + ' 23:59:59'}` + }) + console.log(res) + this.setState({orderList: res || []}) + } catch (e) { + console.error(e) + Dialog.showErr(e) + } finally { + Loading.hide() + } + } + // 点击保存 + // handleSave = () => { + // const {fromDate, toDate, url} = this.state + // // 创建a标签模拟点击下载 + // let a = document.createElement('a'); + // a.hidden = true; + // a.href = url; + // a.download = `微盟订单${fromDate}_${toDate}`; + + // document.body.appendChild(a); + // a.click(); + // document.body.removeChild(a); + // } + + async handleDownload (orderNo, type) { + try { + let url = await fetchJson('/v2/yonghui/GetWeimobOrdersExcel', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: `orderNo=${orderNo}` + }) + if (url && url[type]) { + let a = document.createElement('a'); + a.hidden = true; + a.href = url[type]; + a.download = `微盟订单`; + + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + } else { + Toast.show('没有查询到excel地址') + } + } catch (e) { + console.error(e) + Dialog.showErr(e) + } finally { + Loading.hide() + } + } + + render () { + const {toDate, orderList} = this.state + return ( + +
+ {/* 开始时间 */} + {/*
+
选择开始时间:
+
+
+ {fromDate} +
+
+
*/} + {/* 结束时间 */} +
+
选择日期:
+
+
+ {toDate} +
+
+
+
+
+ 查询订单 +
+ {/* { + url && ( +
+
+
+
保存
+
+ +
+ ) + } */} +
+
+ { + orderList.length > 0 && orderList.map((item, index) => ( +
+
+
{item.orderNo}
+
+
{item.name}
+
{item.phone}
+
+
+
+
精品下载
+
毛菜下载
+
+
+ )) + } +
+
+
+
+
+ ) + } +} + +export default DownloadWMOrder; \ No newline at end of file diff --git a/src/pages/GoodsManagerPages/goods-manager-page.scss b/src/pages/GoodsManagerPages/goods-manager-page.scss new file mode 100644 index 0000000..c563b78 --- /dev/null +++ b/src/pages/GoodsManagerPages/goods-manager-page.scss @@ -0,0 +1,232 @@ +@import '../../assets/style/colors.scss'; + +.zy-ui-cell { + font-size: .32rem; + min-height: 1rem; + display: flex; + padding: 0 .2rem; + background: white; + // justify-content: center; + align-items: center; + border-bottom: 1px solid $gray; + .cell-title { + color: $sliver; + flex-shrink: 0; + width: 2rem; + } + .cell-btn { + flex: 1; + } + .cell-group { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + } + .three { + flex-flow:row wrap; + padding: .1rem 0; + } + .cell-input { + display: flex; + align-items: center; + width: 100%; + input { + padding: .1rem .2rem; + // border: 1px solid $gray; + border: none; + border-radius: .1rem; + flex: 1; + font-size: $black; + outline: none; + } + .suffix { + // flex: 1; + } + } +} +// 说明 +.explain { + font-size: .28rem; + padding: .2rem; + text-align: justify; + text-indent: 2em; + color: $sliver; + p { + line-height: 1.5; + } + .link { + color: $primary; + font-weight: 500; + } +} +// 京西复制到京西 +.jx-to-jx { + .cell-input { + input { + width: 1rem; + flex: initial; + } + .suffix { + // margin-left: .1rem; + // background: $sliver; + padding: .1rem .2rem; + } + .wrong { + border: .04rem dashed $danger; + } + } +} + +// 京西同步到平台 +.jx-to-plat, .reset-goods { + .sync-checkbox { + padding: .2rem .1rem; + } + .sync-option { + padding: .2rem .06rem; + } + .last { + margin-top: .1rem; + } +} + +%upload-btn { + margin-top: 1rem; + height: .9rem; + color: white; + display: flex; + justify-content: center; + align-items: center; + background: $success; + border-radius: .05rem; +} + +// 上传永辉商品表格 +.upload-yh-excel { + font-size: .32rem; + padding: .2rem; + .zy-upload { + .zy-upload-frame { + border: 2px dashed $lightSliver; + padding: .4rem; + font-size: .4rem; + color: $lightSliver; + text-align: center; + border-radius: .1rem; + } + input { + display: none; + } + } + .file-display { + text-align: center; + margin-top: .4rem; + color: $primary; + } + .upload-btn { + @extend %upload-btn; + } +} + +// 下载微盟订单 +.download-wm-order { + font-size: .32rem; + .zy-ui-cell .cell-title { + width: 3rem; + } + .pick-date { + background: $primary; + color: white; + border-radius: .1rem; + padding: .1rem .2rem; + width: 2rem; + text-align: center; + } + .content { + padding: .2rem; + } + .download-btn { + @extend %upload-btn; + margin-top: .6rem; + } + .excel-area { + display: flex; + flex-flow: column; + align-items: center; + $size: 1rem; + .excel-op { + margin: .2rem 0; + display: flex; + align-items: center; + } + .excel-save { + background: $primary; + color: white; + padding: .2rem .6rem; + margin: 0 .2rem; + border-radius: .05rem; + } + .excel-icon { + width: $size; + height: $size; + background-repeat: no-repeat; + background-size: 100%; + background-position: center; + background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cpath d='M1019.52 890.64c-4.67 24.766-33.723 25.597-53.113 26.173-120.497.384-241.186 0-361.81 0V1024h-72.184c-177.257-32.956-354.9-63.48-532.349-95.22V95.348C178.602 63.352 357.139 32.252 535.549 0h69.111v95.348c116.466 0 232.867 0 349.269-.32a105.33 105.33 0 0 1 58.424 11.455 111.474 111.474 0 0 1 11.391 60.216q-.896 310.105 0 619.827a555.643 555.643 0 0 1-3.967 104.115zM423.692 309.21c-26.365 1.28-52.793 2.943-79.094 4.863-19.646 48.89-42.619 96.564-57.913 147.181-14.334-47.738-33.276-93.684-50.617-140.27q-38.396 2.112-76.79 4.543c27.004 60.985 55.736 121.073 81.909 182.378-30.844 59.512-59.705 119.857-89.589 179.753 25.597 1.088 51.194 2.176 76.79 2.56 18.11-47.418 40.7-93.045 56.441-141.295a1439.18 1439.18 0 0 0 58.041 149.55c28.029 1.983 55.93 3.711 84.022 5.439-31.996-66.68-64.76-132.911-96.82-199.591q47.354-97.332 93.876-195.048zm564.793-178.154H604.532v71.415h93.109v83.19h-92.98v47.673h93.108v83.19H604.66v47.61h93.109v83.19H604.66v47.674h93.109v83.19H604.66v47.61h93.109v83.189H604.66v71.415h383.952V131.056zm-81.397 154.732h-162.86v-83.19h162.86v83.19zm0 130.992h-162.86v-83.19h162.86zm0 130.992h-162.86v-83.19h162.86v83.19zm0 130.927h-162.86v-83.19h162.86zm0 130.992h-162.86V726.5h162.86z' fill='%2313CE66'/%3E%3C/svg%3E") + } + .iframe { + width: 100%; + border: 1px solid $lightGray; + height: calc(100vh - 7rem); + } + } + .order-list { + margin-top: .2rem; + height: calc(100vh - 4.4rem); + overflow-y: auto; + .order-item { + display: flex; + align-items: center; + justify-content: space-between; + // margin-bottom: .2rem; + padding: .1rem 0; + border-bottom: 1px solid #ccc; + .top { + display: flex; + justify-content: space-between; + align-items: center; + } + .bottom { + display: flex; + .order-name { + width: 3rem; + } + } + .btn-download { + font-size: .24rem; + color: white; + background: $primary; + padding: .14rem .3rem; + border-radius: .1rem; + &:nth-of-type(1) { + margin-bottom: .2rem; + } + &:nth-of-type(2) { + background: $success; + } + } + } + } +} + +// 微盟价格复制到京西 +.wm-to-jx { + font-size: .32rem; + .content { + padding: .2rem; + } + .submit-btn { + @extend %upload-btn; + } +} diff --git a/src/pages/GoodsManagerPages/upload-yh-excel.js b/src/pages/GoodsManagerPages/upload-yh-excel.js new file mode 100644 index 0000000..5167ae6 --- /dev/null +++ b/src/pages/GoodsManagerPages/upload-yh-excel.js @@ -0,0 +1,93 @@ +import React, { Component } from 'react'; +import PopupPage from '@/components/layout/PopupPage2'; +import './goods-manager-page.scss'; +import fetchJson from '@/utils/fetch'; +import Toast from '@/components/Toast/index.js' +import Loading from '@/components/Loading'; +import Dialog from '@/components/Dialog'; + +class UploadYhExcel extends Component { + constructor (props) { + super(props) + this.state = { + file: null + } + } + fnReturn = () => { + console.log('返回') + this.props.history.goBack(); + } + handleAddFile = () => { + console.log('添加文件') + this.refs.fileUpload.click() + } + fileChange = (e) => { + if (e.target instanceof HTMLInputElement) { + if (e.target.files.length > 1) { + Toast.show('只能选择1个文件') + return false + } + const file = e.target.files[0]; + console.log(file) + // this.setState({file}) + if (file && file.type.indexOf('spreadsheetml.sheet') > -1) { + this.setState({file}) + } else { + Toast.show('文件类型错误') + } + } + } + handleUpload = async () => { + try { + Loading.show() + let form = new FormData() + form.append('userfiles', this.state.file) + form.append('isAsync', true) + form.append('isContinueWhenError', true) + let res = await fetchJson('/v2/yonghui/LoadExcelByYongHui', { + method: 'POST', + body: form + }) + console.log(res) + Dialog.show('成功', '上传成功,请留意钉钉消息关注完成情况', { + showCancel: false + }) + } catch (e) { + console.error(e) + Dialog.showErr(e) + } finally { + Loading.hide() + } + } + render () { + return ( + +
+
+
+ + 选择 excel 文件 +
+ this.fileChange(e)} accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"/> +
+ { + this.state.file && ( +
+
+ 文件名: {this.state.file.name} +
+
+ 上传文件 +
+
+ ) + } +
+
+ ) + } +} + +export default UploadYhExcel; \ No newline at end of file diff --git a/src/pages/HomePage.js b/src/pages/HomePage.js new file mode 100644 index 0000000..f362c5c --- /dev/null +++ b/src/pages/HomePage.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react'; + +class HomePage extends Component { + + render() { + const {path} = this.props.match; + return ( +
+ home +
+ ); + } + +} + +export default HomePage; diff --git a/src/pages/OrderDetail/OrderDetail.js b/src/pages/OrderDetail/OrderDetail.js new file mode 100644 index 0000000..392db22 --- /dev/null +++ b/src/pages/OrderDetail/OrderDetail.js @@ -0,0 +1,564 @@ +import React, { Component } from 'react'; +import './order-detail.scss' +import {connect} from 'react-redux' +import * as dd from 'dingtalk-jsapi'; +import BScroll from 'better-scroll'; +import Loading from '@/components/Loading'; +import fetchJson from '@/utils/fetch'; +import Dialog from '@/components/Dialog'; +import {formatDate, copyText} from '@/utils/tools.js' +import classNames from 'classnames' +import Toast from '@/components/Toast'; +import {setFromOrderDetail} from '@/store/actions.js' + +class OrderDetail extends Component { + constructor(props) { + super(props); + this.state = { + refreshText: '', + scroll: null, + vendorOrderID: '', + order: {} + } + } + async componentDidMount () { + this.props.setFromOrderDetail(true) + // console.log(window.location) + let params = new URLSearchParams(this.props.location.search); + // console.log(params.get('vendorOrderID')) + await this.setState({ + vendorOrderID: params.get('vendorOrderID') + }) + document.title = '订单详情'; + if (dd.ios || dd.android) { + dd.biz.navigation.setTitle({title: '订单详情'}); + } + // 请求数据 + await this.getOrder() + await this.setState({ + refreshText: '下拉刷新', + scroll: new BScroll(this.refs.wrapper, { + click: true, + stopPropagation: true, + // 下拉刷新 + pullDownRefresh: { + threshold: 100, + stop: 100 + }, + bounce: { + // top: true, + bottom: false + }, + // 上拉加载 + // pullUpLoad: { + // threshold: 200 + // } + }) + }); + // 下拉刷新 + this.state.scroll.on('pullingDown', async () => { + console.log('下拉刷新'); + // 请求数据 + await this.getOrder() + this.state.scroll.finishPullDown(); + this.state.scroll.refresh(); + }); + this.state.scroll.on('scroll', pos => { + // console.log(pos); + if (pos.y > 100) { + this.setState({refreshText: '松手刷新'}); + } else { + this.setState({refreshText: '下拉刷新'}); + } + }) + } + + // 查询订单 + getOrder = async () => { + try { + Loading.show() + let {vendorOrderID} = this.state + let {totalCount, data} = await fetchJson(`/v2/order/GetOrders?vendorOrderID=${vendorOrderID}`) + console.log(totalCount, data) + let {stores} = await this.getStore(data[0].jxStoreID) + console.log('门店信息', stores[0]) + this.setState({ + order: this.mapOrder(data[0], stores[0].payPercentage) + }) + } catch (e) { + console.error(e) + Dialog.show('错误', `${e}`, { + showCancel: false, + confirmText: '好的' + }) + } finally { + Loading.hide() + } + } + mapOrder = (json, payPercentage) => { + // const {payPercentage} = this.state + console.log(payPercentage) + let earningPrice = 0 + let expectEarningPrice = 0 + if (!(payPercentage >= 50 && payPercentage <= 100)) { + // 小于50 + earningPrice = ((json.totalShopMoney - json.desiredFee) * (100 - payPercentage / 2) / 100 / 100).toFixed(2) + } else { + // 大于等于50 + earningPrice = (json.earningPrice / 100).toFixed(2) + } + if (!(payPercentage >= 50 && payPercentage <= 100)) { + // 小于50 + // 预计收益=平台结算*门店结算比例/200 + expectEarningPrice = (json.totalShopMoney * payPercentage / 200 / 100).toFixed(2) + } else { + // 大于50 + // 预计收益=平台结算-配送费-预计收益-远距离配送费-京西已加小费 + expectEarningPrice = ((json.totalShopMoney - json.desiredFee - json.earningPrice - json.distanceFreightMoney - json.waybillTipMoney) / 100).toFixed(2) + } + return { + ...json, + orderCreatedAt: formatDate(json.orderCreatedAt, 'YYYY-MM-DD hh:mm:ss'), + distanceFreightMoney: this.toFixed2(json.distanceFreightMoney), + waybillTipMoney: this.toFixed2(json.waybillTipMoney), + shopPrice: this.toFixed2(json.shopPrice) || '0.00', + salePrice: this.toFixed2(json.salePrice) || '0.00', + actualPayPrice: this.toFixed2(json.actualPayPrice) || '0.00', + discountMoney: this.toFixed2(json.discountMoney) || '0.00', + desiredFee: this.toFixed2(json.desiredFee) || '0.00', + earningPrice, + totalShopMoney: this.toFixed2(json.totalShopMoney) || '0.00', + pmSubsidyMoney: this.toFixed2(json.pmSubsidyMoney) || '0.00', + expectEarningPrice, + // deliveryFlag: 3 + // lockStatus: -5 + } + } + // 查询门店 + getStore = async (storeID) => { + try { + let res = await fetchJson('/v2/store/GetStores?storeID=' + storeID) + return res + } catch (e) { + console.error(e) + throw e + } + } + toFixed2 (num) { + return num ? (num / 100).toFixed(2) : 0 + } + copy = (str) => { + console.log(str) + copyText(str, () => { + Toast.show(`[${str}] 复制成功`) + }) + } + // 取消订单 + cancelOrder = () => { + Dialog.show('确认', '是否取消该单', {}, async (confirm) => { + console.log('取消订单') + if (!confirm) return false + try { + const {order} = this.state + Loading.show() + await fetchJson('/v2/order/CancelOrder', { + method: 'PUT', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: `vendorOrderID=${order.vendorOrderID}&vendorID=${order.vendorID}&reason=协商一致` + }) + Toast.show('取消成功') + this.getOrder() + } catch (e) { + Dialog.show('错误', `${e}`, { + showCancel: false, + confirmText: '好的' + }) + } finally { + Loading.hide() + } + }) + } + // 处理取消订单 + dealCancelOrder = () => { + Dialog.show('选择', '选择如何处理该取消订单', { + cancelText: '拒绝', + confirmText: '同意' + }, async (confirm) => { + // console.log('取消订单') + console.log(typeof confirm) + if (typeof confirm === 'boolean') { + let acceptIt = confirm + const {order} = this.state + await this.apiAbnormal('/v2/order/AgreeOrRefuseCancel', `vendorOrderID=${order.vendorOrderID}&vendorID=${order.vendorID}&acceptIt=${acceptIt}&reason=协商一致`) + this.getOrder() + } + }) + } + // 处理取货失败待审核订单 + dealGetGoodsFail = () => { + Dialog.show('选择', '选择如何处理该取货失败订单', { + cancelText: '拒绝', + confirmText: '同意' + }, async (confirm) => { + // console.log('取消订单') + console.log(typeof confirm) + if (typeof confirm === 'boolean') { + let acceptIt = confirm + const {order} = this.state + await this.apiAbnormal('/v2/order/AcceptOrRefuseFailedGetOrder', `vendorOrderID=${order.vendorOrderID}&vendorID=${order.vendorID}&acceptIt=${acceptIt}&reason=协商一致`) + this.getOrder() + } + }) + } + // 召唤平台配送 + reCallPlat = () => { + Dialog.show('确认', '是否召唤平台配送', { + cancelText: '取消', + confirmText: '确定' + }, async (confirm) => { + // console.log('取消订单') + console.log(typeof confirm) + if (confirm) { + const {order} = this.state + await this.apiAbnormal('/v2/order/CallPMCourier', `vendorOrderID=${order.vendorOrderID}&vendorID=${order.vendorID}`) + this.getOrder() + } + }) + } + // 收到退回货物 + reveiveGoods = () => { + Dialog.show('确认', '是否收到退回货物', { + cancelText: '取消', + confirmText: '确定' + }, async (confirm) => { + // console.log('取消订单') + console.log(typeof confirm) + if (confirm) { + const {order} = this.state + await this.apiAbnormal('/v2/order/ConfirmReceiveGoods', `vendorOrderID=${order.vendorOrderID}&vendorID=${order.vendorID}`) + this.getOrder() + } + }) + } + // 封装异常单处理请求 + async apiAbnormal (url, queryString) { + try { + Loading.show() + await fetchJson(url, { + method: 'PUT', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: queryString + }) + Toast.show('操作成功') + } catch (e) { + Dialog.show('错误', `${e}`, { + showCancel: false, + confirmText: '好的' + }) + } finally { + Loading.hide() + } + } + render() { + const {refreshText, order} = this.state + const {cms} = this.props.system + return ( +
+
+
+ { + 'vendorOrderID' in order && ( +
+
+
+
+
+ #{order.orderSeq} +
+ {/* business */} +
+ {order.businessType === 1 ? '立即达' : '定时达'} +
+
+ {/* 状态 */} +
+ {cms.orderStatus && cms.orderStatus[order.status]} +
+
+
+
订单编号:
+
+ {order.vendorOrderID} +
+
+ { + order.vendorOrderID2 && ( +
+
订单编号2:
+
+ {order.vendorOrderID2} +
+
+ ) + } +
+
订单门店:
+
+ {order.storeName}({order.jxStoreID}) +
+
+
+
下单时间:
+
+ {order.orderCreatedAt} +
+
+ {/* 其他几个重要状态 */} + { + order.lockStatus === -5 && ( +
+
其他状态:
+
+ 用户申请取消 + {(order.flag & 6) === 2 && '(已同意)'} + {(order.flag & 6) === 6 && '(已拒绝)'} +
+
+ ) + } + { + order.status === 17 && order.lockStatus === 0 && (order.flag & 24) !== 0 && ( +
+
其他状态:
+
+ 取货失败待审核处理: + {(order.flag & 24) === 8 && '(同意)'} + {(order.flag & 24) === 24 && '(拒绝)'} +
+
+ ) + } + { + order.status === 18 && order.lockStatus === 0 && (order.flag & 64) !== 0 && ( +
+
其他状态:
+
+ 已召唤平台配送 +
+
+ ) + } + { + order.status === 22 && order.lockStatus === 0 && (order.flag & 32) !== 0 && ( +
+
其他状态:
+
+ 已收到货物 +
+
+ ) + } + {/* 其他几个重要状态 */} + { + (order.deliveryFlag & 1) === 1 && ( +
+
运单状态:
+
+ 已停止配送调度 +
+
+ ) + } + { + (order.deliveryFlag & 2) === 2 && ( +
+
运单状态:
+
+ 已转京西自送 +
+
+ ) + } + { + order.lockStatus === -20 && ( +
+
+
+
状态:
+
+ 已锁定 +
+
+
+ ) + } +
+
+
客户姓名:
+
+ {order.consigneeName} +
+
+
+
客户隐私号:
+
+ {order.consigneeMobile} +
+
+
+
客户真实号:
+
+ {order.consigneeMobile2 || '暂无'} +
+
+
+
+
骑手姓名:
+
+ {cms.vendorName && cms.vendorName[order.waybillVendorID]} {order.courierName || '暂无'} +
+
+
+
骑手电话:
+
+ {order.courierMobile || '暂无'} +
+
+
+
运单号:
+
+ {order.vendorWaybillID || '暂无'} +
+
+ { + order.distanceFreightMoney ? ( +
+
远距离配送费:
+
+ ¥{order.distanceFreightMoney} +
+
+ ) : '' + } + { + order.waybillTipMoney ? ( +
+
京西已加运费:
+
+ ¥{order.waybillTipMoney} +
+
+ ) : '' + } +
+
+
商品数量:
+
+ 共 {order.skuCount} 种,{order.goodsCount} 件 +
+
+
+
京西价:
+
+ ¥{order.shopPrice} +
+
+
+
售卖价:
+
+ ¥{order.salePrice} +
+
+
+
实付:
+
+ ¥{order.actualPayPrice} +
+
+
+
订单优惠:
+
+ ¥{order.discountMoney} +
+
+
+
配送费:
+
+ ¥{order.desiredFee} +
+
+
+
门店收益:
+
+ ¥{order.earningPrice} +
+
+
+
平台结算(含补贴):
+
+ ¥{order.totalShopMoney} +
+
+
+
平台补贴:
+
+ ¥{order.pmSubsidyMoney} +
+
+
+
京西收益:
+
+ ¥{order.expectEarningPrice} +
+
+
+ ) + } +
+
{refreshText}
+
+
+ { + order.status < 100 && ( +
取消订单
+ ) + } + { + (order.lockStatus === -5) && (order.flag & 6) === 0 && ( +
处理申请取消
+ ) + } + { + order.status === 17 && (order.flag & 24) === 0 && order.lockStatus === 0 && ( +
处理取货失败待审核
+ ) + } + { + order.status === 18 && (order.flag & 64) === 0 && order.lockStatus === 0 && ( +
召唤平台配送
+ ) + } + { + order.status === 22 && (order.flag & 32) === 0 && order.lockStatus === 0 && ( +
收到退回货物
+ ) + } +
+
+ ); + } +} + +// export default OrderDetail; +export default connect((state, props) => Object.assign({}, props, state), { + setFromOrderDetail +})(OrderDetail) \ No newline at end of file diff --git a/src/pages/OrderDetail/order-detail.scss b/src/pages/OrderDetail/order-detail.scss new file mode 100644 index 0000000..f08bcbf --- /dev/null +++ b/src/pages/OrderDetail/order-detail.scss @@ -0,0 +1,133 @@ +@import '@/assets/style/_colors.scss'; +@import '@/assets/style/_icon.scss'; + +%flex-between { + display: flex; + align-items: center; + justify-content: space-between; +} +%flex-left { + display: flex; + align-items: center; +} +%flex-center { + display: flex; + align-items: center; + justify-content: center; +} +%pd20 { + padding: .2rem; +} + +.order-detail { + font-size: .32rem; + color: $black; + .top-wrapper { + // background: red; + height: calc(100vh - 1rem); + box-sizing: border-box; + overflow: hidden; + position: relative; + } + .bottom-wrapper { + // background: orange; + height: 1rem; + box-sizing: border-box; + border: 1px solid $gray; + @extend %flex-left; + } + .content { + position: absolute; + z-index: 1; + background: white; + // padding: .2rem; + width: 100%; + } + .refreshText { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + height: 1rem; + display: flex; + align-items: center; + justify-content: center; + } + .vendorID { + border-radius: .1rem; + border: 1px solid white; + width: .4rem; + height: .4rem; + background-size: 100%; + background-repeat: no-repeat; + background-position: center; + } + .vendor-icon-0 { + background-image: url(../../assets/imgs/ic_jd.png); + } + .vendor-icon-1 { + background-image: url(../../assets/imgs/ic_mt.png); + } + .vendor-icon-3 { + background-image: url(../../assets/imgs/ic_eb.png); + } + .vendor-icon-9 { + background-image: url(../../assets/imgs/ic_jx.png); + } + .line1 { + @extend %flex-between; + @extend %pd20; + background: $primary; + width: 100%; + color: white; + box-sizing: border-box; + min-height: 1rem; + } + .vendorInfo { + @extend %flex-left; + .seq { + margin-left: .2rem; + font-size: .4rem; + } + .businessType { + margin-left: .2rem; + } + } + .cell1 { + @extend %flex-between; + @extend %pd20; + // background: $primary; + width: 100%; + // color: white; + box-sizing: border-box; + min-height: .9rem; + border-bottom: 1px solid $gray; + div:nth-of-type(1) { + color: $sliver; + } + } + .devider { + height: .1rem; + background: #f4f4f4; + } + .danger { + color: $danger; + } + + .btn { + @extend %pd20; + color: white; + box-sizing: border-box; + height: .9rem; + @extend %flex-center; + margin: 0 .1rem; + border-radius: .1rem; + } + .btn.danger { + background: $danger; + } + .btn.primary { + background: $primary; + } +} diff --git a/src/pages/OrderManager.js b/src/pages/OrderManager.js new file mode 100644 index 0000000..afd0954 --- /dev/null +++ b/src/pages/OrderManager.js @@ -0,0 +1,286 @@ +import React, { Component } from 'react'; +import '@/assets/style/order-manager.scss'; +import BScroll from 'better-scroll'; +import Loading from '@/components/Loading'; +import SearchStore from '@/components/layout/SearchStore'; +import * as dd from 'dingtalk-jsapi'; +import fetchJson from '@/utils/fetch'; +import Dialog from '@/components/Dialog'; +import { formatDate } from '@/utils/tools.js' +import classNames from 'classnames'; +import { connect } from 'react-redux'; +import Toast from '@/components/Toast'; +import { setFromOrderDetail } from '@/store/actions.js' + +class OrderManager extends Component { + constructor(props) { + super(props); + this.state = { + arr: [], + keyword: '', + // fromDate: '', + // toDate: '', + storeIDs: [], + offset: 0, + pageSize: 20, + totalCount: 0, + orderList: [], + page: 1, + scrollY: 0 + } + this.fromDate = formatDate(new Date() - 10 * 24 * 3600 * 1000, 'YYYY-MM-DD') + this.toDate = formatDate(new Date(), 'YYYY-MM-DD') + this.scroll = null + } + componentWillUnmount() { + // this.setState = (state, callback) => { + // return; + // }; + } + async componentDidMount() { + await this.getOrdersAll(1) + console.log('订单管理 mount') + document.title = '订单管理'; + if (dd.ios || dd.android) { + dd.biz.navigation.setTitle({ title: '订单管理' }); + } + // let wrapper = querySelector('.warpper'); + // let scroll = new BScroll('.wrapper', { + // click: true, + // bounce: false + // }); + console.log(this.props.system.fromOrderDetail, '------------------') + if (!this.props.system.fromOrderDetail) { + this.scroll = new BScroll(this.refs.wrapper, { + click: true, + // stopPropagation: true, + // 下拉回弹关闭 + bounce: { + top: false, + bottom: false + }, + // 下拉刷新 + // pullDownRefresh: { + // threshold: 100, + // stop: 100 + // }, + // 上拉加载 + pullUpLoad: { + threshold: 200 + } + }) + + this.scroll.on('scrollEnd', ({ x, y }) => { + console.log(x, y) + this.setState({ scrollY: y }) + }) + // 上拉加载 + this.scroll.on('pullingUp', async () => { + this.scroll.stop(); + console.log('上拉加载更多'); + if (this.state.page * this.state.pageSize < this.state.totalCount) { + await this.setState({ + page: ++this.state.page + }) + await this.getOrders(this.state.page) + this.scroll.finishPullUp(); + this.scroll.refresh(); + } else { + Toast.show('没有更多') + } + }) + } else { + this.props.setFromOrderDetail(false) + console.log(this.state) + } + } + componentDidUpdate() { + this.scroll && this.scroll.refresh(); + } + // 随机生成数据 + productArr(len) { + let arr = new Array(len); + for (let i = 0; i < arr.length; i++) { + arr[i] = Math.round(Math.random() * 100); + } + return arr; + } + // 随机生成颜色 + rdmColor() { + // 0 <= x < 1 + let num = parseInt('ffffff', 16); + let color = Math.floor(Math.random() * num + 1); + return `#${color.toString(16)}`; + } + // 查询订单 + getOrders = async (page) => { + try { + Loading.show() + let { keyword, storeIDs, offset, pageSize, orderList } = this.state + if (page === 1) { + this.setState({ page: 1, orderList: [] }) + } + offset = (page - 1) * pageSize + let { totalCount, data } = await fetchJson(`/v2/order/GetOrders?keyword=${keyword}&fromDate=${this.fromDate}&toDate=${this.toDate}&storeIDs=${JSON.stringify(storeIDs)}&offset=${offset}&pageSize=${pageSize}`) + console.log(totalCount, data) + if (page === 1) { + this.setState({ + totalCount, + orderList: this.mapOrder(data || []) + }) + } else { + // 翻页 + data = data || [] + this.setState({ + totalCount, + orderList: this.mapOrder([...orderList, ...data]) + }) + } + } catch (e) { + console.error(e) + Dialog.show('错误', `${e}`, { + showCancel: false, + confirmText: '好的' + }) + } finally { + Loading.hide() + } + } + getOrdersAll = async (page) => { + try { + Loading.show() + let { keyword, offset, pageSize, orderList } = this.state + if (page === 1) { + this.setState({ page: 1, orderList: [] }) + } + offset = (page - 1) * pageSize + let { totalCount, data } = await fetchJson(`/v2/order/GetOrders?keyword=${keyword}&fromDate=${this.fromDate}&toDate=${this.toDate}&offset=${offset}&pageSize=${pageSize}`) + console.log(totalCount, data) + if (page === 1) { + this.setState({ + totalCount, + orderList: this.mapOrder(data || []) + }) + } else { + // 翻页 + data = data || [] + this.setState({ + totalCount, + orderList: this.mapOrder([...orderList, ...data]) + }) + } + } catch (e) { + console.error(e) + Dialog.show('错误', `${e}`, { + showCancel: false, + confirmText: '好的' + }) + } finally { + Loading.hide() + } + } + mapOrder = (list) => { + return list.map(item => ({ + ...item, + orderCreatedAt: formatDate(item.orderCreatedAt, 'YYYY-MM-DD') + })) + } + fnItemClick(item) { + console.log(item); + this.refs.serachStore.show(); + } + fnChange = (e) => { + console.log(e.target.value) + this.setState({ keyword: e.target.value }) + } + startSearch = () => { + console.log('startSearch') + this.getOrders(1) + } + fnBlur = () => { + console.log('blur') + } + clearKeyword = async () => { + await this.setState({ keyword: '' }) + this.refs.keyword.value = '' + await this.getOrders(1) + } + storePick = async (store) => { + console.log(store.id) + await this.setState({ storeIDs: [store.id] }) + await this.getOrders(1) + } + storeClear = async () => { + await this.setState({ storeIDs: [] }) + await this.getOrders(1) + } + toOrderDetail = (orderID) => { + console.log(orderID) + console.log(this.props.history) + this.props.history.push('/orderdetail?vendorOrderID=' + orderID) + // this.props.history.push('/ordermanger/' + orderID) + } + render() { + const { orderList, keyword } = this.state + const { cms } = this.props.system + return ( +
+ {/* 检索框 */} +
+ {/* 选择门店按钮 */} +
+ {/* 关键字输入框 */} +
+
+ + { + keyword && ( +
+ ) + } +
+
+
+
+ { + orderList.map((order, index) => ( +
+ {/* 下单时间 */} +
+
{order.orderCreatedAt}
+
{order.vendorOrderID}
+
#{order.orderSeq}
+
{cms.orderStatus && cms.orderStatus[order.status]}
+
+ )) + } +
+ {/*
{this.state.refreshText}
*/} + {/*
加载更多
*/} +
+ +
+ ); + } + +} + +// export default connect((state, props) => Object.assign({}, props, state), { +// setStoreSearch +// })(StoreManager); +export default connect((state, props) => Object.assign({}, props, state), { + setFromOrderDetail +})(OrderManager) diff --git a/src/pages/Other.js b/src/pages/Other.js new file mode 100644 index 0000000..e0b1d28 --- /dev/null +++ b/src/pages/Other.js @@ -0,0 +1,15 @@ +import React, { Component } from 'react'; + +class Other extends Component { + + render() { + return ( +
+ 其他测试页面 +
+ ); + } + +} + +export default Other; diff --git a/src/pages/Register.js b/src/pages/Register.js new file mode 100644 index 0000000..bb9bdfe --- /dev/null +++ b/src/pages/Register.js @@ -0,0 +1,121 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import fetchJson from '@/utils/fetch'; +import Form from '@/components/ddauth/Form'; +import '@/assets/style/ddauth.scss'; +import Toast from '@/components/ddauth/Toast'; +import Dialog from '@/components/ddauth/Dialog'; +// 注册页面 + +// console.log(connect) + +class Register extends Component { + constructor (...args) { + super(...args); + this.state = { + debug: '' + } + } + async handleRegister () { + try { + let json = this.refs.form12.getFormData(); + // console.log('1231232', form.get('userID2'), form.get('Name'), form.get('Mobile'), form.get('mobileVerifyCode')); + // let UserID2 = form.get('UserID2'); // 用户名 + // let Name = form.get('Name'); // 昵称 + // let Mobile = form.get('Mobile'); // 手机 + // let mobileVerifyCode = form.get('mobileVerifyCode'); // 验证码 + let {userID2, name, mobile, mobileVerifyCode} = json; + console.log(userID2, name, mobile, mobileVerifyCode); + if (!/^[a-zA-Z0-9_]{4,16}$/.test(userID2)) { + let err = '用户名只能由4至16位英文或数字或下划线组成!'; + throw err; + } else if (!name) { + let err = '请输入昵称!'; + throw err; + } else if (!mobile) { + let err = '请输入手机号!'; + throw err; + } else if (!mobileVerifyCode) { + let err = '请输入验证码!'; + throw err; + } + console.log('验证通过'); + let payload = { + userID2, name, mobile + }; + let form2 = new FormData(); + form2.append('payload', JSON.stringify(payload)); + form2.append('mobileVerifyCode', mobileVerifyCode); + form2.append('authToken', this.props.user.token); + let res = await fetchJson('/v2/user2/RegisterUser', { + method: 'POST', + body: form2 + }) + console.log('注册成功信息', res); + // this.setState({debug: '成功'}); + if (res.hasOwnProperty('code')) { + if (res.code === '-104') { + // 直接绑定 + // let form3 = new FormData(); + let token = res.desc.token; + let authToken = this.props.user.token; + console.log(token, authToken) + // form3.append('authToken', authToken) + await fetchJson('/v2/auth2/AddAuthBind', { + method: 'POST', + headers: { + token, + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: `authToken=${authToken}` + }) + this.props.history.replace('/ddauth'); + } + } else { + this.refs.dialog.show('', '注册成功!', { + showCancel: false, + confirmText: '知道了' + }, res2 => { + if (res2) { + this.props.history.replace('/ddauth'); + // let {Token, name, mobile} = res; + // console.log(Token, mobile, name); + } + }); + } + } catch (e) { + // this.setState({debug: e.toString()}); + this.refs.toast.show(e) + } + } + render () { + return ( +
+

用户注册

+
+
+
+
注 册
+ {/*
+ {this.state.debug} +
*/} + + +
+ ); + } +} + +// export default Register; +export default connect((state, props) => Object.assign({}, props, state), { + +})(Register); diff --git a/src/pages/StoreManager.js b/src/pages/StoreManager.js new file mode 100644 index 0000000..f0e3262 --- /dev/null +++ b/src/pages/StoreManager.js @@ -0,0 +1,345 @@ +import React, { Component } from 'react'; +import fetchJson from '@/utils/fetch'; +import ReactSVG from 'react-svg' +import {connect} from 'react-redux'; +import {setStoreSearch} from '@/store/actions'; +import * as dd from 'dingtalk-jsapi'; +import BScroll from 'better-scroll'; +import classNames from 'classnames'; + +import Dialog from '@/components/Dialog'; +import Loading from '@/components/Loading'; +// import Toast from '@/components/Toast'; +import SVGSearch from '@/assets/svg/icon-search.svg'; +import SVGClear from '@/assets/svg/icon-clear.svg'; +import SVGNoStore from '@/assets/svg/no-store.svg'; +import SVGCreate from '@/assets/svg/icon-create.svg'; +import SVGMore from '@/assets/svg/icon-more.svg'; + +import StoreCell from '@/components/pageCmp/StoreCell'; +import MoreSearch from '@/components/pageCmp/MoreSearch'; +import CreateStore from '@/components/pageCmp/CreateStore'; +import ModifyStore from '@/components/pageCmp/ModifyStore'; + +class StoreManager extends Component { + constructor (...args) { + super(...args); + this.state = { + // search: { + // keyword: '', + // placeID: null, + // statuss: [-1, 0, 1], + // vendorStoreCond: 'and', + // vendorStoreConds: { + // 0: 0, + // 1: 0, + // 3: 0, + // 11: 0 + // }, + // courierStoreCond: 'and', + // courierStoreConds: { + // 101: 0, + // 102: 0 + // }, + // offset: 0, + // pageSize: 30 + // }, + loading: false, + storeList: [], + totalCount: 0, + moreSearchShow: false, + page: 1, + createShow: false, + modifyShow: false, + currentModify: {} + } + this.scroll = null; + } + async componentDidMount () { + // title + document.title = '门店管理'; + if (dd.ios || dd.android) { + dd.biz.navigation.setTitle({title: '门店管理'}); + } + await this.getStores(); + // 滚动监听 + // this.refs.scrollList.addEventListener('scroll', this.handleScroll.bind(this)); + // ---------- 滚动 ---------- + this.scroll = new BScroll(this.refs.scrollList, { + click: true, + stopPropagation: true, + useTransition: false, + // 下拉回弹关闭 + bounce: { + top: false, + // bottom: false + }, + // 下拉刷新 + // pullDownRefresh: { + // threshold: 100, + // stop: 100 + // }, + // 上拉加载 + pullUpLoad: { + threshold: 200 + } + }); + this.scroll.on('pullingUp', async () => { + console.log('上拉加载'); + Loading.show(); + this.scroll.stop(); + // 翻页 + await this.nextPage(); + this.scroll.finishPullUp(); + Loading.hide(); + }) + // ---------- 滚动 ---------- + } + componentDidUpdate () { + this.scroll && this.scroll.refresh(); + } + componentWillUnmount () { + // this.refs.scrollList.removeEventListener('scroll', this.handleScroll.bind(this)); + } + // 组合检索条件 + mapSearch (type) { + let search = JSON.parse(JSON.stringify(this.props.search.storeSearch)); + let arr = []; + if (type === 'append') { + // 分页 + // 页码++ + this.setState((prev) => { + return { + page: ++prev.page + } + }); + search.offset = (this.state.page - 1) * this.props.search.storeSearch.pageSize; + } else { + this.setState({page: 1}); + } + for (let key in search) { + // value 为真 + if (search[key]) { + if (typeof search[key] === 'object') { + arr.push(`${key}=${JSON.stringify(search[key])}`); + } else { + arr.push(`${key}=${search[key]}`); + } + } + } + return arr.join('&'); + } + // 获取门店列表 + async getStores (type) { + Loading.show(); + await this.setState({loading: true}); + try { + let {totalCount, stores} = await fetchJson('/v2/store/GetStores?' + this.mapSearch(type)); + this.setState({ + totalCount + }); + if (type === 'append') { + this.setState({ + storeList: [ + ...this.state.storeList, + ...stores + ] + }); + } else { + // 滚动到顶部 + this.scroll && this.scroll.scrollTo(0, 0, 0); + this.setState({ + storeList: stores || [] + }); + } + console.log(totalCount, stores); + } catch (e) { + Dialog.show('错误', `${e}`, { + showCancel: false, + confirmText: '好的' + }); + } finally { + Loading.hide(); + // 重新计算滚动区域 + this.scroll && this.scroll.refresh(); + this.setState({loading: false}); + } + } + // 更多检索条件 + handleMore () { + console.log('弹出更多检索条件'); + this.setState({moreSearchShow: true}); + } + // 关键字改变 + async fnChange (e) { + await this.props.setStoreSearch({keyword: e.target.value}); + } + // 回车查询 + async startSearch (e) { + if (e.charCode === 13) { + await this.getStores(); + this.refs.keyword.blur(); + } + } + async fnBlur () { + await this.getStores(); + } + // 清除关键字 + async fnClear () { + await this.props.setStoreSearch({keyword: ''}); + this.refs.keyword.value = ''; + await this.getStores(); + } + // 更多条件确定 + async moreSearchConfim () { + this.setState({moreSearchShow: false}); + await this.getStores(); + } + // 翻页 + async nextPage () { + if (this.props.search.storeSearch.pageSize * this.state.page >= this.state.totalCount) { + // 没有更多了 + // this.setState({noMore: true}); + } else { + // 还可以请求 + if (!this.state.loading) await this.getStores('append'); + } + } + // 滚动 + // async handleScroll (e) { + // // 让input失去焦点 + // this.refs.keyword.blur(); + // // console.log(e) + // let scrollTop = e.target.scrollTop; + // let scrollH = e.target.scrollHeight; + // let divH = e.target.offsetHeight; + // if (scrollH - divH <= scrollTop + 500) { + // if (this.props.search.storeSearch.pageSize * this.state.page >= this.state.totalCount) { + // // 没有更多了 + // // this.setState({noMore: true}); + // } else { + // // 还可以请求 + // if (!this.state.loading) await this.getStores('append'); + // } + // } + // } + // 创建门店弹出 + fnCreate () { + console.log('创建弹出'); + this.setState({createShow: true}); + } + // 操作创建门店 + async fnCreateStore () { + console.log('开始创建门店'); + this.setState({createShow: false}); + await this.getStores(); + } + // 编辑门店弹出 + fnModify (shop) { + // console.log('编辑门店弹出', shop); + this.setState({modifyShow: true, currentModify: shop}); + } + // 编辑返回刷新 + async handleUpdate () { + console.log('刷新数据'); + try { + Loading.show(); + let {stores} = await fetchJson('/v2/store/GetStores?storeID=' + this.state.currentModify.id); + console.log('当前门店', stores[0]); + // await this.setState({shop: stores[0]}); + let store = stores[0]; + let index = this.state.storeList.findIndex(item => item.id === this.state.currentModify.id); + let arr = Object.assign([], this.state.storeList); + arr[index] = store; + this.setState({ + storeList: arr + }); + } catch (e) { + Dialog.show('错误', e, {showCancel: false}); + } finally { + Loading.hide(); + this.setState({modifyShow: false, currentModify: {}}); + } + } + render() { + return ( +
+ {/* 检索框 */} +
+ {/* 新建按钮 */} +
+ +
+ {/* 更多检索条件 */} +
+ +
+ {/* 关键字输入框 */} +
+ + + { + this.props.search.storeSearch.keyword && ( + + ) + } +
+
+ {/* body */} + {/* height100vh-200px */} +
+
+ { + this.state.storeList.length > 0 && this.state.storeList.map(shop => ( + + )) + } + { + (this.state.storeList.length === 0 && !this.state.loading) && ( +
+ +
没有检索到门店
+
+ ) + } +
+
+ {/* 更多检索条件 */} + { + this.state.moreSearchShow && ( + { + this.setState({moreSearchShow: false}) + }} + fnConfirm={this.moreSearchConfim.bind(this)} + > + ) + } + {/* 创建门店 */} + { + this.state.createShow && ( + { + this.setState({createShow: false}) + }} + fnConfirm={this.fnCreateStore.bind(this)} + > + ) + } + { + this.state.modifyShow && ( + + ) + } +
+ ); + } + +} + +export default connect((state, props) => Object.assign({}, props, state), { + setStoreSearch +})(StoreManager); diff --git a/src/pages/UserCenter.js b/src/pages/UserCenter.js new file mode 100644 index 0000000..5ca184d --- /dev/null +++ b/src/pages/UserCenter.js @@ -0,0 +1,15 @@ +import React, { Component } from 'react'; + +class UserCenter extends Component { + + render() { + return ( +
+ 个人中心 +
+ ); + } + +} + +export default UserCenter; diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..25bb8b9 --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,52 @@ +// 引入图标 +import SVGStore from '@/assets/svg/icon-store.svg'; +import SVGGoods from '@/assets/svg/icon-goods.svg'; +import SVGOrder from '@/assets/svg/icon-order.svg'; +// import SVGActivity from '@/assets/svg/icon-activity.svg'; +// import SVGUser from '@/assets/svg/icon-user.svg'; + +// 引入组件 +import StoreManager from '@/pages/StoreManager'; +import GoodsManager from '@/pages/GoodsManager'; +import OrderManager from '@/pages/OrderManager'; +// import ActivityManager from '@/pages/ActivityManager'; +// import UserCenter from '@/pages/UserCenter'; + +// 引入商品管理组件 +import JxToJx from '@/pages/GoodsManagerPages/JxToJx'; +import JxToPlat from '@/pages/GoodsManagerPages/JxToPlat'; +import ResetGoods from '@/pages/GoodsManagerPages/ResetGoods'; +import UploadYHExcel from '@/pages/GoodsManagerPages/upload-yh-excel.js'; +import WmToJx from '@/pages/GoodsManagerPages/WmToJx.js'; +// import DownloadWMOrder from '@/pages/GoodsManagerPages/download-wm-order.js'; +// import DeletePlatGoods from '@/pages/GoodsManagerPages/DeletePlatGoods'; +import AuditManager from '@/pages/GoodsManagerPages/aduit-manager.js' + +// 订单详情 +import OrderDetail from '@/pages/OrderDetail/OrderDetail.js' + +// 底部主菜单 +export const IndexList = [ + {to: '/', src: SVGStore, text: '门店管理', component: StoreManager}, + {to: '/goodsmanager', src: SVGGoods, text: '商品管理', component: GoodsManager}, + {to: '/ordermanager', src: SVGOrder, text: '订单管理', component: OrderManager} + // {to: '/activitymanager', src: SVGActivity, text: '活动管理', component: ActivityManager}, + // {to: '/usercenter', src: SVGUser, text: '个人中心', component: UserCenter} +]; + +// 门店管理菜单 +export const goodsManager = [ + {to: '/auditmanager', text: '价格审核', component: AuditManager}, + {to: '/jxtojx', text: '京西门店商品复制', component: JxToJx}, + {to: '/jxtoplat', text: '京西门店商品同步', component: JxToPlat}, + {to: '/resetgoods', text: '初始化平台门店商品', component: ResetGoods}, + {to: '/uploadyhexcel', text: '上传永辉商品表格', component: UploadYHExcel}, + {to: '/wmtojx', text: '微盟价格复制到京西', component: WmToJx}, + // {to: '/downloadwmorder', text: '下载微盟订单', component: DownloadWMOrder}, + // {to: '/deleteplatgoods', text: '删除平台商品', component: DeletePlatGoods} +]; + +// 订单管理 +export const orderManager = [ + {to: '/orderdetail', text: '京西门店商品复制', component: OrderDetail} +] diff --git a/src/setupProxy.js b/src/setupProxy.js new file mode 100644 index 0000000..fd7b015 --- /dev/null +++ b/src/setupProxy.js @@ -0,0 +1,28 @@ +const proxy = require('http-proxy-middleware'); +module.exports = function (app) { + app.use( + proxy( + '/v2/', + { + target: 'http://www.jxc4.com/beta/v2', + changeOrigin: true, + pathRewrite: { + '^/v2/': '/' + } + } + ) + ); + app.use( + proxy( + '/v1/', + { + target: 'http://www.jxc4.com/v2', + changeOrigin: true, + pathRewrite: { + '^/v1/': '/' + } + } + ) + ); + // app.use()... +} diff --git a/src/store/actions.js b/src/store/actions.js new file mode 100644 index 0000000..597a39a --- /dev/null +++ b/src/store/actions.js @@ -0,0 +1,86 @@ +export const SET_TOKEN = 'set_token'; +export const SET_TEL = 'set_tel'; +export const SET_NICKNAME = 'set_nickName'; +export const SET_LOGIN = 'set_login'; + +export const SET_CMS = 'set_cms'; +export const SET_CITYLEVEL2 = 'set_citylevel2'; +export const SET_VENDORORGCODE = 'set_vendorOrgCode' +export const SET_FROMORDERDETAIL = 'set_fromOrderDetail' +export const SET_ROOT = 'SET_ROOT' + +// 设置token +export function setToken (token) { + return { + type: SET_TOKEN, + token + } +}; +export const setTel = (tel) => { + return { + type: SET_TEL, + tel + } +}; +export const setNickName = (nickName) => { + return { + type: SET_NICKNAME, + nickName + } +}; +export const setLogin = (boo) => { + return { + type: SET_LOGIN, + boo + } +} + +// 设置CMS +export function setCMS (json) { + return { + type: SET_CMS, + json + } +}; +// 设置Root +export function setRoot (json) { + return { + type: SET_ROOT, + json + } +}; + +// 设置平台账号 +export function setVendorOrgCode (json) { + return { + type: SET_VENDORORGCODE, + json + } +} + +// 设置市级城市 +export function setCityLevel2 (arr) { + return { + type: SET_CITYLEVEL2, + arr + } +}; + +/* ------ 门店检索 ------ */ +export const SET_STORESEARCH = 'set_store_search'; + +export function setStoreSearch (json) { + return { + type: SET_STORESEARCH, + json + } +}; + +/* ------ 门店检索 ------ */ + +export function setFromOrderDetail (boo) { + return { + type: SET_FROMORDERDETAIL, + boo + } +} diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..3b98f45 --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,8 @@ +import {createStore, combineReducers} from 'redux'; +import user from './user'; +import system from './system'; +import search from './search'; + +export default createStore(combineReducers({ + user, system, search +})); diff --git a/src/store/search.js b/src/store/search.js new file mode 100644 index 0000000..62015eb --- /dev/null +++ b/src/store/search.js @@ -0,0 +1,39 @@ +import * as types from './actions'; + +export default (state = { + storeSearch: { + keyword: '', + placeID: null, + statuss: [-2, -1, 0, 1], + vendorStoreCond: 'and', + vendorStoreConds: { + 0: '0', + 1: '0', + 3: '0', + 9: '0', + 11: '0' + }, + courierStoreCond: 'and', + courierStoreConds: { + 101: 0, + 102: 0 + }, + offset: 0, + pageSize: 30 + } +}, action) => { + switch (action.type) { + case types.SET_STORESEARCH: + return { + ...state, + storeSearch: { + ...state.storeSearch, + ...action.json + } + }; + default: + return { + ...state + }; + }; +}; diff --git a/src/store/system.js b/src/store/system.js new file mode 100644 index 0000000..0791636 --- /dev/null +++ b/src/store/system.js @@ -0,0 +1,43 @@ +import * as types from './actions'; + +export default (state = { + cms: {}, + cityLevel2: [], + vendorOrgCode: {}, // {"0":["320406"],"1":["589"],"3":["34665"]} + fromOrderDetail: false, + yunyingMap: {}, + memberMap: {} +}, action) => { + switch (action.type) { + case types.SET_CMS: + return { + ...state, + cms: action.json + }; + case types.SET_CITYLEVEL2: + return { + ...state, + cityLevel2: [...action.arr] + } + case types.SET_VENDORORGCODE: + return { + ...state, + vendorOrgCode: action.json + } + case types.SET_FROMORDERDETAIL: + return { + ...state, + fromOrderDetail: action.boo + } + case types.SET_ROOT: + return { + ...state, + yunyingMap: action.json.yunyingMap, + memberMap: action.json.memberMap + } + default: + return { + ...state + }; + } +} diff --git a/src/store/user.js b/src/store/user.js new file mode 100644 index 0000000..ca1a98a --- /dev/null +++ b/src/store/user.js @@ -0,0 +1,36 @@ +import * as types from './actions'; +import {DEBUG} from '@/config.js'; + +export default (state = { + token: '', + tel: DEBUG ? '13684045763' : '', + nickName: '', + login: false +}, action) => { + switch (action.type) { + case types.SET_TOKEN: + return { + ...state, + token: action.token + }; + case types.SET_TEL: + return { + ...state, + tel: action.tel + }; + case types.SET_NICKNAME: + return { + ...state, + nickName: action.nickName + }; + case types.SET_LOGIN: + return { + ...state, + login: action.boo + }; + default: + return { + ...state + }; + } +}; diff --git a/src/utils/fetch.js b/src/utils/fetch.js new file mode 100644 index 0000000..ae4a9a8 --- /dev/null +++ b/src/utils/fetch.js @@ -0,0 +1,102 @@ +// import * as config from '@/config'; +import Dialog from '@/components/Dialog'; +import {setCookie, getCookie} from '@/utils/tools'; +import {apiEdition} from '@/config' +// import {yunyingMap, memberMap} from '@/config.js'; + +export default async (url, options) => { + try { + // 处理url服务器切换 + if (apiEdition === 'v1') { + url = apiEdition + '/' + url.replace('v2/', '') + } + console.log(url) + // 从 store 里面取得 token + // const token = store.getState().user.token; + const token = getCookie('token'); + console.log(token); + let headers = { + token + } + if (options && options.headers) headers = { + token, + ...options.headers + } + if (url.includes('/store/GetStores') || url.includes('sku/GetStoreSkuAudit')) { + // console.log('/store/GetStores/store/GetStores', store.getState().user.tel, options, url) + // const mobile = store.getState().user.tel + // const yunyingMap = store.getState().system.yunyingMap + // const memberMap = store.getState().system.memberMap + // console.log(yunyingMap, memberMap) + // if (mobile in yunyingMap) {} else { + // url += `&marketManPhone=${mobile in memberMap ? memberMap[mobile] : mobile}` + // } + } + let res = await fetch(url, { + ...options, + headers + }); + let {code, data, desc} = await res.json(); + // if (url.indexOf('UpdateStore') !== -1) { + // code = '-105' + // desc = '本地数据修改成功,但同步失败,请根据错误提示处理!,同步错误信息:[{"商品ID":0,"分类名":"","门店ID":2,"平台名":"美团外卖","平台商品ID":"","商品nameID":0,"平台价":0,"同步类型":"同步门店","错误信息":"线上测试门店不允许调用poi/save接口"},{"商品ID":0,"分类名":"","门店ID":2,"平台名":"美团外卖","平台商品ID":"","商品nameID":0,"平台价":0,"同步类型":"同步门店","错误信息":"线上测试门店不允许调用poi/save接口"},{"商品ID":0,"分类名":"","门店ID":2,"平台名":"美团外卖","平台商品ID":"","商品nameID":0,"平台价":0,"同步类型":"同步门店","错误信息":"线上测试门店不允许调用poi/save接口"},{"商品ID":0,"分类名":"","门店ID":2,"平台名":"美团外卖","平台商品ID":"","商品nameID":0,"平台价":0,"同步类型":"同步门店","错误信息":"线上测试门店不允许调用poi/save接口"},{"商品ID":0,"分类名":"","门店ID":2,"平台名":"美团外卖","平台商品ID":"","商品nameID":0,"平台价":0,"同步类型":"同步门店","错误信息":"线上测试门店不允许调用poi/save接口"},{"商品ID":0,"分类名":"","门店ID":2,"平台名":"美团外卖","平台商品ID":"","商品nameID":0,"平台价":0,"同步类型":"同步门店","错误信息":"线上测试门店不允许调用poi/save接口"}]' + // data = '' + // } + if (code === '0') { + // 成功 + return data ? JSON.parse(data) : data; + } else { + // 失败 + if (code === '-2') { + // token失效 + console.error(desc) + Dialog.show('错误', desc, { + showCancel: false, + // cancelText: '', + confirmText: '重新登录' + }, res => { + // 跳转重新登录 + // 删除token + setCookie('token', '', -1); + // hash路由跳转到钉钉授权页面 + window.location.hash = '#/ddauth'; + console.log(res); + }); + } else if (code === '-105') { + // 同步失败 + console.log(desc) + throw deal105(desc) + } else if (code === '-104') { + return { + code, + desc: JSON.parse(desc) + }; + } else { + throw desc; + } + } + } catch (e) { + if (e.message) { + throw(e.message); + } else { + throw(e); + } + } +} + +function deal105 (desc) { + let html = '' + // let title = desc.substring(0, desc.indexOf(':')) + let data = JSON.parse(desc.substring(desc.indexOf(':') + 1)) + // console.log(title, data) + html += '
' + data.forEach(item => { + html += '' + }) + html += '
' + return html +} diff --git a/src/utils/getRoot.js b/src/utils/getRoot.js new file mode 100644 index 0000000..4045c7a --- /dev/null +++ b/src/utils/getRoot.js @@ -0,0 +1,33 @@ +import store from '@/store' + +export const getRoot = async () => { + console.log(store) + let root = { + yunyingMap: { + '18048531223': '石锋', + '13684045763': '周扬', + '18160030913': '苏尹岚' + }, + memberMap: {} + } + try { + let res = await fetch('/root.json?r=' + Date.now(), { + headers: { + 'Content-Type': 'application/json;charset=utf-8' + } + }) + root = await res.json() + console.log(`%c 成功 root.json`, `color: orange;`, root) + store.dispatch({ + type: 'SET_ROOT', + json: root + }) + } catch (e) { + console.error(e) + store.dispatch({ + type: 'SET_ROOT', + json: root + }) + console.log(`%c 失败 root.json`, `color: orange;`, root) + } +} diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..514f1a8 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,93 @@ +// 防抖 +/** + * + * @param {function} func - 防抖的函数 + * @param {number} wait - 等待时间 + * @param {boolean} immediate - 是否立即执行 + */ +export function debounce(func, wait, immediate) { + var timeout, result; + var debounced = function () { + var context = this; + var args = arguments; + + if (timeout) clearTimeout(timeout); + if (immediate) { + // 如果已经执行过,不再执行 + var callNow = !timeout; + timeout = setTimeout(function () { + timeout = null; + }, wait) + if (callNow) result = func.apply(context, args) + } + else { + timeout = setTimeout(function () { + func.apply(context, args) + }, wait); + } + return result; + }; + + debounced.cancel = function () { + clearTimeout(timeout); + timeout = null; + }; + + return debounced; +} + +// 节流 +/* + 三种用法 + throttle(func, wait) + throttle(func, wait, {leading: false}) + throttle(func, wait, {trailing: false}) +*/ +/** + * 节流 + * + * @param {function} func - 节流的函数 + * @param {number} wait - 等待时间,单位毫秒 + * @param {object} options - {leading: false} {trailing: false} 二选一 + * leading:false 刚开始不触发,到达等待时间后才触发 + * trailing:false 刚开始触发,到达等待时间后不触发 + */ +export function throttle(func, wait, options) { + var timeout, context, args; + var previous = 0; + if (!options) options = {}; + + var later = function () { + previous = options.leading === false ? 0 : +new Date(); + timeout = null; + func.apply(context, args); + if (!timeout) context = args = null; + }; + + var throttled = function () { + var now = +new Date(); + if (!previous && options.leading === false) previous = now; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = now; + func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + }; + + throttled.cancel = function () { + clearTimeout(timeout); + previous = 0; + timeout = null; + }; + + return throttled; +} \ No newline at end of file diff --git a/src/utils/mapData.js b/src/utils/mapData.js new file mode 100644 index 0000000..bab6d8f --- /dev/null +++ b/src/utils/mapData.js @@ -0,0 +1,14 @@ +// 重组城市数据 +export const mapPlaces = (arr) => { + let tem = []; + tem = arr.map(item => { + return { + code: item.code, + id: item.id, + level: item.level, + name: item.name, + parentCode: item.parentCode + }; + }); + return tem; +}; diff --git a/src/utils/projectTools.js b/src/utils/projectTools.js new file mode 100644 index 0000000..f5622b0 --- /dev/null +++ b/src/utils/projectTools.js @@ -0,0 +1,29 @@ +// 处理营业时间 +export const dealOpenTime = (str) => { + if (str === 0) return '无' + str = str.toString(); + if (str.length < 2) str = '0' + str; + if (str.length < 3) str = '0' + str; + if (str.length < 4) str = '0' + str; + + return str.slice(0, 2) + ':' + str.slice(2, 4); +} + +export const businessHours = () => { + const timeList = [ + // {value: 600, label: '06:00'}, + ]; + // 生成 timeList + for (let i = 1; i < 48; i++) { + timeList.push({value: 0 + (Math.ceil(i / 2) * 30 + Math.floor(i / 2) * 70), label: dealOpenTime(0 + (Math.ceil(i / 2) * 30 + Math.floor(i / 2) * 70))}); + } + timeList.unshift({ + value: 1, + label: '00:01' + }); + timeList.unshift({ + value: 0, + label: '无' + }); + return timeList; +} diff --git a/src/utils/regExp.js b/src/utils/regExp.js new file mode 100644 index 0000000..a62f707 --- /dev/null +++ b/src/utils/regExp.js @@ -0,0 +1,20 @@ +/* + 正则表 +*/ +// 判断是否是纯数字 +export const isNum = str => /^\d+$/.test(str); + +// 判断是否为空 +export const isEmpty = str => str.trim(); + +// 判断手机是否合法 +export const isTel = str => /^\d{11}$/.test(str); + +// 判断是否存在,存在则校验是否是合法手机 +export const isTel2 = str => { + if (!str) { + return true; + } else { + return /^\d{11}$/.test(str); + } +}; diff --git a/src/utils/tools.js b/src/utils/tools.js new file mode 100644 index 0000000..f3f1d8f --- /dev/null +++ b/src/utils/tools.js @@ -0,0 +1,112 @@ +// 正则提取网址的参数 by name +export const getQueryString = (name) => { + var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)'); + var r = window.location.search.substr(1).match(reg); + if (r != null) return unescape(r[2]); return null; +} + +// 设置cookie +export const setCookie = (name, value, day) => { + if (day) { + let nowDate = new Date(); + // 得到过期时间 + nowDate.setDate(nowDate.getDate() + day); + document.cookie = `${name}=${value};expires=${nowDate}`; + } else { + document.cookie = `${name}=${value}`; + } +} + +// 获取cookie +export const getCookie = name => { + try { + let arr = document.cookie.split(';'); + let tmpVal = ''; + arr.forEach(item => { + let key = item.split('=')[0].trim(); + if (key === name) { + let val = item.split('=')[1]; + tmpVal = val; + } + }) + return tmpVal; + } catch (e) { + return ''; + } +} + +export const stopScroll = () => { + // let content = document.querySelector('.popup-content'); + // let content2 = document.querySelector('.modify-body'); + // if (content) { + // content.style['overflow-y'] = 'hidden'; + // } + // if (content2) content2.style['overflow-y'] = 'hidden'; +} +export const startScroll = () => { + // let content = document.querySelector('.popup-content'); + // let content2 = document.querySelector('.modify-body'); + // if (content) content.style['overflow-y'] = 'auto'; + // if (content2) content2.style['overflow-y'] = 'auto'; +} + +// 数组去重 +export const removeArrDuplicate = arr => Array.from(new Set(arr)); + +/** + * 格式化时间 + * @param {String|Number} oldDate 传入的日期 + * @param {String|Null} fmt 格式:YYYY-MM-DD hh:mm:ss + * formatDate(time, 'YYYY-MM-DD hh:mm:ss') + */ +export function formatDate (oldDate, fmt) { + let date = new Date() + if (oldDate || typeof oldDate === 'string' || typeof oldDate === 'number') { + date = new Date(oldDate) + } + + const len2 = (num) => { + return num < 10 ? '0' + num : '' + num + } + + const obj = { + YYYY: date.getFullYear(), + YY: (date.getFullYear() + '').substring(2, 4) * 1, + MM: len2(date.getMonth() + 1), + DD: len2(date.getDate()), + hh: len2(date.getHours()), + mm: len2(date.getMinutes()), + ss: len2(date.getSeconds()) + } + + if (fmt) { + if (/YYYY/.test(fmt)) { + fmt = fmt.replace('YYYY', obj.YYYY) + } else { + fmt = fmt.replace('YY', obj.YY) + } + fmt = fmt.replace('MM', obj.MM) + fmt = fmt.replace('DD', obj.DD) + fmt = fmt.replace('hh', obj.hh) + fmt = fmt.replace('hh', obj.hh) + fmt = fmt.replace('mm', obj.mm) + fmt = fmt.replace('mm', obj.mm) + fmt = fmt.replace('ss', obj.ss) + } else { + fmt = `${obj.YYYY}-${obj.MM}-${obj.DD}` + } + return fmt +} + +// 将数据复制到剪切板 +export function copyText (text, fn) { + // text: 要复制的内容, callback: 回调 + let tag = document.createElement('input') + tag.setAttribute('id', 'cp_hgz_input') + tag.value = text + document.body.appendChild(tag) + document.getElementById('cp_hgz_input').select() + document.execCommand('copy') + document.getElementById('cp_hgz_input').remove() + fn && fn() +}