@@ -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 |