| @@ -0,0 +1,9 @@ | |||
| { | |||
| "presets": [ | |||
| ["env", { | |||
| "modules": false | |||
| }], | |||
| "stage-2" | |||
| ], | |||
| "plugins": ["transform-runtime"] | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| root = true | |||
| [*] | |||
| charset = utf-8 | |||
| indent_style = space | |||
| indent_size = 2 | |||
| end_of_line = lf | |||
| insert_final_newline = true | |||
| trim_trailing_whitespace = true | |||
| @@ -0,0 +1,13 @@ | |||
| .DS_Store | |||
| node_modules/ | |||
| dist/ | |||
| npm-debug.log | |||
| yarn-error.log | |||
| # Editor directories and files | |||
| .idea | |||
| .vscode | |||
| *.suo | |||
| *.ntvs* | |||
| *.njsproj | |||
| *.sln | |||
| @@ -0,0 +1,9 @@ | |||
| // https://github.com/michael-ciniawsky/postcss-load-config | |||
| module.exports = { | |||
| "plugins": { | |||
| // to edit target browsers: use "browserslist" field in package.json | |||
| "postcss-import": {}, | |||
| "autoprefixer": {} | |||
| } | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| # posweb | |||
| > A Vue.js project | |||
| ## Build Setup | |||
| ``` bash | |||
| # install dependencies | |||
| npm install | |||
| # serve with hot reload at localhost:8080 | |||
| npm run dev | |||
| # build for production with minification | |||
| npm run build | |||
| # build for production and view the bundle analyzer report | |||
| npm run build --report | |||
| ``` | |||
| For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). | |||
| @@ -0,0 +1,41 @@ | |||
| 'use strict' | |||
| require('./check-versions')() | |||
| process.env.NODE_ENV = 'production' | |||
| const ora = require('ora') | |||
| const rm = require('rimraf') | |||
| const path = require('path') | |||
| const chalk = require('chalk') | |||
| const webpack = require('webpack') | |||
| const config = require('../config') | |||
| const webpackConfig = require('./webpack.prod.conf') | |||
| const spinner = ora('building for production...') | |||
| spinner.start() | |||
| rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { | |||
| if (err) throw err | |||
| webpack(webpackConfig, function (err, stats) { | |||
| spinner.stop() | |||
| if (err) throw err | |||
| process.stdout.write(stats.toString({ | |||
| colors: true, | |||
| modules: false, | |||
| children: false, | |||
| chunks: false, | |||
| chunkModules: false | |||
| }) + '\n\n') | |||
| if (stats.hasErrors()) { | |||
| console.log(chalk.red(' Build failed with errors.\n')) | |||
| process.exit(1) | |||
| } | |||
| console.log(chalk.cyan(' Build complete.\n')) | |||
| console.log(chalk.yellow( | |||
| ' Tip: built files are meant to be served over an HTTP server.\n' + | |||
| ' Opening index.html over file:// won\'t work.\n' | |||
| )) | |||
| }) | |||
| }) | |||
| @@ -0,0 +1,49 @@ | |||
| 'use strict' | |||
| const chalk = require('chalk') | |||
| const semver = require('semver') | |||
| const packageConfig = require('../package.json') | |||
| const shell = require('shelljs') | |||
| function exec (cmd) { | |||
| return require('child_process').execSync(cmd).toString().trim() | |||
| } | |||
| const versionRequirements = [ | |||
| { | |||
| name: 'node', | |||
| currentVersion: semver.clean(process.version), | |||
| versionRequirement: packageConfig.engines.node | |||
| } | |||
| ] | |||
| if (shell.which('npm')) { | |||
| versionRequirements.push({ | |||
| name: 'npm', | |||
| currentVersion: exec('npm --version'), | |||
| versionRequirement: packageConfig.engines.npm | |||
| }) | |||
| } | |||
| module.exports = function () { | |||
| const warnings = [] | |||
| for (let i = 0; i < versionRequirements.length; i++) { | |||
| const mod = versionRequirements[i] | |||
| if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { | |||
| warnings.push(mod.name + ': ' + | |||
| chalk.red(mod.currentVersion) + ' should be ' + | |||
| chalk.green(mod.versionRequirement) | |||
| ) | |||
| } | |||
| } | |||
| if (warnings.length) { | |||
| console.log('') | |||
| console.log(chalk.yellow('To use this template, you must update following to modules:')) | |||
| console.log() | |||
| for (let i = 0; i < warnings.length; i++) { | |||
| const warning = warnings[i] | |||
| console.log(' ' + warning) | |||
| } | |||
| console.log() | |||
| process.exit(1) | |||
| } | |||
| } | |||
| @@ -0,0 +1,90 @@ | |||
| require('./check-versions')() | |||
| var config = require('../config') | |||
| if (!process.env.NODE_ENV) { | |||
| process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) | |||
| } | |||
| var opn = require('opn') | |||
| var path = require('path') | |||
| var express = require('express') | |||
| var webpack = require('webpack') | |||
| var proxyMiddleware = require('http-proxy-middleware') | |||
| var webpackConfig = require('./webpack.dev.conf') | |||
| // default port where dev server listens for incoming traffic | |||
| var port = process.env.PORT || config.dev.port | |||
| // automatically open browser, if not set will be false | |||
| var autoOpenBrowser = !!config.dev.autoOpenBrowser | |||
| // Define HTTP proxies to your custom API backend | |||
| // https://github.com/chimurai/http-proxy-middleware | |||
| var proxyTable = config.dev.proxyTable | |||
| var app = express() | |||
| var compiler = webpack(webpackConfig) | |||
| var devMiddleware = require('webpack-dev-middleware')(compiler, { | |||
| publicPath: webpackConfig.output.publicPath, | |||
| quiet: true | |||
| }) | |||
| var hotMiddleware = require('webpack-hot-middleware')(compiler, { | |||
| log: () => {} | |||
| }) | |||
| // force page reload when html-webpack-plugin template changes | |||
| compiler.plugin('compilation', function (compilation) { | |||
| compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { | |||
| hotMiddleware.publish({ action: 'reload' }) | |||
| cb() | |||
| }) | |||
| }) | |||
| // proxy api requests | |||
| Object.keys(proxyTable).forEach(function (context) { | |||
| var options = proxyTable[context] | |||
| if (typeof options === 'string') { | |||
| options = { target: options } | |||
| } | |||
| app.use(proxyMiddleware(options.filter || context, options)) | |||
| }) | |||
| // handle fallback for HTML5 history API | |||
| app.use(require('connect-history-api-fallback')()) | |||
| // serve webpack bundle output | |||
| app.use(devMiddleware) | |||
| // enable hot-reload and state-preserving | |||
| // compilation error display | |||
| app.use(hotMiddleware) | |||
| // serve pure static assets | |||
| var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) | |||
| app.use(staticPath, express.static('./static')) | |||
| var uri = 'http://localhost:' + port | |||
| var _resolve | |||
| var readyPromise = new Promise(resolve => { | |||
| _resolve = resolve | |||
| }) | |||
| console.log('> Starting dev server...') | |||
| devMiddleware.waitUntilValid(() => { | |||
| console.log('> Listening at ' + uri + '\n') | |||
| // when env is testing, don't need open it | |||
| if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { | |||
| opn(uri) | |||
| } | |||
| _resolve() | |||
| }) | |||
| var server = app.listen(port) | |||
| module.exports = { | |||
| ready: readyPromise, | |||
| close: () => { | |||
| server.close() | |||
| } | |||
| } | |||
| @@ -0,0 +1,98 @@ | |||
| 'use strict' | |||
| const path = require('path') | |||
| const config = require('../config') | |||
| const ExtractTextPlugin = require('extract-text-webpack-plugin') | |||
| const pkg = require('../package.json') | |||
| exports.assetsPath = function (_path) { | |||
| const assetsSubDirectory = process.env.NODE_ENV === 'production' | |||
| ? config.build.assetsSubDirectory | |||
| : config.dev.assetsSubDirectory | |||
| return path.posix.join(assetsSubDirectory, _path) | |||
| } | |||
| exports.cssLoaders = function (options) { | |||
| options = options || {} | |||
| const cssLoader = { | |||
| loader: 'css-loader', | |||
| options: { | |||
| sourceMap: options.sourceMap | |||
| } | |||
| } | |||
| var postcssLoader = { | |||
| loader: 'postcss-loader', | |||
| options: { | |||
| sourceMap: options.sourceMap | |||
| } | |||
| } | |||
| // generate loader string to be used with extract text plugin | |||
| function generateLoaders (loader, loaderOptions) { | |||
| const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] | |||
| if (loader) { | |||
| loaders.push({ | |||
| loader: loader + '-loader', | |||
| options: Object.assign({}, loaderOptions, { | |||
| sourceMap: options.sourceMap | |||
| }) | |||
| }) | |||
| } | |||
| // Extract CSS when that option is specified | |||
| // (which is the case during production build) | |||
| if (options.extract) { | |||
| return ExtractTextPlugin.extract({ | |||
| use: loaders, | |||
| fallback: 'vue-style-loader' | |||
| }) | |||
| } else { | |||
| return ['vue-style-loader'].concat(loaders) | |||
| } | |||
| } | |||
| // https://vue-loader.vuejs.org/en/configurations/extract-css.html | |||
| return { | |||
| css: generateLoaders(), | |||
| postcss: generateLoaders(), | |||
| less: generateLoaders('less'), | |||
| sass: generateLoaders('sass', { indentedSyntax: true }), | |||
| scss: generateLoaders('sass'), | |||
| stylus: generateLoaders('stylus'), | |||
| styl: generateLoaders('stylus') | |||
| } | |||
| } | |||
| // Generate loaders for standalone style files (outside of .vue) | |||
| exports.styleLoaders = function (options) { | |||
| const output = [] | |||
| const loaders = exports.cssLoaders(options) | |||
| for (const extension in loaders) { | |||
| const loader = loaders[extension] | |||
| output.push({ | |||
| test: new RegExp('\\.' + extension + '$'), | |||
| use: loader | |||
| }) | |||
| } | |||
| return output | |||
| } | |||
| exports.createNotifierCallback = function () { | |||
| const notifier = require('node-notifier') | |||
| return (severity, errors) => { | |||
| if (severity !== 'error') { | |||
| return | |||
| } | |||
| const error = errors[0] | |||
| const filename = error.file && error.file.split('!').pop() | |||
| notifier.notify({ | |||
| title: pkg.name, | |||
| message: severity + ': ' + error.name, | |||
| subtitle: filename || '', | |||
| icon: path.join(__dirname, 'logo.png') | |||
| }) | |||
| } | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| 'use strict' | |||
| const utils = require('./utils') | |||
| const config = require('../config') | |||
| const isProduction = process.env.NODE_ENV === 'production' | |||
| const sourceMapEnabled = isProduction | |||
| ? config.build.productionSourceMap | |||
| : config.dev.cssSourceMap | |||
| module.exports = { | |||
| loaders: utils.cssLoaders({ | |||
| sourceMap: sourceMapEnabled, | |||
| extract: isProduction | |||
| }), | |||
| cssSourceMap: sourceMapEnabled, | |||
| cacheBusting: config.dev.cacheBusting, | |||
| transformToRequire: { | |||
| video: 'src', | |||
| source: 'src', | |||
| img: 'src', | |||
| image: 'xlink:href' | |||
| } | |||
| } | |||
| @@ -0,0 +1,93 @@ | |||
| 'use strict' | |||
| const path = require('path') | |||
| const utils = require('./utils') | |||
| const config = require('../config') | |||
| const vueLoaderConfig = require('./vue-loader.conf') | |||
| const vuxLoader = require('vux-loader') | |||
| function resolve (dir) { | |||
| return path.join(__dirname, '..', dir) | |||
| } | |||
| let webpackConfig = { | |||
| context: path.resolve(__dirname, '../'), | |||
| entry: { | |||
| app: './src/main.js' | |||
| }, | |||
| output: { | |||
| path: config.build.assetsRoot, | |||
| filename: '[name].js', | |||
| publicPath: process.env.NODE_ENV === 'production' | |||
| ? config.build.assetsPublicPath | |||
| : config.dev.assetsPublicPath | |||
| }, | |||
| resolve: { | |||
| extensions: ['.js', '.vue', '.json'], | |||
| alias: { | |||
| 'vue$': 'vue/dist/vue.esm.js', | |||
| '@': resolve('src'), | |||
| } | |||
| }, | |||
| module: { | |||
| rules: [ | |||
| { | |||
| test: /\.vue$/, | |||
| loader: 'vue-loader', | |||
| options: vueLoaderConfig | |||
| }, | |||
| { | |||
| test: /\.js$/, | |||
| loader: 'babel-loader', | |||
| include: [resolve('src'), resolve('test')] | |||
| }, | |||
| { | |||
| test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, | |||
| loader: 'url-loader', | |||
| options: { | |||
| limit: 10000, | |||
| name: utils.assetsPath('img/[name].[hash:7].[ext]') | |||
| } | |||
| }, | |||
| { | |||
| test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, | |||
| loader: 'url-loader', | |||
| options: { | |||
| limit: 10000, | |||
| name: utils.assetsPath('media/[name].[hash:7].[ext]') | |||
| } | |||
| }, | |||
| { | |||
| test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, | |||
| loader: 'url-loader', | |||
| options: { | |||
| limit: 10000, | |||
| name: utils.assetsPath('fonts/[name].[hash:7].[ext]') | |||
| } | |||
| } | |||
| ] | |||
| } | |||
| } | |||
| module.exports = vuxLoader.merge(webpackConfig, { | |||
| plugins: [ | |||
| 'vux-ui', | |||
| 'progress-bar', | |||
| { | |||
| name: 'duplicate-style', | |||
| options: { | |||
| cssProcessorOptions : { | |||
| safe: true, | |||
| zindex: false, | |||
| autoprefixer: { | |||
| add: true, | |||
| browsers: [ | |||
| 'iOS >= 7', | |||
| 'Android >= 4.1' | |||
| ] | |||
| } | |||
| } | |||
| } | |||
| } | |||
| ] | |||
| }) | |||
| @@ -0,0 +1,79 @@ | |||
| 'use strict' | |||
| const utils = require('./utils') | |||
| const webpack = require('webpack') | |||
| const config = require('../config') | |||
| const merge = require('webpack-merge') | |||
| const baseWebpackConfig = require('./webpack.base.conf') | |||
| const HtmlWebpackPlugin = require('html-webpack-plugin') | |||
| const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') | |||
| const portfinder = require('portfinder') | |||
| const devWebpackConfig = merge(baseWebpackConfig, { | |||
| module: { | |||
| rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) | |||
| }, | |||
| // cheap-module-eval-source-map is faster for development | |||
| devtool: config.dev.devtool, | |||
| // these devServer options should be customized in /config/index.js | |||
| devServer: { | |||
| clientLogLevel: 'warning', | |||
| historyApiFallback: true, | |||
| hot: true, | |||
| compress: true, | |||
| host: process.env.HOST || config.dev.host, | |||
| port: process.env.PORT || config.dev.port, | |||
| open: config.dev.autoOpenBrowser, | |||
| overlay: config.dev.errorOverlay ? { | |||
| warnings: false, | |||
| errors: true, | |||
| } : false, | |||
| publicPath: config.dev.assetsPublicPath, | |||
| proxy: config.dev.proxyTable, | |||
| quiet: true, // necessary for FriendlyErrorsPlugin | |||
| watchOptions: { | |||
| poll: config.dev.poll, | |||
| } | |||
| }, | |||
| plugins: [ | |||
| new webpack.DefinePlugin({ | |||
| 'process.env': require('../config/dev.env') | |||
| }), | |||
| new webpack.HotModuleReplacementPlugin(), | |||
| new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. | |||
| new webpack.NoEmitOnErrorsPlugin(), | |||
| // https://github.com/ampedandwired/html-webpack-plugin | |||
| new HtmlWebpackPlugin({ | |||
| filename: 'index.html', | |||
| template: 'index.html', | |||
| inject: true | |||
| }), | |||
| ] | |||
| }) | |||
| module.exports = new Promise((resolve, reject) => { | |||
| portfinder.basePort = process.env.PORT || config.dev.port | |||
| portfinder.getPort((err, port) => { | |||
| if (err) { | |||
| reject(err) | |||
| } else { | |||
| // publish the new Port, necessary for e2e tests | |||
| process.env.PORT = port | |||
| // add port to devServer config | |||
| devWebpackConfig.devServer.port = port | |||
| // Add FriendlyErrorsPlugin | |||
| devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ | |||
| compilationSuccessInfo: { | |||
| // messages: [`Your application is running here: http://${config.dev.host}:${port}`], | |||
| messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], | |||
| }, | |||
| onErrors: config.dev.notifyOnErrors | |||
| ? utils.createNotifierCallback() | |||
| : undefined | |||
| })) | |||
| resolve(devWebpackConfig) | |||
| } | |||
| }) | |||
| }) | |||
| @@ -0,0 +1,118 @@ | |||
| 'use strict' | |||
| const path = require('path') | |||
| const utils = require('./utils') | |||
| const webpack = require('webpack') | |||
| const config = require('../config') | |||
| const merge = require('webpack-merge') | |||
| const baseWebpackConfig = require('./webpack.base.conf') | |||
| const CopyWebpackPlugin = require('copy-webpack-plugin') | |||
| const HtmlWebpackPlugin = require('html-webpack-plugin') | |||
| const ExtractTextPlugin = require('extract-text-webpack-plugin') | |||
| const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') | |||
| const env = require('../config/prod.env') | |||
| const webpackConfig = merge(baseWebpackConfig, { | |||
| module: { | |||
| rules: utils.styleLoaders({ | |||
| sourceMap: config.build.productionSourceMap, | |||
| extract: true, | |||
| usePostCSS: true | |||
| }) | |||
| }, | |||
| devtool: config.build.productionSourceMap ? config.build.devtool : false, | |||
| output: { | |||
| path: config.build.assetsRoot, | |||
| filename: utils.assetsPath('js/[name].[chunkhash].js'), | |||
| chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') | |||
| }, | |||
| plugins: [ | |||
| // http://vuejs.github.io/vue-loader/en/workflow/production.html | |||
| new webpack.DefinePlugin({ | |||
| 'process.env': env | |||
| }), | |||
| // UglifyJs do not support ES6+, you can also use babel-minify for better treeshaking: https://github.com/babel/minify | |||
| new webpack.optimize.UglifyJsPlugin({ | |||
| compress: { | |||
| warnings: false | |||
| }, | |||
| sourceMap: config.build.productionSourceMap, | |||
| parallel: true | |||
| }), | |||
| // extract css into its own file | |||
| new ExtractTextPlugin({ | |||
| filename: utils.assetsPath('css/[name].[contenthash].css'), | |||
| // set the following option to `true` if you want to extract CSS from | |||
| // codesplit chunks into this main css file as well. | |||
| // This will result in *all* of your app's CSS being loaded upfront. | |||
| allChunks: false, | |||
| }), | |||
| // generate dist index.html with correct asset hash for caching. | |||
| // you can customize output by editing /index.html | |||
| // see https://github.com/ampedandwired/html-webpack-plugin | |||
| new HtmlWebpackPlugin({ | |||
| filename: config.build.index, | |||
| template: 'index.html', | |||
| inject: true, | |||
| minify: { | |||
| removeComments: true, | |||
| collapseWhitespace: true, | |||
| removeAttributeQuotes: true | |||
| // more options: | |||
| // https://github.com/kangax/html-minifier#options-quick-reference | |||
| }, | |||
| // necessary to consistently work with multiple chunks via CommonsChunkPlugin | |||
| chunksSortMode: 'dependency' | |||
| }), | |||
| // keep module.id stable when vender modules does not change | |||
| new webpack.HashedModuleIdsPlugin(), | |||
| // enable scope hoisting | |||
| new webpack.optimize.ModuleConcatenationPlugin(), | |||
| // split vendor js into its own file | |||
| new webpack.optimize.CommonsChunkPlugin({ | |||
| name: 'vendor', | |||
| minChunks: function (module) { | |||
| // any required modules inside node_modules are extracted to vendor | |||
| return ( | |||
| module.resource && | |||
| /\.js$/.test(module.resource) && | |||
| module.resource.indexOf( | |||
| path.join(__dirname, '../node_modules') | |||
| ) === 0 | |||
| ) | |||
| } | |||
| }), | |||
| // extract webpack runtime and module manifest to its own file in order to | |||
| // prevent vendor hash from being updated whenever app bundle is updated | |||
| new webpack.optimize.CommonsChunkPlugin({ | |||
| name: 'manifest', | |||
| minChunks: Infinity | |||
| }), | |||
| // This instance extracts shared chunks from code splitted chunks and bundles them | |||
| // in a separate chunk, similar to the vendor chunk | |||
| // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk | |||
| new webpack.optimize.CommonsChunkPlugin({ | |||
| name: 'app', | |||
| async: 'vendor-async', | |||
| children: true, | |||
| minChunks: 3 | |||
| }), | |||
| // copy custom static assets | |||
| new CopyWebpackPlugin([ | |||
| { | |||
| from: path.resolve(__dirname, '../static'), | |||
| to: config.build.assetsSubDirectory, | |||
| ignore: ['.*'] | |||
| } | |||
| ]) | |||
| ] | |||
| }) | |||
| if (config.build.bundleAnalyzerReport) { | |||
| const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin | |||
| webpackConfig.plugins.push(new BundleAnalyzerPlugin()) | |||
| } | |||
| module.exports = webpackConfig | |||
| @@ -0,0 +1,8 @@ | |||
| 'use strict' | |||
| const merge = require('webpack-merge') | |||
| const prodEnv = require('./prod.env') | |||
| module.exports = merge(prodEnv, { | |||
| NODE_ENV: '"development"', | |||
| BASE_API: '"http://10.209.96.72:8080/"', | |||
| }) | |||
| @@ -0,0 +1,74 @@ | |||
| 'use strict' | |||
| // Template version: 1.2.4 | |||
| // see http://vuejs-templates.github.io/webpack for documentation. | |||
| const path = require('path') | |||
| module.exports = { | |||
| dev: { | |||
| // Paths | |||
| assetsSubDirectory: 'static', | |||
| assetsPublicPath: '/', | |||
| proxyTable: {}, | |||
| // Various Dev Server settings | |||
| host: '0.0.0.0', // can be overwritten by process.env.HOST | |||
| port: 8011, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined | |||
| autoOpenBrowser: false, | |||
| errorOverlay: true, | |||
| notifyOnErrors: true, | |||
| poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- | |||
| // Use Eslint Loader? | |||
| // If true, your code will be linted during bundling and | |||
| // linting errors and warnings will be shown in the console. | |||
| useEslint: false, | |||
| // If true, eslint errors and warnings will also be shown in the error overlay | |||
| // in the browser. | |||
| showEslintErrorsInOverlay: false, | |||
| /** | |||
| * Source Maps | |||
| */ | |||
| // https://webpack.js.org/configuration/devtool/#development | |||
| devtool: 'eval-source-map', | |||
| // If you have problems debugging vue-files in devtools, | |||
| // set this to false - it *may* help | |||
| // https://vue-loader.vuejs.org/en/options.html#cachebusting | |||
| cacheBusting: true, | |||
| // CSS Sourcemaps off by default because relative paths are "buggy" | |||
| // with this option, according to the CSS-Loader README | |||
| // (https://github.com/webpack/css-loader#sourcemaps) | |||
| // In our experience, they generally work as expected, | |||
| // just be aware of this issue when enabling this option. | |||
| cssSourceMap: false, | |||
| }, | |||
| build: { | |||
| // Template for index.html | |||
| index: path.resolve(__dirname, '../dist/index.html'), | |||
| // Paths | |||
| assetsRoot: path.resolve(__dirname, '../dist'), | |||
| assetsSubDirectory: 'static', | |||
| assetsPublicPath: '/', | |||
| /** | |||
| * Source Maps | |||
| */ | |||
| productionSourceMap: true, | |||
| // https://webpack.js.org/configuration/devtool/#production | |||
| devtool: '#source-map', | |||
| // Run the build command with an extra argument to | |||
| // View the bundle analyzer report after build finishes: | |||
| // `npm run build --report` | |||
| // Set to `true` or `false` to always turn it on or off | |||
| bundleAnalyzerReport: process.env.npm_config_report | |||
| } | |||
| } | |||
| @@ -0,0 +1,5 @@ | |||
| 'use strict' | |||
| module.exports = { | |||
| NODE_ENV: '"production"', | |||
| BASE_API: '"http://ota.neusoft.com/"' | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| <!DOCTYPE html> | |||
| <html> | |||
| <head> | |||
| <meta charset="utf-8"> | |||
| <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0"> | |||
| <title>东软智能云POS管理系统</title> | |||
| </head> | |||
| <body> | |||
| <div id="app-box"></div> | |||
| <!-- built files will be auto injected --> | |||
| </body> | |||
| </html> | |||
| @@ -0,0 +1,73 @@ | |||
| { | |||
| "name": "posweb", | |||
| "version": "1.0.0", | |||
| "description": "A Vue.js project", | |||
| "author": "", | |||
| "private": true, | |||
| "scripts": { | |||
| "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", | |||
| "start": "npm run dev", | |||
| "build": "node build/build.js" | |||
| }, | |||
| "dependencies": { | |||
| "element-ui": "^2.3.7", | |||
| "fastclick": "^1.0.6", | |||
| "js-cookie": "^2.2.0", | |||
| "vue": "^2.5.2", | |||
| "vue-router": "^3.0.1", | |||
| "vuex": "^2.5.0", | |||
| "vuex-i18n": "^1.3.1", | |||
| "vux": "^2.2.0", | |||
| "vux-uploader": "^0.1.9" | |||
| }, | |||
| "devDependencies": { | |||
| "autoprefixer": "^7.1.2", | |||
| "babel-core": "^6.22.1", | |||
| "babel-loader": "^7.1.1", | |||
| "babel-plugin-transform-runtime": "^6.22.0", | |||
| "babel-preset-env": "^1.3.2", | |||
| "babel-preset-es2015": "^6.24.1", | |||
| "babel-preset-stage-2": "^6.22.0", | |||
| "babel-register": "^6.22.0", | |||
| "chalk": "^2.0.1", | |||
| "connect-history-api-fallback": "^1.3.0", | |||
| "copy-webpack-plugin": "^4.0.1", | |||
| "css-loader": "^0.28.0", | |||
| "eventsource-polyfill": "^0.9.6", | |||
| "extract-text-webpack-plugin": "^3.0.0", | |||
| "file-loader": "^1.1.4", | |||
| "friendly-errors-webpack-plugin": "^1.6.1", | |||
| "html-webpack-plugin": "^2.30.1", | |||
| "less": "^2.7.1", | |||
| "less-loader": "^2.2.3", | |||
| "node-notifier": "^5.1.2", | |||
| "optimize-css-assets-webpack-plugin": "^3.2.0", | |||
| "ora": "^1.2.0", | |||
| "portfinder": "^1.0.13", | |||
| "postcss-import": "^11.0.0", | |||
| "postcss-loader": "^2.0.8", | |||
| "rimraf": "^2.6.0", | |||
| "semver": "^5.3.0", | |||
| "shelljs": "^0.7.6", | |||
| "url-loader": "^0.5.8", | |||
| "vue-loader": "^13.3.0", | |||
| "vue-style-loader": "^3.0.1", | |||
| "vue-template-compiler": "^2.5.2", | |||
| "vux-loader": "^1.0.56", | |||
| "webpack": "^3.6.0", | |||
| "webpack-bundle-analyzer": "^2.9.0", | |||
| "webpack-dev-middleware": "^1.10.0", | |||
| "webpack-dev-server": "^2.9.1", | |||
| "webpack-hot-middleware": "^2.16.1", | |||
| "webpack-merge": "^4.1.0", | |||
| "yaml-loader": "^0.4.0" | |||
| }, | |||
| "engines": { | |||
| "node": ">= 4.0.0", | |||
| "npm": ">= 3.0.0" | |||
| }, | |||
| "browserslist": [ | |||
| "iOS >= 7", | |||
| "Android >= 4.1" | |||
| ] | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| <template> | |||
| <div id="app"> | |||
| <router-view></router-view> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| export default { | |||
| name: 'app' | |||
| } | |||
| </script> | |||
| <style lang="less"> | |||
| @import '~vux/src/styles/reset.less'; | |||
| body { | |||
| background-color: #fbf9fe; | |||
| } | |||
| .el-message{ | |||
| width: 90%; | |||
| min-width: auto; | |||
| } | |||
| .el-message-box{ | |||
| width: 90%; | |||
| width: auto; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,65 @@ | |||
| //全局通用 数据 | |||
| import fetch from '@/utils/fetch' | |||
| //获取上传授权 | |||
| export function getOSSSignature(type) { | |||
| return fetch({ | |||
| url: '/unionService/AliOSS/signature', | |||
| method: 'post', | |||
| params: { | |||
| dir: type | |||
| }, | |||
| withCredentials: false | |||
| }) | |||
| } | |||
| //获取资源下载OSS地址接口 | |||
| export function getMediaResoures() { | |||
| return fetch({ | |||
| url: '/unionService/AliOSS/mediaResource/oss/url', | |||
| method: 'get', | |||
| }) | |||
| } | |||
| //获取更新包下载OSS地址接口 | |||
| export function getPackageResoures() { | |||
| return fetch({ | |||
| url: '/unionService/AliOSS/upgradePackage/oss/url', | |||
| method: 'get', | |||
| }) | |||
| } | |||
| //获取配置下载OSS地址接口 | |||
| export function getConfigResoures() { | |||
| return fetch({ | |||
| url: '/unionService/AliOSS/deviceConfig/oss/url', | |||
| method: 'get', | |||
| }) | |||
| } | |||
| //获取配置下载OSS地址接口 | |||
| export function getLogoResoures() { | |||
| return fetch({ | |||
| url: '/unionService/AliOSS/store/logo/oss/url', | |||
| method: 'get', | |||
| }) | |||
| } | |||
| //获取支付二维码下载OSS地址接口 | |||
| export function qrcodeResoures() { | |||
| return fetch({ | |||
| url: '/unionService/AliOSS/trade/oss/url', | |||
| method: 'get', | |||
| }) | |||
| } | |||
| //获取支付二维码下载OSS地址接口 | |||
| export function getImgResoures() { | |||
| return fetch({ | |||
| url: '/unionService/AliOSS/goods/oss/url', | |||
| method: 'get', | |||
| }) | |||
| } | |||
| //获取省市 | |||
| export function getCitys(id) { | |||
| return fetch({ | |||
| url: '/service-org/Region/getCitys?parentId='+id, | |||
| method: 'get', | |||
| }) | |||
| } | |||
| @@ -0,0 +1,62 @@ | |||
| import fetch from '@/utils/fetch' | |||
| //列表商品 | |||
| export function goodsList(params) { | |||
| return fetch({ | |||
| url: '/service-goods/catering/menus', | |||
| method: 'get', | |||
| params | |||
| }) | |||
| } | |||
| //添加商品 | |||
| export function goodsAdd(params) { | |||
| return fetch({ | |||
| url: '/service-goods/catering/menus', | |||
| method: 'post', | |||
| data: params | |||
| }) | |||
| } | |||
| //修改商品 | |||
| export function goodsEdit(params) { | |||
| return fetch({ | |||
| url: '/service-goods/catering/menus', | |||
| method: 'patch', | |||
| data: params, | |||
| // params: params | |||
| }) | |||
| } | |||
| //删除商品 | |||
| export function goodsDel(id) { | |||
| return fetch({ | |||
| url: '/service-goods/catering/menus/' + id, | |||
| method: 'delete', | |||
| }) | |||
| } | |||
| //菜品类别列表 | |||
| export function goodsCategories(id) { | |||
| return fetch({ | |||
| url: '/service-goods/catering/menus/categories?preset=1', | |||
| method: 'get', | |||
| }) | |||
| } | |||
| // /菜品标签列表 | |||
| export function goodsTags(id) { | |||
| return fetch({ | |||
| url: '/service-goods/catering/menus/tags?preset=1', | |||
| method: 'get', | |||
| }) | |||
| } | |||
| //单菜品查询 | |||
| // /菜品标签列表 | |||
| export function goodsFind(id) { | |||
| return fetch({ | |||
| url: '/service-goods/catering/menus/'+id, | |||
| method: 'get', | |||
| }) | |||
| } | |||
| @@ -0,0 +1,46 @@ | |||
| import fetch from '@/utils/fetch' | |||
| //列表类型 | |||
| export function typeList(params) { | |||
| return fetch({ | |||
| url: '/service-goods/category/', | |||
| method: 'get', | |||
| params | |||
| }) | |||
| } | |||
| //添加类别 | |||
| export function typeAdd(params) { | |||
| return fetch({ | |||
| url: '/service-goods/category', | |||
| method: 'post', | |||
| data: params | |||
| }) | |||
| } | |||
| //修改类型 | |||
| export function typeEdit(id,params) { | |||
| return fetch({ | |||
| url: '/service-goods/category/' + id, | |||
| method: 'put', | |||
| data: params, | |||
| // params: params | |||
| }) | |||
| } | |||
| //修改类型 | |||
| export function typeOrderUpdate(params) { | |||
| return fetch({ | |||
| url: '/service-goods/category/re-order/', | |||
| method: 'post', | |||
| data: params, | |||
| // params: params | |||
| }) | |||
| } | |||
| //删除商品 | |||
| export function typeDel(id) { | |||
| return fetch({ | |||
| url: '/service-goods/category/catering/' + id, | |||
| method: 'delete', | |||
| }) | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| import fetch from '@/utils/fetch' | |||
| export function login(userinfo) { | |||
| return fetch({ | |||
| url: '/service-user/login', | |||
| method: 'post', | |||
| data: userinfo | |||
| }) | |||
| } | |||
| export function getInfo() { | |||
| return fetch({ | |||
| url: '/service-user/user/me', | |||
| method: 'get' | |||
| }) | |||
| } | |||
| @@ -0,0 +1,46 @@ | |||
| import fetch from '@/utils/fetch' | |||
| //订单首页列表 | |||
| export function orderIndex(par) { | |||
| return fetch({ | |||
| url: '/unionService/orderStatistics/shopOrderSummary', | |||
| method: 'get', | |||
| params:par | |||
| }) | |||
| } | |||
| //订单交易流水列表 | |||
| export function orderflow(par) { | |||
| return fetch({ | |||
| url: '/unionService/orderStatistics/shopOrderDetails', | |||
| method: 'get', | |||
| params:par | |||
| }) | |||
| } | |||
| //订单交易流水列表 | |||
| export function orderStatistics(par) { | |||
| return fetch({ | |||
| url: '/service-trade/tradeStatistics/payChannelSummary', | |||
| method: 'get', | |||
| params:par | |||
| }) | |||
| } | |||
| //日报统计 | |||
| //订单交易流水列表 | |||
| export function orderDatestatistics(par) { | |||
| return fetch({ | |||
| url: '/unionService/tradeStatistics/daily', | |||
| method: 'get', | |||
| params:par | |||
| }) | |||
| } | |||
| //占比统计 | |||
| export function orderRationstatistics(par) { | |||
| return fetch({ | |||
| url: '/service-trade/tradeStatistics/payChannelProportion', | |||
| method: 'get', | |||
| params:par | |||
| }) | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| import fetch from '@/utils/fetch' | |||
| // 门店列表 | |||
| export function storeList(params) { | |||
| return fetch({ | |||
| url: '/service-org/shop/list', | |||
| method: 'get', | |||
| params | |||
| }) | |||
| } | |||
| // 门店添加 | |||
| export function storeAdd(params) { | |||
| return fetch({ | |||
| url: '/unionService/shop/shop', | |||
| method: 'post', | |||
| data: params | |||
| }) | |||
| } | |||
| // 门店查询 | |||
| export function storeFind(id) { | |||
| return fetch({ | |||
| url: '/service-org/shop/detail?shopId=' + id, | |||
| method: 'get' | |||
| }) | |||
| } | |||
| // 门店修改 | |||
| export function storeEdit(par) { | |||
| return fetch({ | |||
| url: '/unionService/shop/shop', | |||
| method: 'put', | |||
| data: par | |||
| }) | |||
| } | |||
| // 门店关联ota | |||
| export function storeOta(par) { | |||
| return fetch({ | |||
| url: '/unionService/device/store', | |||
| method: 'put', | |||
| data: par | |||
| }) | |||
| } | |||
| // 门店删除 | |||
| export function storeDel(id) { | |||
| return fetch({ | |||
| url: '/service-org/shop/delete?shopId=' + id, | |||
| method: 'DELETE' | |||
| }) | |||
| } | |||
| @@ -0,0 +1,261 @@ | |||
| <template> | |||
| <div> | |||
| <x-header :left-options="{backText: ''}" style="width: 100%;position: fixed;left: 0px;top: 0px;z-index: 100;"> | |||
| 商品添加 | |||
| </x-header> | |||
| <div class="app-container" style="padding: 60px 0px 10px 0px;height: 100%"> | |||
| <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" size="small"> | |||
| <el-form-item label="门店" prop="shop" v-if="this.usertype=='user'"> | |||
| <el-select v-model="ruleForm.shop" placeholder="门店"> | |||
| <el-option | |||
| v-for="item in shopList" | |||
| :key="item.id" | |||
| :label="item.name" | |||
| :value="item.id"> | |||
| </el-option> | |||
| </el-select> | |||
| </el-form-item> | |||
| <el-form-item label="菜品名" prop="name"> | |||
| <el-input v-model.trim="ruleForm.name" style="width: 90%" placeholder="最多40字"></el-input> | |||
| </el-form-item> | |||
| <el-form-item label="菜品价格" prop="price"> | |||
| <el-input v-model.trim="ruleForm.price" style="width: 90%"></el-input> | |||
| </el-form-item> | |||
| <el-form-item label="菜品类别" prop="category"> | |||
| <el-select v-model="ruleForm.category" placeholder="请选择"> | |||
| <el-option | |||
| v-for="item in cateType" | |||
| :key="item.id" | |||
| :label="item.name" | |||
| :value="item.id"/> | |||
| </el-select> | |||
| </el-form-item> | |||
| <el-form-item label="菜品图片" style="width: 50%" prop="image"> | |||
| <el-tag type="warning">图片最大200kb,建议宽高174*143</el-tag> | |||
| <el-upload | |||
| id="img" | |||
| class="avatar-uploader" | |||
| :action="updata.url" | |||
| :show-file-list="true" | |||
| :fileList="fileList" | |||
| :on-success="handleAvatarSuccess" | |||
| :before-upload="beforeAvatarUpload" | |||
| :data="updata.data" | |||
| accept="image/*" | |||
| > | |||
| <img v-if="ruleForm.image" :src="ruleForm.image+'?x-oss-process=image/resize,m_fixed,h_143,w_174'" class="avatar"> | |||
| <i v-else class="el-icon-plus avatar-uploader-icon"></i> | |||
| </el-upload> | |||
| </el-form-item> | |||
| <el-form-item label="菜品标签" prop="tags"> | |||
| <el-checkbox-group v-model="ruleForm.tags"> | |||
| <el-checkbox v-for="item in tagsTyes" | |||
| :key="item.id" | |||
| :label="item.id" | |||
| >{{item.name}} | |||
| </el-checkbox> | |||
| </el-checkbox-group> | |||
| </el-form-item> | |||
| <el-form-item label="是否上架" prop="onSale"> | |||
| <el-radio-group v-model="ruleForm.onSale"> | |||
| <el-radio :label="0">下架</el-radio> | |||
| <el-radio :label="1">上架</el-radio> | |||
| </el-radio-group> | |||
| </el-form-item> | |||
| <el-form-item> | |||
| <el-button type="primary" @click="submitForm('ruleForm')">立即创建</el-button> | |||
| <el-button @click="resetForm('ruleForm')">重置</el-button> | |||
| </el-form-item> | |||
| </el-form> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import {goodsCategories, goodsTags, goodsAdd} from '@/api/goods' | |||
| import {getOSSSignature, getImgResoures} from '@/api/comm' | |||
| import {storeList} from '@/api/store' | |||
| import {typeList} from "@/api/goodstype"; | |||
| export default { | |||
| created() { | |||
| goodsTags().then(response => { | |||
| this.tagsTyes = response.result | |||
| }) | |||
| //上传签名初始化 | |||
| getOSSSignature("img").then(response => { | |||
| this.updata.data.policy = response.result.policy | |||
| this.updata.data.OSSAccessKeyId = response.result.accessid | |||
| this.updata.data.success_action_status = '200' | |||
| this.updata.data.signature = response.result.signature | |||
| this.updata.url = response.result.host | |||
| }) | |||
| getImgResoures().then(res => { | |||
| this.mediaUrl = res.result | |||
| }) | |||
| if (this.usertype == 'user') { | |||
| //多个门店选择 | |||
| storeList().then(response => { | |||
| this.shopList = response.result.rows | |||
| this.ruleForm.shop = this.shopList[0]['id'] | |||
| }) | |||
| }else{ | |||
| typeList({ | |||
| store:this.ruleForm.shop, | |||
| industry: '1', | |||
| }).then(response => { | |||
| this.cateType = response.result | |||
| this.ruleForm.category='' | |||
| }); | |||
| } | |||
| this.listLoading = false | |||
| }, | |||
| data() { | |||
| return { | |||
| usertype: this.$store.getters.usertype, | |||
| shopList: [], | |||
| fileList:[], | |||
| updata: { | |||
| url: '', | |||
| data: {} | |||
| }, | |||
| ruleForm: { | |||
| name: '', | |||
| price: "", | |||
| category: "", | |||
| tags: [], | |||
| onSale: 1, | |||
| image: "", | |||
| // org: '', | |||
| shop: '', | |||
| }, | |||
| rules: { | |||
| shop: [ | |||
| {required: true, message: '请选择门店', trigger: 'blur'}, | |||
| ], | |||
| name: [ | |||
| {required: true, message: '请输入菜品名称', trigger: 'blur'}, | |||
| {max: 40, message: '最多40字', trigger: 'blur'}, | |||
| ], | |||
| price: [ | |||
| {required: true, message: '请输入价格', trigger: 'blur'}, | |||
| {pattern: /^(0|[1-9][0-9]{0,9})(\.[0-9]{1,2})?$/, message: '请输入正确的价格', trigger: 'blur'} | |||
| ], | |||
| category: [ | |||
| {required: true, message: '请输选择类别', trigger: 'blur'}, | |||
| ], | |||
| }, | |||
| cateType: [], | |||
| tagsTyes: [], | |||
| }; | |||
| }, | |||
| watch:{ | |||
| "ruleForm.shop":function () { | |||
| if(this.ruleForm.shop){ | |||
| typeList({ | |||
| store:this.ruleForm.shop, | |||
| industry: '1', | |||
| }).then(response => { | |||
| this.cateType = response.result | |||
| this.ruleForm.category='' | |||
| }); | |||
| }else{ | |||
| this.cateType=[] | |||
| } | |||
| this.$refs['ruleForm'].clearValidate(); | |||
| } | |||
| }, | |||
| methods: { | |||
| submitForm(formName) { | |||
| this.$refs[formName].validate((valid) => { | |||
| if (valid) { | |||
| goodsAdd(this.ruleForm).then(res => { | |||
| var router = this.$router; | |||
| this.$message({ | |||
| message: '操作成功', | |||
| type: 'success', | |||
| duration: 1000, | |||
| onClose: function () { | |||
| router.push({path: 'index'}) | |||
| } | |||
| }); | |||
| }) | |||
| } else { | |||
| console.log('error submit!!', this.ruleForm.mediaUuid); | |||
| return false; | |||
| } | |||
| }); | |||
| }, | |||
| resetForm(formName) { | |||
| this.$refs[formName].resetFields(); | |||
| }, | |||
| handleAvatarSuccess(res, file) { | |||
| this.filename = file.name | |||
| this.ruleForm.image = this.mediaUrl + file.uid | |||
| this.fileList=[] | |||
| }, | |||
| beforeAvatarUpload(file) { | |||
| // var reader = new FileReader(); | |||
| //// console.log(file,reader) | |||
| // reader.readAsDataURL(file); | |||
| // reader.onload = function(theFile) { | |||
| // var image = new Image(); | |||
| // image.src = theFile.target.result; | |||
| // image.onload = function() { | |||
| // console.log("图片的宽度为"+this.width+",长度为"+this.height); | |||
| // }; | |||
| // }; | |||
| // const isLt2M = file.size / 1024 < 200; | |||
| // if (!isLt2M) { | |||
| // this.$message.error('上传图片大小不能超过 200kb!'); | |||
| // return false | |||
| // } | |||
| this.updata.data.key = "img/" + file.uid | |||
| return true | |||
| }, | |||
| } | |||
| } | |||
| </script> | |||
| <style> | |||
| .el-transfer-panel { | |||
| width: 40%; | |||
| } | |||
| .avatar-uploader .el-upload { | |||
| border: 1px dashed #d9d9d9; | |||
| border-radius: 6px; | |||
| cursor: pointer; | |||
| position: relative; | |||
| overflow: hidden; | |||
| } | |||
| .avatar-uploader .el-upload:hover { | |||
| border-color: #409EFF; | |||
| } | |||
| .avatar-uploader-icon { | |||
| font-size: 28px; | |||
| color: #8c939d; | |||
| width: 11rem; | |||
| height:9rem; | |||
| line-height: 150px; | |||
| text-align: center; | |||
| } | |||
| .avatar { | |||
| width: 11rem; | |||
| height:9rem; | |||
| display: block; | |||
| } | |||
| #img{ | |||
| width:11rem | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,221 @@ | |||
| <template> | |||
| <div> | |||
| <x-header :left-options="{backText: ''}" style="width: 100%;position: fixed;left: 0px;top: 0px;z-index: 100;"> | |||
| 商品修改 | |||
| </x-header> | |||
| <div class="app-container" style="padding: 60px 0px 10px 0px;height: 100%"> | |||
| <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm" size="small"> | |||
| <el-form-item label="菜品名" prop="name"> | |||
| <el-input v-model.trim="ruleForm.name" style="width: 90%" placeholder="最多40字"></el-input> | |||
| </el-form-item> | |||
| <el-form-item label="菜品价格" prop="price"> | |||
| <el-input v-model.trim="ruleForm.price" style="width: 90%"></el-input> | |||
| </el-form-item> | |||
| <el-form-item label="菜品类别" prop="category"> | |||
| <el-select v-model="ruleForm.category" placeholder="请选择"> | |||
| <el-option | |||
| v-for="item in cateType" | |||
| :key="item.id" | |||
| :label="item.name" | |||
| :value="item.id"/> | |||
| </el-select> | |||
| </el-form-item> | |||
| <el-form-item label="菜品图片" style="width: 50%" prop="image"> | |||
| <el-tag type="warning">图片最大200kb,建议宽高174*143</el-tag> | |||
| <el-upload | |||
| id="img" | |||
| class="avatar-uploader" | |||
| :action="updata.url" | |||
| :show-file-list="true" | |||
| :fileList="fileList" | |||
| :on-success="handleAvatarSuccess" | |||
| :before-upload="beforeAvatarUpload" | |||
| :data="updata.data" | |||
| accept="image/*" | |||
| > | |||
| <img v-if="ruleForm.image" :src="ruleForm.image+'?x-oss-process=image/resize,m_fixed,h_143,w_174'" class="avatar"> | |||
| <i v-else class="el-icon-plus avatar-uploader-icon"></i> | |||
| </el-upload> | |||
| </el-form-item> | |||
| <el-form-item label="菜品标签" prop="tags"> | |||
| <el-checkbox-group v-model="ruleForm.tags"> | |||
| <el-checkbox v-for="item in tagsTyes" | |||
| :key="item.id" | |||
| :label="item.id" | |||
| >{{item.name}} | |||
| </el-checkbox> | |||
| </el-checkbox-group> | |||
| </el-form-item> | |||
| <el-form-item label="是否上架" prop="onSale"> | |||
| <el-radio-group v-model="ruleForm.onSale"> | |||
| <el-radio :label="0">下架</el-radio> | |||
| <el-radio :label="1">上架</el-radio> | |||
| </el-radio-group> | |||
| </el-form-item> | |||
| <el-form-item> | |||
| <el-button type="primary" @click="submitForm('ruleForm')">修改</el-button> | |||
| </el-form-item> | |||
| </el-form> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import {goodsCategories, goodsTags, goodsEdit, goodsFind} from '@/api/goods' | |||
| import {getOSSSignature, getImgResoures} from '@/api/comm' | |||
| import {typeList} from "@/api/goodstype"; | |||
| export default { | |||
| created() { | |||
| //初始化 | |||
| // goodsCategories().then(response => { | |||
| // this.cateType = response.result | |||
| // }) | |||
| goodsTags().then(response => { | |||
| this.tagsTyes = response.result | |||
| }) | |||
| //上传签名初始化 | |||
| getOSSSignature("img").then(response => { | |||
| this.updata.data.policy = response.result.policy | |||
| this.updata.data.OSSAccessKeyId = response.result.accessid | |||
| this.updata.data.success_action_status = '200' | |||
| this.updata.data.signature = response.result.signature | |||
| this.updata.url = response.result.host | |||
| }) | |||
| getImgResoures().then(res => { | |||
| this.mediaUrl = res.result | |||
| }) | |||
| goodsFind(this.$route.params.id).then(response => { | |||
| this.ruleForm = response.result | |||
| let put = [] | |||
| for (var i in response.result.tags) { | |||
| put.push(parseInt(i)); | |||
| } | |||
| this.ruleForm.tags = put | |||
| }) | |||
| }, | |||
| data() { | |||
| return { | |||
| fileList:[], | |||
| updata: { | |||
| url: '', | |||
| data: {} | |||
| }, | |||
| ruleForm: { | |||
| name: '', | |||
| price: "", | |||
| category: "", | |||
| tags: [], | |||
| onSale: 1, | |||
| image: "", | |||
| org: '', | |||
| shop: '', | |||
| }, | |||
| rules: { | |||
| name: [ | |||
| {required: true, message: '请输入菜品名称', trigger: 'blur'}, | |||
| {max: 40, message: '最多40字', trigger: 'blur'}, | |||
| ], | |||
| price: [ | |||
| {required: true, message: '请输入价格', trigger: 'blur'}, | |||
| {pattern: /^(0|[1-9][0-9]{0,9})(\.[0-9]{1,2})?$/, message: '请输入正确的价格', trigger: 'blur'} | |||
| ], | |||
| category: [ | |||
| {required: true, message: '请输选择类别', trigger: 'blur'}, | |||
| ], | |||
| }, | |||
| cateType: [], | |||
| tagsTyes: [], | |||
| }; | |||
| }, | |||
| watch:{ | |||
| "ruleForm.shop":function () { | |||
| typeList({ | |||
| store:this.ruleForm.shop, | |||
| industry: '1', | |||
| }).then(response => { | |||
| this.cateType = response.result | |||
| }); | |||
| } | |||
| }, | |||
| methods: { | |||
| submitForm(formName) { | |||
| this.$refs[formName].validate((valid) => { | |||
| if (valid) { | |||
| goodsEdit(this.ruleForm).then(res => { | |||
| var router = this.$router; | |||
| this.$message({ | |||
| message: '操作成功', | |||
| type: 'success', | |||
| duration: 1000, | |||
| onClose: function () { | |||
| router.push({path: '../index'}) | |||
| } | |||
| }); | |||
| }) | |||
| } else { | |||
| console.log('error submit!!', this.ruleForm.mediaUuid); | |||
| return false; | |||
| } | |||
| }); | |||
| }, | |||
| resetForm(formName) { | |||
| this.$refs[formName].resetFields(); | |||
| }, | |||
| handleAvatarSuccess(res, file) { | |||
| this.filename = file.name | |||
| this.ruleForm.image = this.mediaUrl + file.uid | |||
| this.fileList=[] | |||
| }, | |||
| beforeAvatarUpload(file) { | |||
| // const isLt2M = file.size / 1024 < 200; | |||
| // if (!isLt2M) { | |||
| // this.$message.error('上传图片大小不能超过 200kb!'); | |||
| // return false | |||
| // } | |||
| this.updata.data.key = "img/" + file.uid | |||
| return true | |||
| }, | |||
| } | |||
| } | |||
| </script> | |||
| <style> | |||
| .el-transfer-panel { | |||
| width: 40%; | |||
| } | |||
| .avatar-uploader .el-upload { | |||
| border: 1px dashed #d9d9d9; | |||
| border-radius: 6px; | |||
| cursor: pointer; | |||
| position: relative; | |||
| overflow: hidden; | |||
| } | |||
| .avatar-uploader .el-upload:hover { | |||
| border-color: #409EFF; | |||
| } | |||
| .avatar-uploader-icon { | |||
| font-size: 28px; | |||
| color: #8c939d; | |||
| width: 174px; | |||
| height: 143px; | |||
| line-height: 150px; | |||
| text-align: center; | |||
| } | |||
| .avatar { | |||
| width: 174px; | |||
| height: 143px; | |||
| display: block; | |||
| } | |||
| #img{ | |||
| width:11rem | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,124 @@ | |||
| <template> | |||
| <div> | |||
| <x-header :left-options="{backText: ''}" style="width: 100%;position: fixed;left: 0px;top: 0px;z-index: 100;"> | |||
| 商品管理 | |||
| </x-header> | |||
| <div style="padding: 50px 0px 51px 0px;height: 100%"> | |||
| <flexbox style="margin-bottom:5px" orient="vertical"> | |||
| <flexbox-item v-if="usertype=='user'"> | |||
| <selector title="选择门店" :options="opts" v-model="listQuery.shopId" | |||
| :value-map="['id','name']" @on-change="fetchData"></selector> | |||
| </flexbox-item> | |||
| <flexbox-item> | |||
| <router-link to="add"> | |||
| <el-button type="primary" style="margin-left: 10px" size="small">添加商品</el-button> | |||
| </router-link> | |||
| <router-link to="type"> | |||
| <el-button type="success" size="small">商品分类</el-button> | |||
| </router-link> | |||
| </flexbox-item> | |||
| </flexbox> | |||
| <x-table full-bordered class="goodstable"> | |||
| <thead> | |||
| <tr> | |||
| <th>名称</th> | |||
| <th width="20%">价格</th> | |||
| <th width="20%">类别</th> | |||
| <th width="15%">状态</th> | |||
| <th width="15%">操作</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <tr v-for="vo in list"> | |||
| <td>{{vo.name}}</td> | |||
| <td>{{vo.price}}</td> | |||
| <td>{{vo.cateName}}</td> | |||
| <td>{{onSaleType[vo.onSale]}}</td> | |||
| <td> | |||
| <router-link :to="{ path: 'update/'+vo.id}"> <el-button type="text" size="small">编辑</el-button> </router-link> | |||
| </td> | |||
| </tr> | |||
| <tr v-if="list.length==0"> | |||
| <td colspan="5">暂无数据</td> | |||
| </tr> | |||
| </tbody> | |||
| </x-table> | |||
| </div> | |||
| <myfooter activeIndex="1"> | |||
| </myfooter> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import {XTable, Tabbar, TabbarItem, Flexbox, FlexboxItem, Selector} from 'vux' | |||
| import {goodsList, goodsCategories, goodsTags, goodsDel} from '@/api/goods' | |||
| import Myfooter from '@/components/layout/Myfooter' | |||
| import {storeList} from '@/api/store' | |||
| export default { | |||
| components: { | |||
| XTable, Tabbar, TabbarItem, Flexbox, FlexboxItem, Selector, Myfooter | |||
| }, | |||
| data() { | |||
| return { | |||
| list: [], | |||
| listQuery: { | |||
| id: '', | |||
| shopId: '', | |||
| name: '', | |||
| categories: '', | |||
| tags: '', | |||
| onSale: '', | |||
| limit: 25, | |||
| offset: 0 | |||
| }, | |||
| cateType: [], | |||
| opts: [], | |||
| onSaleType: {1: '上架', 0: '下架'}, | |||
| usertype: this.$store.getters.usertype, | |||
| } | |||
| }, | |||
| created() { | |||
| console.log(this.usertype) | |||
| if (this.usertype == "user") { | |||
| storeList().then(response => { | |||
| if (response.result.rows.length > 0) { | |||
| this.opts = response.result.rows | |||
| this.listQuery.shopId = this.opts[0]['id'] | |||
| } | |||
| }).then(() => { | |||
| this.fetchData() | |||
| }) | |||
| } else { | |||
| this.fetchData() | |||
| } | |||
| }, | |||
| methods: { | |||
| fetchData() { | |||
| goodsList(this.listQuery).then(response => { | |||
| this.list = response.result.data | |||
| }) | |||
| }, | |||
| } | |||
| } | |||
| </script> | |||
| <style> | |||
| .headercss { | |||
| width: 100%; | |||
| position: absolute; | |||
| left: 0px; | |||
| top: 0px; | |||
| z-index: 100; | |||
| } | |||
| .goodstable { | |||
| font-size: 14px; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,206 @@ | |||
| <template> | |||
| <div> | |||
| <x-header :left-options="{backText: ''}" style="width: 100%;position: fixed;left: 0px;top: 0px;z-index: 100;"> | |||
| 商品分类 | |||
| </x-header> | |||
| <div class="app-container" style="padding: 60px 0px 10px 0px;height: 100%"> | |||
| <flexbox style="margin-bottom:5px" orient="vertical"> | |||
| <flexbox-item v-if="usertype=='user'"> | |||
| <selector title="选择门店" :options="shopList" v-model="ruleForm.store" @on-change="selectlist" | |||
| :value-map="['id','name']"></selector> | |||
| </flexbox-item> | |||
| <flexbox-item> | |||
| <el-button type="primary" style="margin-left: 10px" size="small" @click="addType()">添加分类</el-button> | |||
| </flexbox-item> | |||
| </flexbox> | |||
| <!--<el-form :model="ruleForm" ref="ruleForm" label-width="20px" v-if="usertype!='admin'">--> | |||
| <!--<selector title="选择门店" :options="shopList" v-model="ruleForm.store"--> | |||
| <!--:value-map="['id','name']"></selector>--> | |||
| <!--</el-form>--> | |||
| <!--<div class="listbutton">--> | |||
| <!--<el-button type="primary" @click="addType()" style="margin: 0px 0 10px 10px;">添加分类</el-button>--> | |||
| <!--</div>--> | |||
| <el-dialog title="" :visible.sync="dialogFormVisible" width="90%" :before-close="handleClose" > | |||
| <el-form :model="form" :rules="rule" ref="form" size="mini"> | |||
| <el-form-item label="类别名称" prop="name"> | |||
| <el-input v-model.trim="form.name"></el-input> | |||
| </el-form-item> | |||
| </el-form> | |||
| <div slot="footer" class="dialog-footer"> | |||
| <el-button @click="handleClose">取 消</el-button> | |||
| <el-button type="primary" @click="subTable">确 定</el-button> | |||
| </div> | |||
| </el-dialog> | |||
| <div> | |||
| <el-table :data="list" element-loading-text="拼命加载中" fit size="mini" class="goodstypetable"> | |||
| <el-table-column align="center" label='类别名称' prop="name"> | |||
| </el-table-column> | |||
| <el-table-column align="center" label='排序'> | |||
| <template slot-scope="scope"> | |||
| <el-button icon="el-icon-upload2" type="text" style="font-size: 20px" | |||
| :disabled="scope.$index==0" @click="up(scope.$index)"></el-button> | |||
| <el-button icon="el-icon-download" type="text" style="font-size: 20px" | |||
| :disabled="scope.$index==list.length-1" @click="down(scope.$index)"></el-button> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column align="center" label='操作'> | |||
| <template slot-scope="scope"> | |||
| <el-button type="text" size="mini" @click="updateTable(scope.row)">编辑</el-button> | |||
| <el-button type="text" size="mini" @click="delTable(scope.row.id)">删除</el-button> | |||
| </template> | |||
| </el-table-column> | |||
| </el-table> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import {XTable, Tabbar, TabbarItem, Flexbox, FlexboxItem, Selector} from 'vux' | |||
| import {storeList} from "@/api/store"; | |||
| import {typeAdd, typeList, typeEdit, typeDel, typeOrderUpdate} from "@/api/goodstype"; | |||
| export default { | |||
| components: { | |||
| XTable, Tabbar, TabbarItem, Flexbox, FlexboxItem, Selector | |||
| }, | |||
| created() { | |||
| if (this.usertype != 'admin') { | |||
| storeList({org: this.ruleForm.org}).then(response => { | |||
| this.shopList = response.result.rows; | |||
| this.ruleForm.store = this.shopList[0].id; | |||
| this.selectlist() | |||
| }); | |||
| } else { | |||
| this.selectlist() | |||
| } | |||
| }, | |||
| data() { | |||
| return { | |||
| dialogVisible: false, | |||
| usertype: this.$store.getters.usertype, | |||
| ruleForm: { | |||
| store: '', | |||
| industry: '1', | |||
| }, | |||
| form: { | |||
| name: '', | |||
| industry: '1', | |||
| }, | |||
| manageList: [], | |||
| shopList: [], | |||
| list: [], | |||
| dialogFormVisible: false, | |||
| rule: { | |||
| name: [ | |||
| {required: true, message: '请填写类别名称', trigger: 'blur'}, | |||
| {min: 1, max: 4, message: '最多4个字', trigger: 'blur'}, | |||
| ], | |||
| }, | |||
| edit: false, | |||
| upid: "", | |||
| } | |||
| }, | |||
| methods: { | |||
| handleClose(){ | |||
| this.$refs['form'].clearValidate(); | |||
| this.dialogFormVisible = false | |||
| }, | |||
| up(index) { | |||
| var nowObj = this.list[index] | |||
| var upObj = this.list[index - 1] | |||
| this.$set(this.list, index - 1, nowObj) | |||
| this.$set(this.list, index, upObj) | |||
| this.updateSore() | |||
| }, | |||
| down(index) { | |||
| var nowObj = this.list[index] | |||
| var downObj = this.list[index + 1] | |||
| this.$set(this.list, index + 1, nowObj) | |||
| this.$set(this.list, index, downObj) | |||
| this.updateSore() | |||
| }, | |||
| updateSore() { | |||
| var listL = this.list.length | |||
| var postList = this.list.map((v, key) => { | |||
| return {id: v.id, order: listL - key} | |||
| }) | |||
| typeOrderUpdate(postList).then(response => { | |||
| // this.list = response.result | |||
| }); | |||
| }, | |||
| shopChange() { | |||
| this.selectlist() | |||
| }, | |||
| selectlist() { | |||
| typeList(this.ruleForm).then(response => { | |||
| this.list = response.result | |||
| }); | |||
| }, | |||
| addType() { | |||
| this.edit = false; | |||
| this.form = { | |||
| 'name': '', | |||
| "industry": "1", | |||
| }, | |||
| this.dialogFormVisible = true | |||
| }, | |||
| updateTable(item) { | |||
| this.edit = true; | |||
| this.upid = item.id | |||
| this.form = { | |||
| name: item.name, | |||
| "industry": 1, | |||
| } | |||
| this.dialogFormVisible = true | |||
| }, | |||
| delTable(id) { | |||
| this.$confirm('此操作将删除您所选的数据, 是否继续?', '', { | |||
| type: 'warning', | |||
| center: true | |||
| }).then(() => { | |||
| typeDel(id).then(response => { | |||
| this.selectlist() | |||
| }); | |||
| }).catch(() => { | |||
| }); | |||
| }, | |||
| subTable() { | |||
| this.$refs['form'].validate((valid) => { | |||
| if (valid) { | |||
| if (this.usertype != 'admin') this.form.store = this.ruleForm.store | |||
| if (this.edit) { | |||
| typeEdit(this.upid, this.form).then(response => { | |||
| this.dialogFormVisible = false | |||
| this.selectlist() | |||
| }); | |||
| } else { | |||
| typeAdd(this.form).then(response => { | |||
| this.dialogFormVisible = false | |||
| this.selectlist() | |||
| }); | |||
| } | |||
| } else { | |||
| return false; | |||
| } | |||
| }) | |||
| } | |||
| }, | |||
| } | |||
| </script> | |||
| <style> | |||
| .goodstypetable td { | |||
| padding: 0px 0px; | |||
| } | |||
| .el-message-box{ | |||
| width: 90%; | |||
| width: auto; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,64 @@ | |||
| <template> | |||
| <el-menu :default-active="activeIndex" class=" footerLayout el-menu-demo " mode="horizontal" @select="handleSelect" | |||
| menu-trigger="click"> | |||
| <el-menu-item index="1">商品管理</el-menu-item> | |||
| <!--<el-submenu index="1">--> | |||
| <!--<template slot="title">商品管理{{activeIndex}}</template>--> | |||
| <!--<el-menu-item index="2-1" class="fontCss">商品列表</el-menu-item>--> | |||
| <!--<el-menu-item index="2-2" class="fontCss">商品分类</el-menu-item>--> | |||
| <!--</el-submenu>--> | |||
| <el-menu-item index="2" class="fontCss">交易管理</el-menu-item> | |||
| </el-menu> | |||
| </template> | |||
| <script> | |||
| import {Tabbar, TabbarItem} from 'vux' | |||
| export default { | |||
| components: { | |||
| Tabbar, TabbarItem | |||
| }, | |||
| props: { | |||
| activeIndex: {default: '1'}, | |||
| }, | |||
| methods: { | |||
| handleSelect(key, keyPath) { | |||
| var path = "" | |||
| switch (key) { | |||
| case '1': | |||
| path = "/goods/index"; | |||
| break; | |||
| case '2': | |||
| path = "/order/ratiostatistics"; | |||
| break; | |||
| } | |||
| this.$router.push({path: path}); | |||
| } | |||
| } | |||
| } | |||
| </script> | |||
| <style> | |||
| .footerLayout { | |||
| width: 100%; | |||
| position: fixed; | |||
| left: 0px; | |||
| bottom: 0px; | |||
| z-index: 100; | |||
| } | |||
| .footerLayout .el-menu-item { | |||
| height: 50px; | |||
| line-height: 50px; | |||
| } | |||
| .footerLayout li { | |||
| width: 50%; | |||
| text-align: center; | |||
| /*font-size: 16px;*/ | |||
| } | |||
| .fontCss { | |||
| text-align: center; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,154 @@ | |||
| <template> | |||
| <div class="bjimg"> | |||
| <x-header :left-options="{showBack: false}">{{token}}登录</x-header> | |||
| <box gap="20px 30px"> | |||
| <flexbox orient="vertical" style=""> | |||
| <flexbox-item> | |||
| <h2 style="color: white;text-align:center;margin: 30px 0 15px 0">东软智能云POS管理系统</h2> | |||
| </flexbox-item> | |||
| <flexbox-item> | |||
| <x-input title="" placeholder="用户名" class="inputcss" v-model.trim="loginForm.username"> | |||
| <img slot="label" style="padding-right:10px;display:block;" | |||
| src="../assets/user.png" width="20" height="20"> | |||
| </x-input> | |||
| </flexbox-item> | |||
| <flexbox-item> | |||
| <x-input title="" placeholder="密码" type="password" class="inputcss" | |||
| v-model.trim="loginForm.password"> | |||
| <img slot="label" style="padding-right:10px;display:block;" | |||
| src="../assets/password.png" width="20" height="20"> | |||
| </x-input> | |||
| </flexbox-item> | |||
| <flexbox-item> | |||
| <x-input title="验证码" placeholder="验证码" class="inputcss" v-model.trim="loginForm.captcha"> | |||
| <img slot="label" style="padding-right:10px;display:block;" | |||
| src="../assets/yzm.png" width="20" height="20"> | |||
| <img slot="right-full-height" | |||
| @click="reRand" :src="captcha" width="100" | |||
| height="50"> | |||
| </x-input> | |||
| </flexbox-item> | |||
| <flexbox-item> | |||
| <!--<x-button type="default" style="width: 85%;margin-top: 20px" @click.native="sub">登录</x-button>--> | |||
| <el-button type="primary" style="width:100%;margin-top: 15px" | |||
| @click.native.prevent="sub"> | |||
| 立即登录 | |||
| </el-button> | |||
| </flexbox-item> | |||
| </flexbox> | |||
| <toast v-model="showMessage" type="text" width="16em">{{msg}}</toast> | |||
| </box> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import {XInput, Flexbox, FlexboxItem, XButton, Box} from 'vux' | |||
| import {login} from "@/api/login"; | |||
| export default { | |||
| components: { | |||
| Flexbox, | |||
| FlexboxItem, | |||
| XInput, | |||
| XButton, | |||
| Box | |||
| }, | |||
| created() { | |||
| this.randStr = Math.random().toString() | |||
| }, | |||
| data() { | |||
| return { | |||
| randStr: "", | |||
| msg: '', | |||
| showMessage: false, | |||
| loginForm: { | |||
| username: "", | |||
| password: "", | |||
| captcha: "" | |||
| }, | |||
| token: this.$store.getters.name | |||
| } | |||
| }, | |||
| methods: { | |||
| reRand() { | |||
| this.randStr = Math.random().toString() | |||
| }, | |||
| sub() { | |||
| // console.log(this.loginForm.username, this.loginForm.password, this.loginForm.captcha) | |||
| var username = this.loginForm.username | |||
| var password = this.loginForm.password | |||
| var captcha = this.loginForm.captcha | |||
| const reg = /^[a-zA-Z0-9]+$/ | |||
| if (!reg.test(username)) { | |||
| this.msg = "请输入正确的用户名" | |||
| this.showMessage = true | |||
| return | |||
| } | |||
| if (password.length < 6) { | |||
| this.msg = "密码不能小于6位" | |||
| this.showMessage = true | |||
| return | |||
| } | |||
| if (captcha.length != 5) { | |||
| this.msg = "验证码有误" | |||
| this.showMessage = true | |||
| return | |||
| } | |||
| this.msg = "" | |||
| login(this.loginForm).then(response => { | |||
| if (response.code != 200) { | |||
| this.msg = response.message | |||
| this.showMessage = true | |||
| } else { | |||
| if (response.result.type == "sa" || response.result.type == "ca") { | |||
| this.msg = '无权登录' | |||
| this.showMessage = true | |||
| } else { | |||
| this.$store.dispatch("Login", response.result) | |||
| .then(() => { | |||
| this.$router.push({path: "/order/ratiostatistics"}); | |||
| }); | |||
| } | |||
| } | |||
| }) | |||
| // this.showMessage = true | |||
| } | |||
| }, | |||
| computed: { | |||
| captcha: function () { | |||
| return process.env.BASE_API + "service-user/login/captcha?rand=" + this.randStr | |||
| } | |||
| } | |||
| } | |||
| </script> | |||
| <style> | |||
| .vux-demo { | |||
| text-align: center; | |||
| } | |||
| .logo { | |||
| width: 100px; | |||
| height: 100px | |||
| } | |||
| .weui-input::placeholder { | |||
| color: white; | |||
| } | |||
| .bjimg { | |||
| position:absolute; | |||
| width:100%; | |||
| height:100%; | |||
| background-image: url('../assets/background.png'); | |||
| overflow:hidden | |||
| } | |||
| .inputcss { | |||
| border: 1px solid hsla(0, 0%, 100%, .1); | |||
| background: rgba(0, 0, 0, .1); | |||
| border-radius: 5px; | |||
| color: white; | |||
| margin-top: 8px; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,177 @@ | |||
| <template> | |||
| <div> | |||
| <div class="headercss"> | |||
| <x-header :left-options="{backText: ''}">交易管理</x-header> | |||
| </div> | |||
| <div style="padding-top: 15px;margin-top: 35px"> | |||
| <flexbox style="margin-bottom:3px"> | |||
| <flexbox-item> | |||
| <selector title="选择门店" :options="opts" v-model="listQuery.shopId" :value-map="['id','name']" | |||
| @on-change="fetchData()"></selector> | |||
| </flexbox-item> | |||
| </flexbox> | |||
| <flexbox> | |||
| <flexbox-item :span="3"> | |||
| <x-button type="primary" mini style="margin-left: 15px;" @click.native="nowOrder">当日</x-button> | |||
| </flexbox-item> | |||
| <flexbox-item> | |||
| <calendar v-model="date" title="切换日期" disable-future placeholder="" @on-change="fetchData()" ></calendar> | |||
| </flexbox-item> | |||
| </flexbox> | |||
| </div> | |||
| <card class="cardcss"> | |||
| <div slot="content" class="card-demo-flex card-demo-content01"> | |||
| <div class="vux-1px-r"> | |||
| <span style=" color: #f74c31;">{{todayAmount}}</span> | |||
| <br/> | |||
| 实收总金额 | |||
| </div> | |||
| <div class="vux-1px-r"> | |||
| <span>{{todayCount}}</span> | |||
| <br/> | |||
| 收单数 | |||
| </div> | |||
| <div class="vux-1px-r"> | |||
| <span>{{todayRefundAmount}}</span> | |||
| <br/> | |||
| 退款总金额 | |||
| </div> | |||
| <div> | |||
| <span>{{todayRefundCount}}</span> | |||
| <br/> | |||
| 退款笔数 | |||
| </div> | |||
| </div> | |||
| </card> | |||
| <div style="padding: 2px 0px 51px 0px;margin-top: 10px"> | |||
| <x-table full-bordered class="xtable"> | |||
| <thead> | |||
| <tr> | |||
| <th width="28%">支付方式</th> | |||
| <th>消费金额</th> | |||
| <th>折扣金额</th> | |||
| <th>消费笔数</th> | |||
| <th>退款金额</th> | |||
| <th>退款笔数</th> | |||
| <th>实收金额</th> | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| <tr v-for="vo in list"> | |||
| <td>{{vo.payTypeName}}</td> | |||
| <td>{{vo.payAmount}}</td> | |||
| <td>{{vo.discountAmount}}</td> | |||
| <td>{{vo.payCount}}</td> | |||
| <td>{{vo.refundAmount}}</td> | |||
| <td>{{vo.refundCount}}</td> | |||
| <td>{{vo.amount}}</td> | |||
| </tr> | |||
| </tbody> | |||
| </x-table> | |||
| </div> | |||
| <myfooter activeIndex="2"> | |||
| </myfooter> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import {XTable, Tabbar, TabbarItem, Flexbox, FlexboxItem, Calendar, Selector, Card, dateFormat} from 'vux' | |||
| import {orderStatistics} from '@/api/order' | |||
| import {storeList} from '@/api/store' | |||
| import Myfooter from '@/components/layout/Myfooter' | |||
| export default { | |||
| components: { | |||
| XTable, Tabbar, TabbarItem, Flexbox, FlexboxItem, Calendar, Selector, Card,Myfooter | |||
| }, | |||
| data() { | |||
| return { | |||
| date: '', | |||
| token: this.$store.getters.name, | |||
| opts: [], | |||
| listQuery: { | |||
| shopId: '', | |||
| timeMode: '1', | |||
| }, | |||
| list: null, | |||
| todayAmount: 0, | |||
| todayCount: 0, | |||
| todayRefundAmount: 0, | |||
| todayRefundCount: 0, | |||
| } | |||
| }, | |||
| created() { | |||
| storeList().then(response => { | |||
| if (response.result.rows.length > 0) { | |||
| this.opts = response.result.rows | |||
| } | |||
| }).then(() => { | |||
| this.listQuery.shopId = this.opts[0]['id'] | |||
| }) | |||
| }, | |||
| methods: { | |||
| fetchData() { | |||
| if (!this.date) { | |||
| this.date = dateFormat(new Date(), 'YYYY-MM-DD') | |||
| } | |||
| this.listQuery.startTime=this.date | |||
| this.listQuery.endTime=this.date | |||
| orderStatistics(this.listQuery).then(response => { | |||
| this.list = response.result.payChannels | |||
| }) | |||
| }, | |||
| nowOrder(){ | |||
| this.date='' | |||
| this.fetchData() | |||
| } | |||
| }, | |||
| watch: { | |||
| "list": function (list) { | |||
| let payAmount = 0, payCount = 0, refundAmount = 0, refundCount = 0 | |||
| list.forEach(function (x) { | |||
| payAmount += x.amount | |||
| payCount += x.payCount | |||
| refundAmount += x.refundAmount | |||
| refundCount += x.refundCount | |||
| }); | |||
| this.todayAmount = payAmount.toFixed(2) | |||
| this.todayCount = payCount | |||
| this.todayRefundAmount = refundAmount.toFixed(2) | |||
| this.todayRefundCount = refundCount | |||
| } | |||
| } | |||
| } | |||
| </script> | |||
| <style scoped lang="less"> | |||
| @import '~vux/src/styles/1px.less'; | |||
| .card-demo-flex { | |||
| display: flex; | |||
| } | |||
| .card-demo-content01 { | |||
| padding: 6px 0; | |||
| } | |||
| .card-padding { | |||
| padding: 15px; | |||
| } | |||
| .card-demo-flex > div { | |||
| flex: 1; | |||
| text-align: center; | |||
| font-size: 14px; | |||
| } | |||
| .cardcss { | |||
| margin-top: 0px; | |||
| } | |||
| .xtable{ | |||
| font-size: 10px; | |||
| } | |||
| thead th { | |||
| /*font-weight: bold;*/ | |||
| } | |||
| .headercss{ | |||
| position: fixed;top: 0px;width: 100%; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,25 @@ | |||
| // The Vue build version to load with the `import` command | |||
| // (runtime-only or standalone) has been set in webpack.base.conf with an alias. | |||
| import Vue from 'vue' | |||
| import store from './store' | |||
| import App from './App' | |||
| import router from './router' | |||
| import '@/permission' // 权限 | |||
| Vue.config.productionTip = false | |||
| import { XHeader ,Toast ,ToastPlugin,XButton } from 'vux' | |||
| console.log(navigator.userAgent) | |||
| import ElementUI from 'element-ui' | |||
| import 'element-ui/lib/theme-chalk/index.css' | |||
| import locale from 'element-ui/lib/locale/lang/zh-CN' | |||
| Vue.component('x-header', XHeader) | |||
| Vue.component('toast', Toast) | |||
| Vue.use(ToastPlugin) | |||
| Vue.component('x-button',XButton ) | |||
| Vue.use(ElementUI, { locale }) | |||
| /* eslint-disable no-new */ | |||
| new Vue({ | |||
| router, | |||
| store, | |||
| render: h => h(App) | |||
| }).$mount('#app-box') | |||
| @@ -0,0 +1,33 @@ | |||
| import router from './router' | |||
| import store from './store' | |||
| import { | |||
| getToken | |||
| } from '@/utils/auth' // 验权 | |||
| const whiteList = ['/login'] | |||
| router.beforeEach((to, from, next) => { | |||
| if (getToken()) { | |||
| if (to.path === '/login') { | |||
| next({ | |||
| path: '/order/ratiostatistics' | |||
| }) | |||
| } else { | |||
| if (!store.getters.usertype) { | |||
| store.dispatch('GetInfo').then(res => { | |||
| next() | |||
| }) | |||
| } else { | |||
| next() | |||
| } | |||
| } | |||
| } else { | |||
| if (whiteList.indexOf(to.path) !== -1) { | |||
| next() | |||
| } else { | |||
| next('/login') | |||
| } | |||
| } | |||
| }) | |||
| router.afterEach((to, from) => { | |||
| // console.log(to) | |||
| }) | |||
| @@ -0,0 +1 @@ | |||
| module.exports = file => require('@/components/' + file + '.vue').default // vue-loader at least v13.0.0+ | |||
| @@ -0,0 +1 @@ | |||
| module.exports = file => () => import('@/components/' + file + '.vue') | |||
| @@ -0,0 +1,43 @@ | |||
| import Vue from 'vue' | |||
| import Router from 'vue-router' | |||
| const _import = require('./_import_' + process.env.NODE_ENV) | |||
| Vue.use(Router) | |||
| export default new Router({ | |||
| routes: [ | |||
| { | |||
| path: '/', | |||
| name: '交易占比', | |||
| component: _import('order/ratiostatistics') | |||
| }, | |||
| { | |||
| path: '/login', | |||
| name: '登录', | |||
| component: _import('login') | |||
| }, | |||
| { | |||
| path: '/order/ratiostatistics', | |||
| name: '交易占比', | |||
| component: _import('order/ratiostatistics') | |||
| }, | |||
| { | |||
| path: '/goods/index', | |||
| name: '商品管理', | |||
| component: _import('goods/index') | |||
| }, | |||
| { | |||
| path: '/goods/add', | |||
| name: '商品添加', | |||
| component: _import('goods/add') | |||
| }, | |||
| { | |||
| path: '/goods/update/:id', | |||
| name: '商品添加', | |||
| component: _import('goods/edit') | |||
| }, | |||
| { | |||
| path: '/goods/type', | |||
| name: '商品类型', | |||
| component: _import('goods/type') | |||
| } | |||
| ] | |||
| }) | |||
| @@ -0,0 +1,8 @@ | |||
| const getters = { | |||
| token: state => state.user.token, | |||
| name: state => state.user.name, | |||
| usertype: state => state.user.type, | |||
| orgId: state => state.user.orgId, | |||
| } | |||
| export default getters | |||
| @@ -0,0 +1,13 @@ | |||
| import Vue from 'vue' | |||
| import Vuex from 'vuex' | |||
| import user from './modules/user' | |||
| import getters from './getters' | |||
| Vue.use(Vuex) | |||
| const store = new Vuex.Store({ | |||
| modules: { | |||
| user, | |||
| }, | |||
| getters | |||
| }) | |||
| export default store | |||
| @@ -0,0 +1,65 @@ | |||
| import { | |||
| getInfo, | |||
| } from '@/api/login' | |||
| import { | |||
| getToken, | |||
| setToken, | |||
| removeToken | |||
| } from '@/utils/auth' | |||
| const user = { | |||
| state: { | |||
| token: getToken(), | |||
| name: '', | |||
| type: '', | |||
| orgId: '', | |||
| }, | |||
| mutations: { | |||
| SET_TOKEN: (state, token) => { | |||
| state.token = token | |||
| }, | |||
| SET_NAME: (state, name) => { | |||
| state.name = name | |||
| }, | |||
| SET_TYPE: (state, type) => { | |||
| state.type = type | |||
| }, | |||
| SET_ORGID: (state, orgId) => { | |||
| state.orgId = orgId | |||
| }, | |||
| }, | |||
| actions: { | |||
| // 登录 | |||
| Login({ | |||
| commit | |||
| }, userInfo) { | |||
| setToken(userInfo.token) | |||
| commit('SET_TOKEN', userInfo.token) | |||
| commit('SET_NAME', userInfo.name) | |||
| commit('SET_TYPE', userInfo.type) | |||
| commit('SET_ORGID', userInfo.orgId) | |||
| }, | |||
| // 获取用户信息 | |||
| GetInfo({ | |||
| commit, | |||
| state | |||
| }) { | |||
| return new Promise((resolve, reject) => { | |||
| getInfo().then(response => { | |||
| const userInfo = response.result | |||
| commit('SET_NAME', userInfo.name) | |||
| commit('SET_TYPE', userInfo.type) | |||
| commit('SET_ORGID', userInfo.orgId) | |||
| resolve() | |||
| }).catch(error => { | |||
| reject(error) | |||
| }) | |||
| }) | |||
| }, | |||
| } | |||
| } | |||
| export default user | |||
| @@ -0,0 +1,16 @@ | |||
| import Cookies from 'js-cookie' | |||
| const TokenKey = 'Admin-Token' | |||
| export function getToken() { | |||
| // return "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTUyMTc4MzIwNX0.59_l_ir1uXS3v5PIQxA721UzSyJIcM8WceqZJAFEHnGIDHtQ8N_pkL7096eGvMPAqSCeOZabnALosqeovUZ0Hw" | |||
| return Cookies.get(TokenKey) | |||
| } | |||
| export function setToken(token) { | |||
| return Cookies.set(TokenKey, token) | |||
| } | |||
| export function removeToken() { | |||
| return Cookies.remove(TokenKey) | |||
| } | |||
| @@ -0,0 +1,72 @@ | |||
| import axios from 'axios' | |||
| import { | |||
| Message | |||
| } from 'element-ui' | |||
| import store from '../store' | |||
| import { | |||
| getToken | |||
| } from '@/utils/auth' | |||
| // 创建axios实例 | |||
| const service = axios.create({ | |||
| baseURL: process.env.BASE_API, // api的base_url | |||
| timeout: 30000, // 请求超时时间 | |||
| withCredentials: true | |||
| }) | |||
| // request拦截器 | |||
| service.interceptors.request.use(config => { | |||
| const token = getToken() | |||
| if (token) { | |||
| config.headers['Authorization'] = 'Bearer ' + token // 让每个请求携带自定义token 请根据实际情况自行修改 | |||
| } | |||
| return config | |||
| }, error => { | |||
| // Do something with request error | |||
| console.log(error) // for debug | |||
| Promise.reject(error) | |||
| }) | |||
| // respone拦截器 | |||
| service.interceptors.response.use( | |||
| response => { | |||
| /** | |||
| * code为非200是抛错 可结合自己业务进行修改 | |||
| */ | |||
| const res = response.data | |||
| if (res.code !== 200 && res.code !== '200') { | |||
| Message({ | |||
| message: res.message, | |||
| type: 'error', | |||
| duration: 2 * 1000 | |||
| }) | |||
| // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了; | |||
| if (res.code === 50008 || res.code === 50012 || res.code === 50014) { | |||
| MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', { | |||
| confirmButtonText: '重新登录', | |||
| cancelButtonText: '取消', | |||
| type: 'warning' | |||
| }).then(() => { | |||
| store.dispatch('FedLogOut').then(() => { | |||
| location.reload() // 为了重新实例化vue-router对象 避免bug | |||
| }) | |||
| }) | |||
| } | |||
| return Promise.reject('error') | |||
| } else { | |||
| return response.data | |||
| } | |||
| }, | |||
| error => { | |||
| console.log(error) // for debug | |||
| Message({ | |||
| message: error.message, | |||
| type: 'error', | |||
| duration: 5 * 1000 | |||
| }) | |||
| return Promise.reject(error) | |||
| } | |||
| ) | |||
| export default service | |||