├── .gitignore ├── README.md ├── client ├── .babelrc ├── .editorconfig ├── .postcssrc.js ├── admin.html ├── build │ ├── build.js │ ├── check-versions.js │ ├── logo.png │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── config │ ├── dev.env.js │ ├── index.js │ └── prod.env.js ├── index.html ├── package.json ├── src │ ├── App.vue │ ├── admin.js │ ├── api │ │ ├── admin.js │ │ └── client.js │ ├── assets │ │ ├── css │ │ │ ├── common.less │ │ │ └── var.less │ │ ├── font │ │ │ ├── iconfont.css │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.svg │ │ │ ├── iconfont.ttf │ │ │ └── iconfont.woff │ │ └── img │ │ │ ├── banner1.jpg │ │ │ ├── banner2.jpg │ │ │ ├── banner3.jpg │ │ │ ├── code.png │ │ │ ├── index1.gif │ │ │ └── index2.gif │ ├── components │ │ ├── FadeSwiper.vue │ │ ├── FixedNav.vue │ │ ├── GoodsItem.vue │ │ ├── NoticeList.vue │ │ ├── NumberInput.vue │ │ ├── Popup.vue │ │ ├── Radio.vue │ │ ├── SectionHeader.vue │ │ ├── Slick.vue │ │ ├── Tag.vue │ │ ├── TextInput.vue │ │ ├── TipsInput.vue │ │ └── ZoomImg.vue │ ├── config │ │ ├── axios-admin.js │ │ └── axios-client.js │ ├── main.js │ ├── pages │ │ ├── ErrorPage.vue │ │ ├── admin │ │ │ ├── AdminLogin.vue │ │ │ ├── Backstage.vue │ │ │ ├── EditAdmin.vue │ │ │ ├── EditGoods.vue │ │ │ ├── EditOrders.vue │ │ │ ├── EditUser.vue │ │ │ ├── Goods.vue │ │ │ ├── Messages.vue │ │ │ └── Orders.vue │ │ └── client │ │ │ ├── Cart.vue │ │ │ ├── GoodsDetail.vue │ │ │ ├── GoodsList.vue │ │ │ ├── Mall.vue │ │ │ ├── MallIndex.vue │ │ │ ├── MallLogin.vue │ │ │ ├── MallShow.vue │ │ │ ├── MyData.vue │ │ │ ├── MyOrder.vue │ │ │ └── Personal.vue │ ├── router │ │ ├── admin.js │ │ └── client.js │ ├── store │ │ ├── actions.js │ │ ├── getters.js │ │ ├── index.js │ │ ├── mutation-types.js │ │ ├── mutations.js │ │ └── state.js │ └── util │ │ └── util.js └── static │ └── .gitkeep ├── screen ├── cart.gif ├── data.gif ├── goodsDetail.gif ├── index.gif ├── login.gif ├── manage.gif ├── orders.gif ├── search.gif └── type.gif └── server ├── app.js ├── bin └── www ├── config └── sequelizeBase.js ├── controllers ├── goods.js ├── goodsAdmin.js ├── msgAdmin.js ├── orderAdmin.js ├── user.js └── userAdmin.js ├── models ├── AdminModel.js ├── CommentModel.js ├── GoodsDetailModel.js ├── GoodsModel.js ├── MessageModel.js ├── OrderModel.js ├── ReplyModel.js ├── TypeModel.js └── UserModel.js ├── package-lock.json ├── package.json └── routes ├── admin.js ├── mall.js └── user.js /.gitignore: -------------------------------------------------------------------------------- 1 | client/node_modules/ 2 | server/node_modules/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-MoreMall 2 | 一个基于vue全家桶的pc端综合性网上购物商城,包括前台商城和后台管理系统,请求数据采用axios,后端采用koa2,数据库采用mysql。 3 | 4 | ## 已完成功能 5 | 6 | 1. 登录注册; 7 | 2. 商品分类; 8 | 3. 购买商品; 9 | 4. 搜索商品; 10 | 5. 购物车; 11 | 6. 订单管理; 12 | 7. 商品问答; 13 | 8. 商品评价; 14 | 9. 修改资料; 15 | 10. 后台管理; 16 | 17 | ## 项目截图 18 | 19 | 首页 20 | 21 | ![](screen/index.gif) 22 | 23 | 商品分类页 24 | 25 | ![](screen/type.gif) 26 | 27 | 搜索页 28 | 29 | ![](screen/search.gif) 30 | 31 | 商品详情页 32 | 33 | ![](screen/goodsDetail.gif) 34 | 35 | 登录页 36 | 37 | ![](screen/login.gif) 38 | 39 | 购物车页 40 | 41 | ![](screen/cart.gif) 42 | 43 | 订单页 44 | 45 | ![](screen/orders.gif) 46 | 47 | 个人资料页 48 | 49 | ![](screen/data.gif) 50 | 51 | 后台管理页 52 | 53 | ![](screen/manage.gif) -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false 5 | }], 6 | "stage-2" 7 | ], 8 | "plugins": ["transform-runtime"] 9 | } 10 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /client/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "postcss-import": {}, 7 | "autoprefixer": {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /client/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MoreMall后台管理 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, function (err, stats) { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /client/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | function exec (cmd) { 7 | return require('child_process').execSync(cmd).toString().trim() 8 | } 9 | 10 | const versionRequirements = [ 11 | { 12 | name: 'node', 13 | currentVersion: semver.clean(process.version), 14 | versionRequirement: packageConfig.engines.node 15 | } 16 | ] 17 | 18 | if (shell.which('npm')) { 19 | versionRequirements.push({ 20 | name: 'npm', 21 | currentVersion: exec('npm --version'), 22 | versionRequirement: packageConfig.engines.npm 23 | }) 24 | } 25 | 26 | module.exports = function () { 27 | const warnings = [] 28 | for (let i = 0; i < versionRequirements.length; i++) { 29 | const mod = versionRequirements[i] 30 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 31 | warnings.push(mod.name + ': ' + 32 | chalk.red(mod.currentVersion) + ' should be ' + 33 | chalk.green(mod.versionRequirement) 34 | ) 35 | } 36 | } 37 | 38 | if (warnings.length) { 39 | console.log('') 40 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 41 | console.log() 42 | for (let i = 0; i < warnings.length; i++) { 43 | const warning = warnings[i] 44 | console.log(' ' + warning) 45 | } 46 | console.log() 47 | process.exit(1) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /client/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/client/build/logo.png -------------------------------------------------------------------------------- /client/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const pkg = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | return path.posix.join(assetsSubDirectory, _path) 12 | } 13 | 14 | exports.cssLoaders = function (options) { 15 | options = options || {} 16 | 17 | const cssLoader = { 18 | loader: 'css-loader', 19 | options: { 20 | sourceMap: options.sourceMap 21 | } 22 | } 23 | 24 | var postcssLoader = { 25 | loader: 'postcss-loader', 26 | options: { 27 | sourceMap: options.sourceMap 28 | } 29 | } 30 | 31 | // generate loader string to be used with extract text plugin 32 | function generateLoaders (loader, loaderOptions) { 33 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 34 | if (loader) { 35 | loaders.push({ 36 | loader: loader + '-loader', 37 | options: Object.assign({}, loaderOptions, { 38 | sourceMap: options.sourceMap 39 | }) 40 | }) 41 | } 42 | 43 | // Extract CSS when that option is specified 44 | // (which is the case during production build) 45 | if (options.extract) { 46 | return ExtractTextPlugin.extract({ 47 | use: loaders, 48 | fallback: 'vue-style-loader' 49 | }) 50 | } else { 51 | return ['vue-style-loader'].concat(loaders) 52 | } 53 | } 54 | 55 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 56 | return { 57 | css: generateLoaders(), 58 | postcss: generateLoaders(), 59 | less: generateLoaders('less'), 60 | sass: generateLoaders('sass', { indentedSyntax: true }), 61 | scss: generateLoaders('sass'), 62 | stylus: generateLoaders('stylus'), 63 | styl: generateLoaders('stylus') 64 | } 65 | } 66 | 67 | // Generate loaders for standalone style files (outside of .vue) 68 | exports.styleLoaders = function (options) { 69 | const output = [] 70 | const loaders = exports.cssLoaders(options) 71 | for (const extension in loaders) { 72 | const loader = loaders[extension] 73 | output.push({ 74 | test: new RegExp('\\.' + extension + '$'), 75 | use: loader 76 | }) 77 | } 78 | return output 79 | } 80 | 81 | exports.createNotifierCallback = function () { 82 | const notifier = require('node-notifier') 83 | 84 | return (severity, errors) => { 85 | if (severity !== 'error') { 86 | return 87 | } 88 | const error = errors[0] 89 | 90 | const filename = error.file && error.file.split('!').pop() 91 | notifier.notify({ 92 | title: pkg.name, 93 | message: severity + ': ' + error.name, 94 | subtitle: filename || '', 95 | icon: path.join(__dirname, 'logo.png') 96 | }) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /client/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | 10 | module.exports = { 11 | loaders: utils.cssLoaders({ 12 | sourceMap: sourceMapEnabled, 13 | extract: isProduction 14 | }), 15 | cssSourceMap: sourceMapEnabled, 16 | cacheBusting: config.dev.cacheBusting, 17 | transformToRequire: { 18 | video: 'src', 19 | source: 'src', 20 | img: 'src', 21 | image: 'xlink:href' 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | module.exports = { 12 | context: path.resolve(__dirname, '../'), 13 | entry: { 14 | app: './src/main.js', 15 | admin: './src/admin.js', 16 | }, 17 | output: { 18 | path: config.build.assetsRoot, 19 | filename: '[name].js', 20 | publicPath: process.env.NODE_ENV === 'production' 21 | ? config.build.assetsPublicPath 22 | : config.dev.assetsPublicPath 23 | }, 24 | resolve: { 25 | extensions: ['.js', '.vue', '.json'], 26 | alias: { 27 | 'vue$': 'vue/dist/vue.esm.js', 28 | '@': resolve('src'), 29 | } 30 | }, 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.vue$/, 35 | loader: 'vue-loader', 36 | options: vueLoaderConfig 37 | }, 38 | { 39 | test: /\.js$/, 40 | loader: 'babel-loader', 41 | include: [resolve('src'), resolve('test')] 42 | }, 43 | { 44 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 45 | loader: 'url-loader', 46 | options: { 47 | limit: 10000, 48 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 49 | } 50 | }, 51 | { 52 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 53 | loader: 'url-loader', 54 | options: { 55 | limit: 10000, 56 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 57 | } 58 | }, 59 | { 60 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 61 | loader: 'url-loader', 62 | options: { 63 | limit: 10000, 64 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 65 | } 66 | } 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /client/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const baseWebpackConfig = require('./webpack.base.conf') 7 | const HtmlWebpackPlugin = require('html-webpack-plugin') 8 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 9 | const portfinder = require('portfinder') 10 | 11 | const devWebpackConfig = merge(baseWebpackConfig, { 12 | module: { 13 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 14 | }, 15 | // cheap-module-eval-source-map is faster for development 16 | devtool: config.dev.devtool, 17 | 18 | // these devServer options should be customized in /config/index.js 19 | devServer: { 20 | clientLogLevel: 'warning', 21 | historyApiFallback: true, 22 | hot: true, 23 | compress: true, 24 | host: process.env.HOST || config.dev.host, 25 | port: process.env.PORT || config.dev.port, 26 | open: config.dev.autoOpenBrowser, 27 | overlay: config.dev.errorOverlay ? { 28 | warnings: false, 29 | errors: true, 30 | } : false, 31 | publicPath: config.dev.assetsPublicPath, 32 | proxy: config.dev.proxyTable, 33 | quiet: true, // necessary for FriendlyErrorsPlugin 34 | watchOptions: { 35 | poll: config.dev.poll, 36 | } 37 | }, 38 | plugins: [ 39 | new webpack.DefinePlugin({ 40 | 'process.env': require('../config/dev.env') 41 | }), 42 | new webpack.HotModuleReplacementPlugin(), 43 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 44 | new webpack.NoEmitOnErrorsPlugin(), 45 | // https://github.com/ampedandwired/html-webpack-plugin 46 | new HtmlWebpackPlugin({ 47 | filename: 'index.html', 48 | template: 'index.html', 49 | inject: true, 50 | chunks:['app'] 51 | }), 52 | new HtmlWebpackPlugin({ 53 | filename: 'admin.html', 54 | template: 'admin.html', 55 | inject: true, 56 | chunks:['admin'] 57 | }), 58 | ] 59 | }) 60 | 61 | module.exports = new Promise((resolve, reject) => { 62 | portfinder.basePort = process.env.PORT || config.dev.port 63 | portfinder.getPort((err, port) => { 64 | if (err) { 65 | reject(err) 66 | } else { 67 | // publish the new Port, necessary for e2e tests 68 | process.env.PORT = port 69 | // add port to devServer config 70 | devWebpackConfig.devServer.port = port 71 | 72 | // Add FriendlyErrorsPlugin 73 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 74 | compilationSuccessInfo: { 75 | messages: [`Your application is running here: http://${config.dev.host}:${port}`], 76 | }, 77 | onErrors: config.dev.notifyOnErrors 78 | ? utils.createNotifierCallback() 79 | : undefined 80 | })) 81 | 82 | resolve(devWebpackConfig) 83 | } 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /client/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | 13 | const env = require('../config/prod.env') 14 | 15 | const webpackConfig = merge(baseWebpackConfig, { 16 | module: { 17 | rules: utils.styleLoaders({ 18 | sourceMap: config.build.productionSourceMap, 19 | extract: true, 20 | usePostCSS: true 21 | }) 22 | }, 23 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 24 | output: { 25 | path: config.build.assetsRoot, 26 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | // UglifyJs do not support ES6+, you can also use babel-minify for better treeshaking: https://github.com/babel/minify 35 | new webpack.optimize.UglifyJsPlugin({ 36 | compress: { 37 | warnings: false 38 | }, 39 | sourceMap: config.build.productionSourceMap, 40 | parallel: true 41 | }), 42 | // extract css into its own file 43 | new ExtractTextPlugin({ 44 | filename: utils.assetsPath('css/[name].[contenthash].css'), 45 | // set the following option to `true` if you want to extract CSS from 46 | // codesplit chunks into this main css file as well. 47 | // This will result in *all* of your app's CSS being loaded upfront. 48 | allChunks: false, 49 | }), 50 | // Compress extracted CSS. We are using this plugin so that possible 51 | // duplicated CSS from different components can be deduped. 52 | new OptimizeCSSPlugin({ 53 | cssProcessorOptions: config.build.productionSourceMap 54 | ? { safe: true, map: { inline: false } } 55 | : { safe: true } 56 | }), 57 | // generate dist index.html with correct asset hash for caching. 58 | // you can customize output by editing /index.html 59 | // see https://github.com/ampedandwired/html-webpack-plugin 60 | new HtmlWebpackPlugin({ 61 | filename: config.build.index, 62 | template: 'index.html', 63 | inject: true, 64 | minify: { 65 | removeComments: true, 66 | collapseWhitespace: true, 67 | removeAttributeQuotes: true 68 | // more options: 69 | // https://github.com/kangax/html-minifier#options-quick-reference 70 | }, 71 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 72 | chunksSortMode: 'dependency', 73 | chunks:['manifest','vendor','app'] 74 | }), 75 | //多页面 76 | new HtmlWebpackPlugin({ 77 | filename: config.build.admin, 78 | template: 'admin.html', 79 | inject: true, 80 | minify: { 81 | removeComments: true, 82 | collapseWhitespace: true, 83 | removeAttributeQuotes: true 84 | // more options: 85 | // https://github.com/kangax/html-minifier#options-quick-reference 86 | }, 87 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 88 | chunksSortMode: 'dependency', 89 | chunks:['manifest','vendor','admin'] 90 | }), 91 | // keep module.id stable when vender modules does not change 92 | new webpack.HashedModuleIdsPlugin(), 93 | // enable scope hoisting 94 | new webpack.optimize.ModuleConcatenationPlugin(), 95 | // split vendor js into its own file 96 | new webpack.optimize.CommonsChunkPlugin({ 97 | name: 'vendor', 98 | minChunks: function (module) { 99 | // any required modules inside node_modules are extracted to vendor 100 | return ( 101 | module.resource && 102 | /\.js$/.test(module.resource) && 103 | module.resource.indexOf( 104 | path.join(__dirname, '../node_modules') 105 | ) === 0 106 | ) 107 | } 108 | }), 109 | // extract webpack runtime and module manifest to its own file in order to 110 | // prevent vendor hash from being updated whenever app bundle is updated 111 | new webpack.optimize.CommonsChunkPlugin({ 112 | name: 'manifest', 113 | minChunks: Infinity 114 | }), 115 | // This instance extracts shared chunks from code splitted chunks and bundles them 116 | // in a separate chunk, similar to the vendor chunk 117 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 118 | new webpack.optimize.CommonsChunkPlugin({ 119 | name: 'app', 120 | async: 'vendor-async', 121 | children: true, 122 | minChunks: 3 123 | }), 124 | 125 | // copy custom static assets 126 | new CopyWebpackPlugin([ 127 | { 128 | from: path.resolve(__dirname, '../static'), 129 | to: config.build.assetsSubDirectory, 130 | ignore: ['.*'] 131 | } 132 | ]) 133 | ] 134 | }) 135 | 136 | if (config.build.productionGzip) { 137 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 138 | 139 | webpackConfig.plugins.push( 140 | new CompressionWebpackPlugin({ 141 | asset: '[path].gz[query]', 142 | algorithm: 'gzip', 143 | test: new RegExp( 144 | '\\.(' + 145 | config.build.productionGzipExtensions.join('|') + 146 | ')$' 147 | ), 148 | threshold: 10240, 149 | minRatio: 0.8 150 | }) 151 | ) 152 | } 153 | 154 | if (config.build.bundleAnalyzerReport) { 155 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 156 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 157 | } 158 | 159 | module.exports = webpackConfig 160 | -------------------------------------------------------------------------------- /client/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /client/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.2.4 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | // Use Eslint Loader? 24 | // If true, your code will be linted during bundling and 25 | // linting errors and warnings will be shown in the console. 26 | useEslint: true, 27 | // If true, eslint errors and warnings will also be shown in the error overlay 28 | // in the browser. 29 | showEslintErrorsInOverlay: false, 30 | 31 | /** 32 | * Source Maps 33 | */ 34 | 35 | // https://webpack.js.org/configuration/devtool/#development 36 | devtool: 'eval-source-map', 37 | 38 | // If you have problems debugging vue-files in devtools, 39 | // set this to false - it *may* help 40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 41 | cacheBusting: true, 42 | 43 | // CSS Sourcemaps off by default because relative paths are "buggy" 44 | // with this option, according to the CSS-Loader README 45 | // (https://github.com/webpack/css-loader#sourcemaps) 46 | // In our experience, they generally work as expected, 47 | // just be aware of this issue when enabling this option. 48 | cssSourceMap: false, 49 | }, 50 | 51 | build: { 52 | // Template for index.html 53 | index: path.resolve(__dirname, '../dist/index.html'), 54 | admin: path.resolve(__dirname, '../dist/admin.html'), 55 | 56 | // Paths 57 | assetsRoot: path.resolve(__dirname, '../dist'), 58 | assetsSubDirectory: 'static', 59 | assetsPublicPath: '', 60 | 61 | /** 62 | * Source Maps 63 | */ 64 | 65 | productionSourceMap: true, 66 | // https://webpack.js.org/configuration/devtool/#production 67 | devtool: '#source-map', 68 | 69 | // Gzip off by default as many popular static hosts such as 70 | // Surge or Netlify already gzip all static assets for you. 71 | // Before setting to `true`, make sure to: 72 | // npm install --save-dev compression-webpack-plugin 73 | productionGzip: false, 74 | productionGzipExtensions: ['js', 'css'], 75 | 76 | // Run the build command with an extra argument to 77 | // View the bundle analyzer report after build finishes: 78 | // `npm run build --report` 79 | // Set to `true` or `false` to always turn it on or off 80 | bundleAnalyzerReport: process.env.npm_config_report 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /client/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MoreMall商城 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shopping-mall", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "cmh1996 <752380178@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "build": "node build/build.js" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.17.1", 14 | "vue": "^2.5.2", 15 | "vue-router": "^3.0.1", 16 | "vuex": "^3.0.1" 17 | }, 18 | "devDependencies": { 19 | "autoprefixer": "^7.1.2", 20 | "babel-core": "^6.22.1", 21 | "babel-loader": "^7.1.1", 22 | "babel-plugin-transform-runtime": "^6.22.0", 23 | "babel-polyfill": "^6.26.0", 24 | "babel-preset-env": "^1.3.2", 25 | "babel-preset-stage-2": "^6.22.0", 26 | "babel-register": "^6.22.0", 27 | "chalk": "^2.0.1", 28 | "copy-webpack-plugin": "^4.0.1", 29 | "css-loader": "^0.28.0", 30 | "eventsource-polyfill": "^0.9.6", 31 | "extract-text-webpack-plugin": "^3.0.0", 32 | "file-loader": "^1.1.4", 33 | "friendly-errors-webpack-plugin": "^1.6.1", 34 | "html-webpack-plugin": "^2.30.1", 35 | "less": "^2.7.3", 36 | "less-loader": "^4.0.5", 37 | "node-notifier": "^5.1.2", 38 | "optimize-css-assets-webpack-plugin": "^3.2.0", 39 | "ora": "^1.2.0", 40 | "portfinder": "^1.0.13", 41 | "postcss-import": "^11.0.0", 42 | "postcss-loader": "^2.0.8", 43 | "rimraf": "^2.6.0", 44 | "semver": "^5.3.0", 45 | "shelljs": "^0.7.6", 46 | "url-loader": "^0.5.8", 47 | "vue-loader": "^13.3.0", 48 | "vue-style-loader": "^3.0.1", 49 | "vue-template-compiler": "^2.5.2", 50 | "webpack": "^3.6.0", 51 | "webpack-bundle-analyzer": "^2.9.0", 52 | "webpack-dev-server": "^2.9.1", 53 | "webpack-merge": "^4.1.0" 54 | }, 55 | "engines": { 56 | "node": ">= 4.0.0", 57 | "npm": ">= 3.0.0" 58 | }, 59 | "browserslist": [ 60 | "> 1%", 61 | "last 2 versions", 62 | "not ie <= 8" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /client/src/admin.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import Vue from 'vue'; 3 | import App from './App'; 4 | import router from './router/admin'; 5 | import store from './store'; 6 | 7 | import './assets/css/common.less'; 8 | import './assets/font/iconfont.css'; 9 | 10 | Vue.config.productionTip = false; 11 | 12 | new Vue({ 13 | el: '#app', 14 | router, 15 | store, 16 | template: '', 17 | components: { App } 18 | }); -------------------------------------------------------------------------------- /client/src/assets/css/common.less: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video,textarea { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | box-sizing: border-box; 18 | font-family: "Microsoft YaHei","Times New Roman",Georgia,Serif; 19 | vertical-align: baseline; 20 | } 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | body { 27 | line-height: 1; 28 | } 29 | a { 30 | text-decoration:none; 31 | outline: none; 32 | color: black; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | input{ 38 | outline: none; 39 | font-family: "微软雅黑"; 40 | padding:0; 41 | margin: 0; 42 | box-sizing: border-box; 43 | } 44 | blockquote, q { 45 | quotes: none; 46 | } 47 | blockquote:before, blockquote:after, 48 | q:before, q:after { 49 | content: ''; 50 | content: none; 51 | } 52 | table { 53 | border-collapse: collapse; 54 | border-spacing: 0; 55 | } 56 | textarea{ 57 | outline: none; 58 | resize:none; 59 | } 60 | button{ 61 | cursor: pointer; 62 | } 63 | .float-left{ 64 | float: left; 65 | } 66 | .float-right{ 67 | float: right; 68 | } 69 | .clear:after{ 70 | display: table; 71 | content: ''; 72 | clear: both; 73 | } 74 | .ellipsis{ 75 | overflow: hidden; 76 | text-overflow:ellipsis; 77 | white-space: nowrap; 78 | } 79 | .ellipsis-two{ 80 | overflow:hidden; 81 | text-overflow:ellipsis; 82 | display:-webkit-box; 83 | -webkit-box-orient:vertical; 84 | -webkit-line-clamp:2; 85 | } 86 | .ellipsis-three{ 87 | overflow:hidden; 88 | text-overflow:ellipsis; 89 | display:-webkit-box; 90 | -webkit-box-orient:vertical; 91 | -webkit-line-clamp:3; 92 | } 93 | 94 | .container{ 95 | width: 1200px; 96 | margin: 0 auto; 97 | } 98 | 99 | .container .leftContainer{ 100 | width: 800px; 101 | padding-top: 20px; 102 | float: left; 103 | overflow: hidden; 104 | } 105 | .container .rightContainer{ 106 | width: 350px; 107 | padding-top: 20px; 108 | float: right; 109 | overflow: hidden; 110 | } 111 | 112 | .minContainer{ 113 | width: 1120px; 114 | margin: 0 auto; 115 | } 116 | .minContainer .leftContainer{ 117 | width: 750px; 118 | padding-top: 20px; 119 | float: left; 120 | overflow: hidden; 121 | } 122 | .minContainer .rightContainer{ 123 | width: 300px; 124 | padding-top: 20px; 125 | float: right; 126 | overflow: hidden; 127 | } 128 | 129 | table{ 130 | width: 100%; 131 | border: 1px solid #e6e8eb; 132 | background-color: white; 133 | thead{ 134 | border-bottom: 1px solid #e6e8eb; 135 | tr{ 136 | height: 40px; 137 | line-height: 40px; 138 | background-color: #fdfdfd; 139 | th{ 140 | text-align: left; 141 | font-weight: 500; 142 | padding: 0 10px; 143 | user-select:none; 144 | } 145 | } 146 | } 147 | tbody{ 148 | font-size: 15px; 149 | tr{ 150 | td{ 151 | padding: 10px 10px; 152 | border-bottom: 1px solid #e6e8eb; 153 | font-size: 13px; 154 | line-height: 18px; 155 | .normal{ 156 | width: 50px; 157 | height: 25px; 158 | color:#0e90d2; 159 | border: 1px solid #0e90d2; 160 | background-color: white; 161 | border-radius: 5px; 162 | margin-right: 5px; 163 | } 164 | .delete{ 165 | width: 50px; 166 | height: 25px; 167 | color:#d4282d; 168 | border: 1px solid #d4282d; 169 | background-color: white; 170 | border-radius: 5px; 171 | } 172 | } 173 | &:hover{ 174 | background-color: rgb(250,250,250); 175 | } 176 | } 177 | } 178 | } 179 | 180 | input::-webkit-outer-spin-button, 181 | input::-webkit-inner-spin-button { 182 | -webkit-appearance: none !important; 183 | margin: 0; 184 | } 185 | input[type="number"]{-moz-appearance:textfield;} -------------------------------------------------------------------------------- /client/src/assets/css/var.less: -------------------------------------------------------------------------------- 1 | @headerBgColor:white; 2 | @navBgColor:white; 3 | @subHeaderBgColor:white; 4 | @bgColor:#f7fafc; 5 | @borderColor:#e6e8eb; 6 | @iconDefaultColor:#adadab; 7 | @iconDeepColor:#737373; 8 | @subBgColor:#f2f2f2; 9 | 10 | @falseColor:#be4141; 11 | 12 | @mainColor:#337da4; 13 | @secondColor:#0e90d2; 14 | @thirdColor:#b4a078; 15 | @forthColor:#199475; 16 | @fifthColor:#4e8858; 17 | @sixthColor:#00bdd1; 18 | @seventhColor:#144459; 19 | @eighthColor:#2f334d; 20 | 21 | @fontWhiteColor:white; 22 | @fontShallowColor:#d8d8d8; 23 | @fontDefaultColor:#7d7d7d; 24 | @fontDeepColor:#263238; 25 | 26 | @dayWeatherBg0:#3667e6; 27 | @dayWeatherBg1:#9ec5fb; 28 | @dayWeatherHeaderBorder:#3664e3; 29 | @nightWeatherBg0:#1a2260; 30 | @nightWeatherBg1:#306ea0; 31 | @nightWeatherHeaderBorder:#0b2e52; 32 | 33 | @popupBgColor:rgba(54,56,71,0.8); 34 | @banColor:#ee1b48; 35 | @confirmColor:#00b0f6; 36 | -------------------------------------------------------------------------------- /client/src/assets/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/client/src/assets/font/iconfont.eot -------------------------------------------------------------------------------- /client/src/assets/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/client/src/assets/font/iconfont.ttf -------------------------------------------------------------------------------- /client/src/assets/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/client/src/assets/font/iconfont.woff -------------------------------------------------------------------------------- /client/src/assets/img/banner1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/client/src/assets/img/banner1.jpg -------------------------------------------------------------------------------- /client/src/assets/img/banner2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/client/src/assets/img/banner2.jpg -------------------------------------------------------------------------------- /client/src/assets/img/banner3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/client/src/assets/img/banner3.jpg -------------------------------------------------------------------------------- /client/src/assets/img/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/client/src/assets/img/code.png -------------------------------------------------------------------------------- /client/src/assets/img/index1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/client/src/assets/img/index1.gif -------------------------------------------------------------------------------- /client/src/assets/img/index2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/client/src/assets/img/index2.gif -------------------------------------------------------------------------------- /client/src/components/FadeSwiper.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 72 | 73 | -------------------------------------------------------------------------------- /client/src/components/FixedNav.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | -------------------------------------------------------------------------------- /client/src/components/GoodsItem.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 45 | 46 | -------------------------------------------------------------------------------- /client/src/components/NoticeList.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 49 | 50 | -------------------------------------------------------------------------------- /client/src/components/NumberInput.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 56 | 57 | -------------------------------------------------------------------------------- /client/src/components/Popup.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 39 | 40 | -------------------------------------------------------------------------------- /client/src/components/Radio.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 36 | 37 | -------------------------------------------------------------------------------- /client/src/components/SectionHeader.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 36 | 37 | -------------------------------------------------------------------------------- /client/src/components/Slick.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 61 | 62 | -------------------------------------------------------------------------------- /client/src/components/Tag.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 29 | 30 | 51 | -------------------------------------------------------------------------------- /client/src/components/TextInput.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 39 | 40 | -------------------------------------------------------------------------------- /client/src/components/TipsInput.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 76 | 77 | -------------------------------------------------------------------------------- /client/src/components/ZoomImg.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 25 | 26 | -------------------------------------------------------------------------------- /client/src/config/axios-admin.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from '../store' 3 | import * as types from '../store/mutation-types' 4 | import router from '../router/admin' 5 | 6 | // axios 配置 7 | axios.defaults.timeout = 5000; 8 | axios.defaults.baseURL = 'http://localhost:3000'; 9 | 10 | // http request 拦截器 11 | axios.interceptors.request.use( 12 | config => { 13 | if (store.state.adminToken) { 14 | config.headers.Authorization = `Bearer ${store.state.adminToken}`; 15 | } 16 | return config; 17 | }, 18 | err => { 19 | return Promise.reject(err); 20 | } 21 | ); 22 | 23 | // http response 拦截器 24 | axios.interceptors.response.use( 25 | response => { 26 | return response; 27 | }, 28 | error => { 29 | if (error.response) { 30 | switch (error.response.status) { 31 | case 401: 32 | // 401 清除token信息并跳转到登录页面 33 | store.commit(types.ADMIN_LOGOUT); 34 | router.replace({ 35 | path: 'login', 36 | query: {redirect: router.currentRoute.fullPath} 37 | }) 38 | } 39 | } 40 | // console.log(JSON.stringify(error));//console : Error: Request failed with status code 402 41 | return Promise.reject(error.response.data) 42 | }); 43 | 44 | export default axios; -------------------------------------------------------------------------------- /client/src/config/axios-client.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from '../store' 3 | import * as types from '../store/mutation-types' 4 | import router from '../router/client' 5 | 6 | // axios 配置 7 | axios.defaults.timeout = 5000; 8 | axios.defaults.baseURL = 'http://localhost:3000'; 9 | 10 | // http request 拦截器 11 | axios.interceptors.request.use( 12 | config => { 13 | if (store.state.clientToken) { 14 | config.headers.Authorization = `Bearer ${store.state.clientToken}`; 15 | } 16 | return config; 17 | }, 18 | err => { 19 | return Promise.reject(err); 20 | } 21 | ); 22 | 23 | // http response 拦截器 24 | axios.interceptors.response.use( 25 | response => { 26 | return response; 27 | }, 28 | error => { 29 | if (error.response) { 30 | switch (error.response.status) { 31 | case 401: 32 | // 401 清除token信息并跳转到登录页面 33 | store.commit(types.CLIENT_LOGOUT); 34 | router.replace({ 35 | path: 'login', 36 | query: {redirect: router.currentRoute.fullPath} 37 | }) 38 | } 39 | } 40 | // console.log(JSON.stringify(error));//console : Error: Request failed with status code 402 41 | return Promise.reject(error.response.data) 42 | }); 43 | 44 | export default axios; -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import Vue from 'vue'; 3 | import App from './App'; 4 | import router from './router/client'; 5 | import store from './store'; 6 | 7 | import './assets/css/common.less'; 8 | import './assets/font/iconfont.css'; 9 | 10 | Vue.config.productionTip = false; 11 | 12 | /* eslint-disable no-new */ 13 | new Vue({ 14 | el: '#app', 15 | router, 16 | store, 17 | template: '', 18 | components: { App } 19 | }); 20 | -------------------------------------------------------------------------------- /client/src/pages/ErrorPage.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | 23 | -------------------------------------------------------------------------------- /client/src/pages/admin/AdminLogin.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 57 | 58 | 104 | -------------------------------------------------------------------------------- /client/src/pages/admin/Backstage.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 81 | 82 | -------------------------------------------------------------------------------- /client/src/pages/admin/EditAdmin.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 72 | 73 | -------------------------------------------------------------------------------- /client/src/pages/admin/EditGoods.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 214 | 215 | -------------------------------------------------------------------------------- /client/src/pages/admin/EditOrders.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 123 | 124 | -------------------------------------------------------------------------------- /client/src/pages/admin/EditUser.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 80 | 81 | -------------------------------------------------------------------------------- /client/src/pages/admin/Goods.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 128 | 129 | -------------------------------------------------------------------------------- /client/src/pages/admin/Messages.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 131 | 132 | -------------------------------------------------------------------------------- /client/src/pages/admin/Orders.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 84 | 85 | -------------------------------------------------------------------------------- /client/src/pages/client/Cart.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 141 | 142 | -------------------------------------------------------------------------------- /client/src/pages/client/GoodsList.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 119 | 120 | -------------------------------------------------------------------------------- /client/src/pages/client/Mall.vue: -------------------------------------------------------------------------------- 1 | 102 | 103 | 160 | 161 | -------------------------------------------------------------------------------- /client/src/pages/client/MallLogin.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 97 | 98 | 156 | -------------------------------------------------------------------------------- /client/src/pages/client/MallShow.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 151 | 152 | -------------------------------------------------------------------------------- /client/src/pages/client/MyData.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 145 | 146 | -------------------------------------------------------------------------------- /client/src/pages/client/MyOrder.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 216 | 217 | -------------------------------------------------------------------------------- /client/src/pages/client/Personal.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 47 | 48 | -------------------------------------------------------------------------------- /client/src/router/admin.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | import store from '@/store'; 5 | 6 | import AdminLogin from '@/pages/admin/AdminLogin' 7 | import Backstage from '@/pages/admin/Backstage' 8 | import EditUser from '@/pages/admin/EditUser' 9 | import EditAdmin from '@/pages/admin/EditAdmin' 10 | import Goods from '@/pages/admin/Goods' 11 | import Orders from '@/pages/admin/Orders' 12 | import EditOrders from '@/pages/admin/EditOrders' 13 | import EditGoods from '@/pages/admin/EditGoods' 14 | import Messages from '@/pages/admin/Messages' 15 | import ErrorPage from '@/pages/ErrorPage' 16 | 17 | Vue.use(Router) 18 | 19 | let router = new Router({ 20 | routes: [ 21 | { 22 | path:"/", 23 | redirect:"/login" 24 | },{ 25 | path: '/login', 26 | name: 'AdminLogin', 27 | component: AdminLogin 28 | },{ 29 | path: '/backstage', 30 | name: 'Backstage', 31 | redirect:"/backstage/editUser", 32 | component: Backstage, 33 | children: [ 34 | { 35 | path: 'editUser', 36 | name: 'EditUser', 37 | component: EditUser, 38 | meta: { 39 | requireLogin:true, 40 | }, 41 | },{ 42 | path: 'editAdmin', 43 | name: 'EditAdmin', 44 | component: EditAdmin, 45 | meta: { 46 | requireLogin:true, 47 | }, 48 | },{ 49 | path: 'goods', 50 | name: 'Goods', 51 | component: Goods, 52 | meta: { 53 | requireLogin:true, 54 | }, 55 | },{ 56 | path: 'goods/:id', 57 | name: 'EditGoods', 58 | component: EditGoods, 59 | meta: { 60 | requireLogin:true, 61 | }, 62 | },{ 63 | path: 'orders', 64 | name: 'Orders', 65 | component: Orders, 66 | meta: { 67 | requireLogin:true, 68 | }, 69 | },{ 70 | path: 'orders/:id', 71 | name: 'EditOrders', 72 | component: EditOrders, 73 | meta: { 74 | requireLogin:true, 75 | }, 76 | },{ 77 | path: 'messages', 78 | name: 'Messages', 79 | component: Messages, 80 | meta: { 81 | requireLogin:true, 82 | }, 83 | } 84 | ] 85 | },{//404页面 86 | path:'*', 87 | name:'ErrorPage', 88 | component: ErrorPage 89 | } 90 | ], 91 | scrollBehavior (to, from, savedPosition) { 92 | if (savedPosition) { 93 | return savedPosition 94 | } else { 95 | return { x: 0, y: 0 } 96 | } 97 | } 98 | }); 99 | 100 | //登录拦截 101 | router.beforeEach((to,from,next) => { 102 | if(to.meta.requireLogin){ 103 | if(store.state.adminToken){ 104 | next() 105 | }else{ 106 | next({ 107 | path: '/login', 108 | query:{redirect: to.fullPath} 109 | }) 110 | } 111 | }else{ 112 | next(); 113 | } 114 | }); 115 | 116 | export default router; -------------------------------------------------------------------------------- /client/src/router/client.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | import store from '@/store'; 5 | 6 | import Mall from '@/pages/client/Mall'; 7 | import MallShow from '@/pages/client/MallShow'; 8 | import MallIndex from '@/pages/client/MallIndex'; 9 | import MallLogin from '@/pages/client/MallLogin'; 10 | import GoodsDetail from '@/pages/client/GoodsDetail'; 11 | import GoodsList from '@/pages/client/GoodsList'; 12 | import Personal from '@/pages/client/Personal'; 13 | import MyOrder from '@/pages/client/MyOrder'; 14 | import MyData from '@/pages/client/MyData'; 15 | import Cart from '@/pages/client/Cart'; 16 | import ErrorPage from '@/pages/ErrorPage'; 17 | 18 | Vue.use(Router); 19 | 20 | let router = new Router({ 21 | routes: [ 22 | { 23 | path:"/", 24 | redirect:"/mall" 25 | },{ 26 | path: '/login', 27 | name: 'MallLogin', 28 | component: MallLogin 29 | },{ 30 | path: '/mall', 31 | name: 'Mall', 32 | component: Mall, 33 | redirect:'/mall/show', 34 | children:[ 35 | { 36 | path: 'show', 37 | name: 'MallShow', 38 | component: MallShow, 39 | redirect:'/mall/show/index', 40 | children:[ 41 | { 42 | path: 'index', 43 | name: 'MallIndex', 44 | component: MallIndex 45 | },{ 46 | path: 'goodsList/:typeId/:keyword', 47 | name: 'GoodsList', 48 | component: GoodsList 49 | }, 50 | ] 51 | },{ 52 | path: 'goods/:id', 53 | name: 'GoodsDetail', 54 | component: GoodsDetail 55 | },{ 56 | path: 'personal', 57 | name: 'Personal', 58 | component: Personal, 59 | redirect:'/mall/personal/cart', 60 | children:[ 61 | { 62 | path: 'cart', 63 | name: 'Cart', 64 | component: Cart, 65 | meta: { 66 | requireLogin:true, 67 | }, 68 | },{ 69 | path: 'myData', 70 | name: 'MyData', 71 | component: MyData, 72 | meta: { 73 | requireLogin:true, 74 | }, 75 | },{ 76 | path: 'myOrder', 77 | name: 'MyOrder', 78 | component: MyOrder, 79 | meta: { 80 | requireLogin:true, 81 | }, 82 | } 83 | ] 84 | } 85 | ] 86 | },{ 87 | path:'*', 88 | name:'ErrorPage', 89 | component: ErrorPage 90 | } 91 | ], 92 | scrollBehavior (to, from, savedPosition) { 93 | if (savedPosition) { 94 | return savedPosition 95 | } else { 96 | return { x: 0, y: 0 } 97 | } 98 | } 99 | }); 100 | 101 | //登录拦截 102 | router.beforeEach((to,from,next) => { 103 | if(to.meta.requireLogin){ 104 | if(store.state.clientToken){ 105 | next() 106 | }else{ 107 | next({ 108 | path: '/login', 109 | query:{redirect: to.fullPath} 110 | }) 111 | } 112 | }else{ 113 | next(); 114 | } 115 | }); 116 | 117 | export default router; 118 | -------------------------------------------------------------------------------- /client/src/store/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './mutation-types'; 2 | 3 | /*export const setNewsCacheByType = function({commit},{type,data}){ 4 | commit(types.SET_CACHE_NEWS,type,data) 5 | }*/ -------------------------------------------------------------------------------- /client/src/store/getters.js: -------------------------------------------------------------------------------- 1 | export const adminToken = state => state.adminToken; 2 | export const clientToken = state => state.clientToken; -------------------------------------------------------------------------------- /client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vuex from 'vuex'; 2 | import Vue from 'vue'; 3 | import * as actions from './actions'; 4 | import * as getters from './getters'; 5 | import state from './state'; 6 | import mutations from './mutations'; 7 | 8 | Vue.use(Vuex); 9 | 10 | export default new Vuex.Store({ 11 | actions, 12 | getters, 13 | state, 14 | mutations, 15 | }); -------------------------------------------------------------------------------- /client/src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const SET_ADMIN_TOKEN = 'SET_ADMIN_TOKEN'; 2 | export const SET_ADMIN_NAME = 'SET_ADMIN_NAME'; 3 | export const ADMIN_LOGOUT = 'ADMIN_LOGOUT'; 4 | 5 | export const SET_CLIENT_TOKEN = 'SET_CLIENT_TOKEN'; 6 | export const SET_CLIENT_NAME = 'SET_CLIENT_NAME'; 7 | export const CLIENT_LOGOUT = 'CLIENT_LOGOUT'; 8 | 9 | -------------------------------------------------------------------------------- /client/src/store/mutations.js: -------------------------------------------------------------------------------- 1 | import * as types from './mutation-types'; 2 | import {setLocalItem} from '../util/util'; 3 | 4 | const mutations = { 5 | //管理员 6 | [types.SET_ADMIN_TOKEN]:(state, adminToken) => { 7 | state.adminToken = adminToken; 8 | setLocalItem('adminToken',adminToken); 9 | }, 10 | [types.ADMIN_LOGOUT]: (state) => { 11 | state.adminToken = null; 12 | state.adminName = ''; 13 | localStorage.removeItem('adminToken','adminName'); 14 | }, 15 | [types.SET_ADMIN_NAME]:(state, name) => { 16 | state.adminName = name; 17 | setLocalItem('adminName',name); 18 | }, 19 | 20 | //客户 21 | [types.SET_CLIENT_TOKEN]:(state, clientToken) => { 22 | state.clientToken = clientToken; 23 | setLocalItem('clientToken',clientToken); 24 | }, 25 | [types.SET_CLIENT_NAME]:(state, name) => { 26 | state.clientName = name; 27 | setLocalItem('clientName',name); 28 | }, 29 | [types.CLIENT_LOGOUT]: (state) => { 30 | state.clientToken = null; 31 | state.clientName = ''; 32 | localStorage.removeItem('clientToken','clientName'); 33 | }, 34 | } 35 | 36 | export default mutations; -------------------------------------------------------------------------------- /client/src/store/state.js: -------------------------------------------------------------------------------- 1 | import {getLocalItem} from '../util/util'; 2 | const state = { 3 | //后台管理 4 | adminToken:getLocalItem('adminToken')?getLocalItem('adminToken'):null, 5 | adminName:getLocalItem('adminName')?getLocalItem('adminName'):'', 6 | 7 | //客户商城 8 | clientName:getLocalItem('clientName')?getLocalItem('clientName'):'', 9 | clientToken:getLocalItem('clientToken')?getLocalItem('clientToken'):null, 10 | } 11 | 12 | export default state; -------------------------------------------------------------------------------- /client/src/util/util.js: -------------------------------------------------------------------------------- 1 | //获取屏幕宽高 2 | export function getClientSize(){ 3 | let h = document.documentElement.clientHeight || document.body.clientHeight; 4 | let w = document.documentElement.clientWidth || document.body.clientWidth; 5 | return { 6 | width:w, 7 | height:h 8 | } 9 | } 10 | 11 | //获取滚动条宽度 12 | export function getScrollWidth() { 13 | let noScroll, scroll, oDiv = document.createElement("DIV"); 14 | oDiv.style.cssText = "position:absolute; top:-1000px; width:100px; height:100px; overflow:hidden;"; 15 | noScroll = document.body.appendChild(oDiv).clientWidth; 16 | oDiv.style.overflowY = "scroll"; 17 | scroll = oDiv.clientWidth; 18 | document.body.removeChild(oDiv); 19 | return noScroll-scroll; 20 | } 21 | 22 | //回到顶部 23 | export function backToTop(){ 24 | let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; 25 | if(scrollTop>0){ 26 | window.requestAnimationFrame(backToTop); 27 | window.scrollTo(0,scrollTop-(scrollTop/5)); 28 | } 29 | } 30 | 31 | //取本地储存数据 32 | export function getLocalItem(key){ 33 | let value; 34 | try { 35 | value = localStorage.getItem(key); 36 | } catch (ex) { 37 | // 开发环境下提示error 38 | if (__DEV__) { 39 | console.error('localStorage.getItem报错, ', ex.message); 40 | } 41 | } finally { 42 | return value; 43 | } 44 | } 45 | 46 | //设置本地储存数据 47 | export function setLocalItem(key, value){ 48 | try { 49 | // ios safari 无痕模式下,直接使用 localStorage.setItem 会报错 50 | localStorage.setItem(key, value); 51 | } catch (ex) { 52 | // 开发环境下提示 error 53 | if (__DEV__) { 54 | console.error('localStorage.setItem报错, ', ex.message); 55 | } 56 | } 57 | } 58 | 59 | //取会话储存数据 60 | export function getSessionItem(key){ 61 | let value; 62 | try { 63 | value = sessionStorage.getItem(key); 64 | } catch (ex) { 65 | // 开发环境下提示error 66 | if (__DEV__) { 67 | console.error('sessionStorage.getItem报错, ', ex.message); 68 | } 69 | } finally { 70 | return value; 71 | } 72 | } 73 | 74 | //设置会话储存数据 75 | export function setSessionItem(key, value){ 76 | try { 77 | // ios safari 无痕模式下,直接使用 sessionStorage.setItem 会报错 78 | sessionStorage.setItem(key, value); 79 | } catch (ex) { 80 | // 开发环境下提示 error 81 | if (__DEV__) { 82 | console.error('sessionStorage.setItem报错, ', ex.message); 83 | } 84 | } 85 | } 86 | 87 | //Unicode转中文汉字 88 | export function decode(str){ 89 | str = str.replace(/(\\u)(\w{1,4})/gi,function($0){ 90 | return (String.fromCharCode(parseInt((escape($0).replace(/(%5Cu)(\w{1,4})/g,"$2")),16))); 91 | }); 92 | str = str.replace(/(&#x)(\w{1,4});/gi,function($0){ 93 | return String.fromCharCode(parseInt(escape($0).replace(/(%26%23x)(\w{1,4})(%3B)/g,"$2"),16)); 94 | }); 95 | str = str.replace(/(&#)(\d{1,6});/gi,function($0){ 96 | return String.fromCharCode(parseInt(escape($0).replace(/(%26%23)(\d{1,6})(%3B)/g,"$2"))); 97 | }); 98 | 99 | return str; 100 | } 101 | 102 | //转化为00:00时间格式 103 | export function convertTime(seconds){ 104 | return [ 105 | parseInt(seconds / 60 % 60), 106 | parseInt(seconds % 60) 107 | ].join(":").replace(/\b(\d)\b/g, "0$1"); 108 | } 109 | 110 | export function shuffle(arr){ 111 | for(let i=arr.length-1;i>=0;i--){ 112 | let randomIndex = Math.floor(Math.random()*(i+1)); 113 | let itemAtIndex = arr[randomIndex]; 114 | arr[randomIndex] = arr[i]; 115 | arr[i] = itemAtIndex; 116 | } 117 | return arr; 118 | } -------------------------------------------------------------------------------- /client/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/client/static/.gitkeep -------------------------------------------------------------------------------- /screen/cart.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/screen/cart.gif -------------------------------------------------------------------------------- /screen/data.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/screen/data.gif -------------------------------------------------------------------------------- /screen/goodsDetail.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/screen/goodsDetail.gif -------------------------------------------------------------------------------- /screen/index.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/screen/index.gif -------------------------------------------------------------------------------- /screen/login.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/screen/login.gif -------------------------------------------------------------------------------- /screen/manage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/screen/manage.gif -------------------------------------------------------------------------------- /screen/orders.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/screen/orders.gif -------------------------------------------------------------------------------- /screen/search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/screen/search.gif -------------------------------------------------------------------------------- /screen/type.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmh1996/vue-mall/0d758796a50f3d50c2537b031ddde3af0f68cae8/screen/type.gif -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa'); 2 | const app = new Koa(); 3 | const json = require('koa-json'); 4 | const onerror = require('koa-onerror'); 5 | const bodyparser = require('koa-bodyparser'); 6 | const logger = require('koa-logger'); 7 | const jwt = require('koa-jwt'); 8 | const cors = require('koa2-cors'); 9 | 10 | const mall = require('./routes/mall'); 11 | const user = require('./routes/user'); 12 | const admin = require('./routes/admin'); 13 | 14 | // error handler 15 | onerror(app); 16 | 17 | // middlewares 18 | app.use(cors({ 19 | origin: '*', 20 | exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'], 21 | maxAge: 5, 22 | credentials: true, 23 | allowMethods: ['GET', 'POST', 'DELETE'], 24 | allowHeaders: ['Content-Type', 'Authorization', 'Accept'], 25 | })); 26 | app.use(bodyparser({ 27 | enableTypes:['json', 'form', 'text'] 28 | })); 29 | app.use(json()); 30 | app.use(logger()); 31 | app.use(require('koa-static')(__dirname + '/public')); 32 | 33 | // logger 34 | app.use(async (ctx, next) => { 35 | const start = new Date() 36 | await next() 37 | const ms = new Date() - start 38 | console.log(`${ctx.method} ${ctx.url} - ${ms}ms`) 39 | }); 40 | 41 | 42 | app.use(jwt({ 43 | secret:'chambers' 44 | }).unless({path:[/^\/api/]})); 45 | 46 | // routes 47 | app.use(mall.routes(), mall.allowedMethods()); 48 | app.use(user.routes(), user.allowedMethods()); 49 | app.use(admin.routes(), admin.allowedMethods()); 50 | 51 | // error-handling 52 | app.on('error', (err, ctx) => { 53 | console.error('server error', err, ctx) 54 | }); 55 | 56 | module.exports = app; 57 | -------------------------------------------------------------------------------- /server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('demo:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | // app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app.callback()); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /server/config/sequelizeBase.js: -------------------------------------------------------------------------------- 1 | //sequelize基础配置文件 2 | const Sequelize = require('sequelize'); 3 | const sequelize = new Sequelize('mall','root','',{ 4 | host:'127.0.0.1', 5 | dialect:'mysql', 6 | }) 7 | 8 | module.exports = sequelize; -------------------------------------------------------------------------------- /server/controllers/goodsAdmin.js: -------------------------------------------------------------------------------- 1 | const AdminModel = require('../models/AdminModel.js'); 2 | const GoodsDetailModel = require('../models/GoodsDetailModel.js'); 3 | const GoodsModel = require('../models/GoodsModel.js'); 4 | const TypeModel = require('../models/TypeModel.js'); 5 | const jwt = require('jsonwebtoken'); 6 | const moment = require('moment'); 7 | 8 | //得到类目 9 | exports.getType = async (ctx)=>{ 10 | try{ 11 | const types = await TypeModel.findAll({ 12 | attributes:['id','name'] 13 | }); 14 | ctx.body = { 15 | code:0, 16 | data:types 17 | } 18 | } 19 | catch(e){ 20 | ctx.body={ 21 | code:10000, 22 | message:'网络出错' 23 | } 24 | } 25 | } 26 | 27 | //得到商品 28 | exports.getGoodsByType = async (ctx)=>{ 29 | const typeId = ctx.query.typeId; 30 | try{ 31 | const goods = await GoodsModel.findAll({ 32 | attributes:['id','img','name'], 33 | order: [ 34 | ['createtime','DESC'] 35 | ], 36 | where:{ 37 | typeId:typeId 38 | } 39 | }); 40 | ctx.body = { 41 | code:0, 42 | data:goods 43 | } 44 | } 45 | catch(e){ 46 | ctx.body={ 47 | code:10000, 48 | message:'网络出错' 49 | } 50 | } 51 | } 52 | 53 | //增加类目 54 | exports.addType = async (ctx)=>{ 55 | const name = ctx.request.body.name; 56 | try{ 57 | const res = TypeModel.create({ 58 | name:name 59 | }) 60 | ctx.body = { 61 | code:0 62 | } 63 | } 64 | catch(e){ 65 | ctx.body={ 66 | code:10000, 67 | message:'网络出错' 68 | } 69 | } 70 | } 71 | 72 | //得到商品信息 73 | exports.getGoodsInfo = async (ctx)=>{ 74 | const id = ctx.query.id; 75 | try{ 76 | const goods = await GoodsModel.findOne({ 77 | attributes:['id','typeId','img','name','desc'], 78 | where:{ 79 | id:id 80 | } 81 | }); 82 | const specs = await GoodsDetailModel.findAll({ 83 | where:{ 84 | goodsId:id 85 | } 86 | }) 87 | ctx.body = { 88 | code:0, 89 | data:{ 90 | goods:goods, 91 | specs:specs 92 | } 93 | } 94 | } 95 | catch(e){ 96 | ctx.body={ 97 | code:10000, 98 | message:'网络出错' 99 | } 100 | } 101 | } 102 | 103 | //增加商品 104 | exports.addGoods = async (ctx)=>{ 105 | const goodsObj = ctx.request.body; 106 | try{ 107 | const goods = await GoodsModel.create({ 108 | name:goodsObj.name, 109 | typeId:goodsObj.typeId, 110 | img:goodsObj.img, 111 | desc:goodsObj.desc, 112 | updatetime:new Date(), 113 | createtime:new Date(), 114 | }); 115 | for(let item of goodsObj.specList){ 116 | const spec = await GoodsDetailModel.create({ 117 | goodsId:goods.id, 118 | specName:item.specName, 119 | stockNum:item.stockNum, 120 | unitPrice:item.unitPrice, 121 | updatetime:new Date(), 122 | createtime:new Date() 123 | }) 124 | }; 125 | 126 | ctx.body = { 127 | code:0 128 | } 129 | } 130 | catch(e){ 131 | ctx.body={ 132 | code:10000, 133 | message:'网络出错' 134 | } 135 | } 136 | } 137 | 138 | //增加规格 139 | exports.addSpec = async (ctx)=>{ 140 | const specObj = ctx.request.body; 141 | try{ 142 | const spec = await GoodsDetailModel.create({ 143 | goodsId:specObj.goodsId, 144 | specName:specObj.specName, 145 | stockNum:specObj.stockNum, 146 | unitPrice:specObj.unitPrice, 147 | updatetime:new Date(), 148 | createtime:new Date(), 149 | }); 150 | 151 | ctx.body = { 152 | code:0, 153 | data:spec 154 | } 155 | } 156 | catch(e){ 157 | ctx.body={ 158 | code:10000, 159 | message:'网络出错' 160 | } 161 | } 162 | } 163 | 164 | //删除规格 165 | exports.deleteSpec = async (ctx)=>{ 166 | const specObj = ctx.request.body; 167 | try{ 168 | const res = await GoodsDetailModel.destroy({ 169 | where:{ 170 | goodsId:specObj.goodsId, 171 | specName:specObj.specName 172 | } 173 | }); 174 | 175 | ctx.body = { 176 | code:0 177 | } 178 | } 179 | catch(e){ 180 | ctx.body={ 181 | code:10000, 182 | message:'网络出错' 183 | } 184 | } 185 | } 186 | 187 | //更新商品信息 188 | exports.updateGoods = async (ctx)=>{ 189 | const data = ctx.request.body; 190 | try{ 191 | const res = await GoodsModel.update( 192 | { 193 | name:data.name, 194 | typeId:data.typeId, 195 | img:data.img, 196 | desc:data.desc 197 | }, 198 | { 199 | where: { 200 | id:data.id 201 | } 202 | } 203 | ); 204 | for(let item of data.specList){ 205 | const res2 = await GoodsDetailModel.update( 206 | { 207 | specName:item.specName, 208 | stockNum:item.stockNum, 209 | unitPrice:item.unitPrice, 210 | }, 211 | { 212 | where: { 213 | id:item.id 214 | } 215 | } 216 | ); 217 | } 218 | ctx.body = { 219 | code:0 220 | } 221 | } 222 | catch(e){ 223 | ctx.body={ 224 | code:10000, 225 | message:'网络出错' 226 | } 227 | } 228 | } 229 | 230 | //删除商品 231 | exports.deleteGoods = async (ctx)=>{ 232 | const id = ctx.query.id; 233 | try{ 234 | const res = await GoodsModel.destroy({ 235 | where:{ 236 | id:id 237 | } 238 | }); 239 | const res2 = await GoodsDetailModel.destroy({ 240 | where:{ 241 | goodsId:id 242 | } 243 | }) 244 | ctx.body = { 245 | code:0 246 | } 247 | } 248 | catch(e){ 249 | ctx.body={ 250 | code:10000, 251 | message:'网络出错' 252 | } 253 | } 254 | } 255 | 256 | -------------------------------------------------------------------------------- /server/controllers/msgAdmin.js: -------------------------------------------------------------------------------- 1 | const AdminModel = require('../models/AdminModel.js'); 2 | const UserModel = require('../models/UserModel.js'); 3 | const MessageModel = require('../models/MessageModel.js'); 4 | const GoodsModel = require('../models/GoodsModel.js'); 5 | const ReplyModel = require('../models/ReplyModel.js'); 6 | const jwt = require('jsonwebtoken'); 7 | const moment = require('moment'); 8 | 9 | //获得未回复的留言 10 | exports.getNoReplyMsg = async (ctx)=>{ 11 | try{ 12 | const noReplyMsgs = await MessageModel.findAll({ 13 | attributes:['id','userId','goodsId','content','createtime'], 14 | order: [ 15 | ['createtime','DESC'] 16 | ], 17 | where:{ 18 | state:0 19 | } 20 | }); 21 | if(noReplyMsgs.length===0){ 22 | ctx.body={ 23 | code:10000, 24 | data:[] 25 | } 26 | return; 27 | }else{ 28 | let messageList = []; 29 | for(let msg of noReplyMsgs){ 30 | let user = await UserModel.findOne({ 31 | attributes:['headimg','nickname'], 32 | where:{ 33 | id:msg.dataValues.userId 34 | } 35 | }); 36 | //用户已被删除 37 | if(!user){ 38 | user = {}; 39 | user.nickname = '该用户已注销'; 40 | user.headimg = '' 41 | } 42 | let goods = await GoodsModel.findOne({ 43 | attributes:['id','name'], 44 | where:{ 45 | id:msg.dataValues.goodsId 46 | } 47 | }); 48 | //商品已被删除 49 | if(!goods){ 50 | goods = {}; 51 | goods.id = 0; 52 | goods.name = '该商品已下架'; 53 | } 54 | messageList.push({ 55 | user:{ 56 | name:user.nickname, 57 | headimg:user.headimg 58 | }, 59 | id:msg.dataValues.id, 60 | content:msg.dataValues.content, 61 | time:moment(msg.dataValues.createtime).add('hours',8).format('MM-DD HH:mm'), 62 | goods:{ 63 | id:goods.id, 64 | name:goods.name 65 | } 66 | }) 67 | }; 68 | ctx.body = { 69 | code:0, 70 | data:messageList 71 | } 72 | } 73 | } 74 | catch(e){ 75 | console.log('eeeeeeeeee',e) 76 | ctx.body={ 77 | code:10000, 78 | message:'网络出错' 79 | } 80 | } 81 | } 82 | 83 | //获得已回复的留言 84 | exports.getRepliedMsg = async (ctx)=>{ 85 | try{ 86 | const repliedMsg = await MessageModel.findAll({ 87 | attributes:['id','userId','goodsId','content','createtime'], 88 | order: [ 89 | ['createtime','DESC'] 90 | ], 91 | where:{ 92 | state:1 93 | } 94 | }); 95 | if(repliedMsg.length===0){ 96 | ctx.body={ 97 | code:10000, 98 | data:[] 99 | } 100 | return; 101 | }else{ 102 | let messageList = []; 103 | for(let msg of repliedMsg){ 104 | let user = await UserModel.findOne({ 105 | attributes:['headimg','nickname'], 106 | where:{ 107 | id:msg.dataValues.userId 108 | } 109 | }); 110 | //用户已被删除 111 | if(!user){ 112 | user = {}; 113 | user.nickname = '该用户已注销'; 114 | user.headimg = '' 115 | } 116 | let goods = await GoodsModel.findOne({ 117 | attributes:['id','name'], 118 | where:{ 119 | id:msg.dataValues.goodsId 120 | } 121 | }); 122 | //商品已被删除 123 | if(!goods){ 124 | goods = {}; 125 | goods.id = 0; 126 | goods.name = '该商品已下架'; 127 | } 128 | const reply = await ReplyModel.findOne({ 129 | attributes:['content'], 130 | where:{ 131 | messageId:msg.dataValues.id 132 | } 133 | }); 134 | messageList.push({ 135 | user:{ 136 | name:user.nickname, 137 | headimg:user.headimg 138 | }, 139 | id:msg.dataValues.id, 140 | content:msg.dataValues.content, 141 | replyContent:reply.content, 142 | time:moment(msg.dataValues.createtime).add('hours',8).format('MM-DD HH:mm'), 143 | goods:{ 144 | id:goods.id, 145 | name:goods.name 146 | } 147 | }) 148 | }; 149 | ctx.body = { 150 | code:0, 151 | data:messageList 152 | } 153 | } 154 | } 155 | catch(e){ 156 | console.log('eeeeeeeeee',e) 157 | ctx.body={ 158 | code:10000, 159 | message:'网络出错' 160 | } 161 | } 162 | } 163 | 164 | //回复 165 | exports.reply = async (ctx)=>{ 166 | const replyObj = ctx.request.body; 167 | try{ 168 | const res = await ReplyModel.create({ 169 | messageId:replyObj.id, 170 | content:replyObj.content, 171 | createtime:new Date() 172 | }); 173 | const res2 = await MessageModel.update( 174 | { 175 | state:1 176 | }, 177 | { 178 | where: { 179 | id:replyObj.id 180 | } 181 | } 182 | ); 183 | ctx.body = { 184 | code:0 185 | } 186 | } 187 | catch(e){ 188 | ctx.body={ 189 | code:10000, 190 | message:'网络出错' 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /server/controllers/orderAdmin.js: -------------------------------------------------------------------------------- 1 | const AdminModel = require('../models/AdminModel.js'); 2 | const UserModel = require('../models/UserModel.js'); 3 | const MessageModel = require('../models/MessageModel.js'); 4 | const GoodsModel = require('../models/GoodsModel.js'); 5 | const GoodsDetailModel = require('../models/GoodsDetailModel.js'); 6 | const OrderModel = require('../models/OrderModel.js'); 7 | const moment = require('moment'); 8 | 9 | //获得订单s 10 | exports.getOrders = async (ctx)=>{ 11 | /*-1全部,0未付款,1已付款未发货,2已发货未确认收到,3确认到货订单完成*/ 12 | const state = Number(ctx.query.state); 13 | try{ 14 | let orders = []; 15 | if(state===-1){ 16 | orders = await OrderModel.findAll({ 17 | attributes:['id','userId','goodsDetailId','goodsNum','amount','state','createtime'], 18 | order: [ 19 | ['updatetime','DESC'] 20 | ] 21 | }) 22 | }else{ 23 | orders = await OrderModel.findAll({ 24 | attributes:['id','userId','goodsDetailId','goodsNum','amount','state','updatetime'], 25 | order: [ 26 | ['updatetime','DESC'] 27 | ], 28 | where:{ 29 | state:state 30 | } 31 | }) 32 | }; 33 | if(orders.length===0){ 34 | ctx.body={ 35 | code:0, 36 | data:[] 37 | } 38 | return; 39 | } 40 | let orderList = []; 41 | for(let order of orders){ 42 | let user = await UserModel.findOne({ 43 | attributes:['nickname','recipient','address','phone'], 44 | where:{ 45 | id:order.dataValues.userId 46 | } 47 | }); 48 | if(!user){ 49 | user = { 50 | nickname:'已注销账户', 51 | recipient:'已注销账户', 52 | address:'已注销账户', 53 | phone:'已注销账户', 54 | }; 55 | } 56 | let spec = await GoodsDetailModel.findOne({ 57 | attributes:['goodsId','specName'], 58 | where:{ 59 | id:order.dataValues.goodsDetailId 60 | } 61 | }); 62 | if(!spec){ 63 | spec = { 64 | goodsId:0, 65 | specName:'已下架', 66 | }; 67 | } 68 | let goods = await GoodsModel.findOne({ 69 | attributes:['name'], 70 | where:{ 71 | id:spec.goodsId 72 | } 73 | }); 74 | if(!goods){ 75 | goods = { 76 | name:'已下架', 77 | }; 78 | } 79 | orderList.push({ 80 | id:order.dataValues.id, 81 | user:{ 82 | nickname:user.nickname, 83 | name:user.recipient, 84 | address:user.address, 85 | phone:user.phone 86 | }, 87 | goods:goods.name, 88 | spec:spec.specName, 89 | num:order.dataValues.goodsNum, 90 | amount:order.dataValues.amount, 91 | state:order.dataValues.state===0?'未付款':order.dataValues.state===1?'未发货':order.dataValues.state===2?'已发货':'已到货', 92 | time:moment(order.dataValues.updatetime).format('MM-DD HH:mm'), 93 | }) 94 | } 95 | ctx.body = { 96 | code:0, 97 | data:orderList 98 | } 99 | } 100 | catch(e){ 101 | ctx.body={ 102 | code:10000, 103 | message:'网络出错' 104 | } 105 | } 106 | } 107 | 108 | 109 | //获得订单 110 | exports.getOrder = async (ctx)=>{ 111 | const id = Number(ctx.query.id); 112 | try{ 113 | let order = await OrderModel.findOne({ 114 | attributes:['id','goodsDetailId','goodsNum','amount','state'], 115 | where:{ 116 | id:id 117 | } 118 | }) 119 | if(!order){ 120 | ctx.body={ 121 | code:0, 122 | data:{} 123 | } 124 | return; 125 | } 126 | const spec = await GoodsDetailModel.findOne({ 127 | attributes:['id','goodsId','specName'], 128 | where:{ 129 | id:order.goodsDetailId 130 | } 131 | }); 132 | const specs = await GoodsDetailModel.findAll({ 133 | attributes:['id','specName','unitPrice'], 134 | where:{ 135 | goodsId:spec.goodsId 136 | } 137 | }); 138 | const goods = await GoodsModel.findOne({ 139 | attributes:['name'], 140 | where:{ 141 | id:spec.goodsId 142 | } 143 | }); 144 | ctx.body = { 145 | code:0, 146 | data:{ 147 | id:order.id, 148 | goods:goods.name, 149 | amount:order.amount, 150 | num:order.goodsNum, 151 | spec:specs, 152 | states:[ 153 | {id:0,name:'未付款'}, 154 | {id:1,name:'未发货'}, 155 | {id:2,name:'已发货'}, 156 | {id:3,name:'已到货'}, 157 | ], 158 | curSpec:{ 159 | id:spec.id, 160 | name:spec.specName 161 | }, 162 | curState:{ 163 | id:order.state, 164 | name:order.state===0?'未付款':order.state===1?'未发货':order.state===2?'已发货':'已到货', 165 | } 166 | } 167 | } 168 | } 169 | catch(e){ 170 | ctx.body={ 171 | code:10000, 172 | message:'网络出错' 173 | } 174 | } 175 | } 176 | 177 | //修改订单 178 | exports.changeOrder = async (ctx)=>{ 179 | const orderObj = ctx.request.body; 180 | try{ 181 | const order = await OrderModel.findOne({ 182 | attributes:['goodsNum'], 183 | where:{ 184 | id:orderObj.id 185 | } 186 | }); 187 | const difNum = orderObj.num - order.goodsNum; 188 | const spec = await GoodsDetailModel.findOne({ 189 | attributes:['unitPrice','stockNum'], 190 | where:{ 191 | id:orderObj.spec, 192 | } 193 | }); 194 | await GoodsDetailModel.update( 195 | { 196 | stockNum:spec.stockNum - difNum 197 | }, 198 | { 199 | where: { 200 | id:orderObj.spec 201 | } 202 | } 203 | ) 204 | const res = await OrderModel.update( 205 | { 206 | goodsNum:orderObj.num, 207 | goodsDetailId:orderObj.spec, 208 | state:orderObj.state, 209 | amount:spec.unitPrice*orderObj.num 210 | }, 211 | { 212 | where: { 213 | id:orderObj.id 214 | } 215 | } 216 | ); 217 | ctx.body={ 218 | code:0 219 | } 220 | } 221 | catch(e){ 222 | ctx.body={ 223 | code:10000, 224 | message:'网络出错' 225 | } 226 | } 227 | } 228 | 229 | //删除订单 230 | exports.deleteOrder = async (ctx)=>{ 231 | const id = ctx.query.id; 232 | try{ 233 | const order = await OrderModel.findOne({ 234 | attributes:['state','goodsNum'], 235 | where:{ 236 | id:id 237 | } 238 | }); 239 | //还没结束的订单,那就库存增加 240 | if(order.state!==3){ 241 | const goodsDetail = await GoodsDetailModel.findOne({ 242 | attributes:['stockNum'], 243 | where:{ 244 | id:id 245 | } 246 | }); 247 | await GoodsDetailModel.update( 248 | { 249 | stockNum:goodsDetail.stockNum+order.goodsNum 250 | }, 251 | { 252 | where: { 253 | id:id 254 | } 255 | } 256 | ); 257 | }; 258 | 259 | const res = await OrderModel.destroy({ 260 | where:{ 261 | id:id 262 | } 263 | }); 264 | ctx.body = { 265 | code:0 266 | } 267 | } 268 | catch(e){ 269 | ctx.body = { 270 | code:10000, 271 | message:'网络出错' 272 | } 273 | } 274 | } 275 | 276 | 277 | -------------------------------------------------------------------------------- /server/controllers/user.js: -------------------------------------------------------------------------------- 1 | const UserModel = require('../models/UserModel.js'); 2 | const jwt = require('jsonwebtoken'); 3 | const bcrypt = require('bcryptjs'); 4 | const salt = bcrypt.genSaltSync(10); 5 | const moment = require('moment'); 6 | 7 | //注册 8 | exports.signup = async (ctx)=>{ 9 | const hashPwd = bcrypt.hashSync(ctx.request.body.pwd, salt); 10 | const user = { 11 | email:ctx.request.body.email, 12 | pwd:hashPwd, 13 | nickname:ctx.request.body.nickname, 14 | recipient:ctx.request.body.recipient, 15 | address:ctx.request.body.address, 16 | phone:ctx.request.body.phone, 17 | createtime:new Date(), 18 | updatetime:new Date() 19 | }; 20 | 21 | //验证邮箱唯一性 22 | const emailUniq = await UserModel.findOne({ 23 | where: { 24 | email: ctx.request.body.email 25 | } 26 | }) 27 | //如果已经存在 28 | if(emailUniq){ 29 | ctx.body = { 30 | code:10000, 31 | message:'该邮箱已被注册' 32 | }; 33 | return; 34 | } 35 | 36 | //验证昵称唯一性 37 | const nicknameUniq = await UserModel.findOne({ 38 | where: { 39 | nickname: ctx.request.body.nickname 40 | } 41 | }) 42 | //如果已经存在 43 | if(nicknameUniq){ 44 | ctx.body = { 45 | code:10000, 46 | message:'该昵称已被注册' 47 | } 48 | return; 49 | } 50 | 51 | //插入数据 52 | const res = await UserModel.create(user); 53 | const token = jwt.sign(res.id,'chambers'); 54 | ctx.body = { 55 | code:0, 56 | data:{ 57 | name:res.nickname, 58 | token:token 59 | } 60 | } 61 | } 62 | 63 | //登录 64 | exports.login = async (ctx)=>{ 65 | const user = ctx.request.body; 66 | //看该邮箱是否已经注册 67 | const emailSigned = await UserModel.findOne({ 68 | where: { 69 | email: user.account 70 | } 71 | }) 72 | 73 | //如果不存在 74 | if(!emailSigned){ 75 | ctx.body = { 76 | code:10000, 77 | message:'该邮箱还没注册,请前往注册' 78 | }; 79 | return; 80 | } 81 | //已经存在 82 | else{ 83 | //密码不对 84 | if(!bcrypt.compareSync(user.pwd, emailSigned.pwd)){ 85 | ctx.body = { 86 | code:10000, 87 | message:'密码不正确' 88 | }; 89 | return; 90 | } 91 | //密码正确 92 | else{ 93 | const token = jwt.sign(emailSigned.id,'chambers'); 94 | ctx.body = { 95 | code:0, 96 | data:{ 97 | name:emailSigned.nickname, 98 | token:token 99 | }, 100 | } 101 | } 102 | } 103 | } 104 | 105 | //获取user基本资料 106 | exports.getData = async (ctx)=>{ 107 | const id = jwt.verify(ctx.query.token,'chambers'); 108 | try{ 109 | const user = await UserModel.findOne({ 110 | attributes:['id','email','nickname','recipient','address','phone','headimg'], 111 | where: { 112 | id: id 113 | } 114 | }) 115 | if(!user){ 116 | ctx.body = { 117 | code:10000, 118 | message:'该用户不存在' 119 | }; 120 | return; 121 | } 122 | ctx.body = { 123 | code:0, 124 | data:{ 125 | id:user.id, 126 | headimg:user.headimg, 127 | email:user.email, 128 | nickname:user.nickname, 129 | recipient:user.recipient, 130 | address:user.address, 131 | phone:user.phone, 132 | } 133 | } 134 | }catch(e){ 135 | ctx.body = { 136 | code:10000, 137 | message:'网络错误' 138 | } 139 | } 140 | } 141 | 142 | //更改用户资料 143 | exports.updateUserData = async (ctx)=>{ 144 | const data = ctx.request.body; 145 | 146 | try{ 147 | const res = await UserModel.update( 148 | { 149 | recipient:data.recipient, 150 | address:data.address, 151 | phone:data.phone, 152 | nickname:data.nickname 153 | }, 154 | { 155 | where: { 156 | id:data.id 157 | } 158 | } 159 | ) 160 | //正常修改 161 | ctx.body = { 162 | code:0, 163 | nickname:data.nickname 164 | } 165 | }catch(e){ 166 | //发生错误 167 | ctx.body = { 168 | code:10000, 169 | message:'网络出错' 170 | } 171 | } 172 | } 173 | 174 | 175 | //修改密码 176 | exports.updatePwd = async (ctx)=>{ 177 | const data = ctx.request.body; 178 | 179 | const account = await UserModel.findOne({ 180 | where: { 181 | id: data.id 182 | } 183 | }) 184 | 185 | if(!bcrypt.compareSync(data.oldPwd, account.pwd)){ 186 | ctx.body = { 187 | code:10000, 188 | message:'密码不正确' 189 | }; 190 | return; 191 | } 192 | //密码正确 193 | else{ 194 | try{ 195 | const hashPwd = bcrypt.hashSync(data.newPwd, salt); 196 | const res = await UserModel.update( 197 | { 198 | pwd:hashPwd 199 | }, 200 | { 201 | where: { 202 | id:data.id 203 | } 204 | } 205 | ) 206 | //正常修改 207 | ctx.body = { 208 | code:0 209 | } 210 | }catch(e){ 211 | //发生错误 212 | ctx.body = { 213 | code:10000, 214 | message:'修改密码出错' 215 | } 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /server/controllers/userAdmin.js: -------------------------------------------------------------------------------- 1 | const AdminModel = require('../models/AdminModel.js'); 2 | const UserModel = require('../models/UserModel.js'); 3 | const jwt = require('jsonwebtoken'); 4 | const moment = require('moment'); 5 | 6 | //管理员登录 7 | exports.login = async (ctx)=>{ 8 | const admin = ctx.request.body; 9 | try{ 10 | //看该账号是否已经注册 11 | const accountSigned = await AdminModel.findOne({ 12 | where: { 13 | account: admin.account 14 | } 15 | }) 16 | 17 | //如果不存在 18 | if(!accountSigned){ 19 | ctx.body = { 20 | code:10000, 21 | message:'该账号还没注册,请联系管理员注册' 22 | }; 23 | return; 24 | } 25 | //已经存在 26 | else{ 27 | //密码不对 28 | if(accountSigned.pwd!==admin.pwd){ 29 | ctx.body = { 30 | code:10000, 31 | message:'密码不正确' 32 | }; 33 | return; 34 | } 35 | //密码正确 36 | else{ 37 | const token = jwt.sign(accountSigned.id,'chambers'); 38 | ctx.body = { 39 | code:0, 40 | data:{ 41 | token:token, 42 | name:accountSigned.name 43 | } 44 | } 45 | } 46 | } 47 | } 48 | catch(e){ 49 | ctx.body = { 50 | code:10000, 51 | message:'网络出错' 52 | } 53 | } 54 | } 55 | 56 | //管理员修改密码 57 | exports.changePwd = async (ctx)=>{ 58 | const pwdObj = ctx.request.body; 59 | pwdObj.adminToken = jwt.decode(pwdObj.adminToken); 60 | try{ 61 | const adminOldPwd = await AdminModel.findOne({ 62 | attributes:['pwd'], 63 | where:{ 64 | id:pwdObj.adminToken 65 | } 66 | }); 67 | if(adminOldPwd.pwd!==pwdObj.oldPwd){ 68 | ctx.body = { 69 | code:10000, 70 | message:'旧密码错误' 71 | } 72 | return; 73 | } 74 | const res = await AdminModel.update( 75 | { 76 | pwd:pwdObj.newPwd 77 | }, 78 | { 79 | where: { 80 | id:pwdObj.adminToken 81 | } 82 | } 83 | ) 84 | ctx.body = { 85 | code:0 86 | } 87 | } 88 | catch(e){ 89 | ctx.body = { 90 | code:10000, 91 | message:'网络出错' 92 | } 93 | } 94 | } 95 | 96 | 97 | //查询所有用户 98 | exports.getAllUser = async (ctx)=>{ 99 | try{ 100 | const users = await UserModel.findAll({ 101 | attributes:['id','email','nickname','sex','recipient','address','phone'] 102 | }); 103 | ctx.body = { 104 | code:0, 105 | data:users 106 | } 107 | } 108 | catch(e){ 109 | ctx.body = { 110 | code:10000, 111 | message:'网络出错' 112 | } 113 | } 114 | } 115 | 116 | //删除用户 117 | exports.deleteUser = async (ctx)=>{ 118 | const id = ctx.query.id; 119 | try{ 120 | const res = await UserModel.destroy({ 121 | where:{ 122 | id:id 123 | } 124 | }); 125 | ctx.body = { 126 | code:0 127 | } 128 | } 129 | catch(e){ 130 | ctx.body = { 131 | code:10000, 132 | message:'网络出错' 133 | } 134 | } 135 | } 136 | 137 | //查询指定用户 138 | exports.searchUser = async (ctx)=>{ 139 | const word = ctx.query.word; 140 | try{ 141 | const users = await UserModel.findAll({ 142 | attributes:['id','email','nickname','sex','recipient','address','phone'], 143 | where:{ 144 | '$or':[ 145 | {email:word}, 146 | {phone:word}, 147 | {nickname:{'$like':'%'+word+'%'}}, 148 | {recipient:{'$like':'%'+word+'%'}}, 149 | {address:{'$like':'%'+word+'%'}}, 150 | ] 151 | } 152 | }); 153 | ctx.body = { 154 | code:0, 155 | data:users 156 | } 157 | } 158 | catch(e){ 159 | ctx.body = { 160 | code:10000, 161 | message:'网络出错' 162 | } 163 | } 164 | } 165 | 166 | -------------------------------------------------------------------------------- /server/models/AdminModel.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const sequelize = require('../config/sequelizeBase'); 3 | 4 | const AdminModel = sequelize.define('admin',{ 5 | id:{ 6 | type:Sequelize.BIGINT, 7 | primaryKey:true, 8 | allowNull:false, 9 | autoIncrement:true 10 | }, 11 | account:{ 12 | type:Sequelize.STRING(255), 13 | unique:true, 14 | allowNull:false 15 | }, 16 | name:{ 17 | type:Sequelize.STRING(64), 18 | allowNull:false 19 | }, 20 | pwd:{ 21 | type:Sequelize.STRING(255), 22 | allowNull:false 23 | }, 24 | },{ 25 | timestamps:false, 26 | }); 27 | 28 | module.exports = AdminModel; -------------------------------------------------------------------------------- /server/models/CommentModel.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const sequelize = require('../config/sequelizeBase'); 3 | 4 | const CommentModel = sequelize.define('comment',{ 5 | id:{ 6 | type:Sequelize.BIGINT, 7 | primaryKey:true, 8 | allowNull:false, 9 | autoIncrement:true 10 | }, 11 | userId:{ 12 | type:Sequelize.BIGINT, 13 | allowNull:false 14 | }, 15 | goodsId:{ 16 | type:Sequelize.BIGINT, 17 | allowNull:false 18 | }, 19 | goodsDetailId:{ 20 | type:Sequelize.BIGINT, 21 | allowNull:false 22 | }, 23 | orderId:{ 24 | type:Sequelize.BIGINT, 25 | allowNull:false 26 | }, 27 | content:{ 28 | type:Sequelize.STRING(500), 29 | allowNull:true 30 | }, 31 | /*打分,一颗星代表20分,最高100分*/ 32 | score:{ 33 | type:Sequelize.INTEGER, 34 | allowNull:false 35 | }, 36 | createtime:{ 37 | type:Sequelize.DATE, 38 | allowNull:false 39 | }, 40 | },{ 41 | timestamps:false, 42 | }); 43 | 44 | module.exports = CommentModel; -------------------------------------------------------------------------------- /server/models/GoodsDetailModel.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const sequelize = require('../config/sequelizeBase'); 3 | 4 | const GoodsDetailModel = sequelize.define('goodsDetail',{ 5 | id:{ 6 | type:Sequelize.BIGINT, 7 | primaryKey:true, 8 | allowNull:false, 9 | autoIncrement:true 10 | }, 11 | goodsId:{ 12 | type:Sequelize.BIGINT, 13 | allowNull:false 14 | }, 15 | specName:{ 16 | type:Sequelize.STRING(500), 17 | allowNull:false 18 | }, 19 | stockNum:{ 20 | type:Sequelize.INTEGER, 21 | defaultValue: 0, 22 | allowNull:false 23 | }, 24 | unitPrice:{ 25 | type:Sequelize.FLOAT, 26 | allowNull:false 27 | }, 28 | updatetime:{ 29 | type:Sequelize.DATE, 30 | allowNull:false 31 | }, 32 | createtime:{ 33 | type:Sequelize.DATE, 34 | allowNull:false 35 | }, 36 | },{ 37 | timestamps:false, 38 | }); 39 | 40 | module.exports = GoodsDetailModel; -------------------------------------------------------------------------------- /server/models/GoodsModel.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const sequelize = require('../config/sequelizeBase'); 3 | 4 | const GoodsModel = sequelize.define('goods',{ 5 | id:{ 6 | type:Sequelize.BIGINT, 7 | primaryKey:true, 8 | allowNull:false, 9 | autoIncrement:true 10 | }, 11 | name:{ 12 | type:Sequelize.STRING(500), 13 | allowNull:false 14 | }, 15 | typeId:{ 16 | type:Sequelize.BIGINT, 17 | allowNull:false 18 | }, 19 | img:{ 20 | type:Sequelize.STRING(500), 21 | allowNull:true 22 | }, 23 | desc:{ 24 | type:Sequelize.TEXT, 25 | allowNull:true 26 | }, 27 | updatetime:{ 28 | type:Sequelize.DATE, 29 | allowNull:false 30 | }, 31 | createtime:{ 32 | type:Sequelize.DATE, 33 | allowNull:false 34 | }, 35 | },{ 36 | timestamps:false, 37 | }); 38 | 39 | module.exports = GoodsModel; -------------------------------------------------------------------------------- /server/models/MessageModel.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const sequelize = require('../config/sequelizeBase'); 3 | 4 | const MessageModel = sequelize.define('message',{ 5 | id:{ 6 | type:Sequelize.BIGINT, 7 | primaryKey:true, 8 | allowNull:false, 9 | autoIncrement:true 10 | }, 11 | userId:{ 12 | type:Sequelize.BIGINT, 13 | allowNull:false 14 | }, 15 | goodsId:{ 16 | type:Sequelize.BIGINT, 17 | allowNull:false 18 | }, 19 | content:{ 20 | type:Sequelize.STRING(500), 21 | allowNull:false 22 | }, 23 | /*0未回复,1已回复*/ 24 | state:{ 25 | type:Sequelize.ENUM(0,1), 26 | defaultValue: 0, 27 | allowNull:false 28 | }, 29 | createtime:{ 30 | type:Sequelize.DATE, 31 | allowNull:false 32 | }, 33 | },{ 34 | timestamps:false, 35 | }); 36 | 37 | module.exports = MessageModel; -------------------------------------------------------------------------------- /server/models/OrderModel.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const sequelize = require('../config/sequelizeBase'); 3 | 4 | const OrderModel = sequelize.define('order',{ 5 | id:{ 6 | type:Sequelize.BIGINT, 7 | primaryKey:true, 8 | allowNull:false, 9 | autoIncrement:true 10 | }, 11 | userId:{ 12 | type:Sequelize.BIGINT, 13 | allowNull:false 14 | }, 15 | goodsDetailId:{ 16 | type:Sequelize.BIGINT, 17 | allowNull:false 18 | }, 19 | goodsNum:{ 20 | type:Sequelize.INTEGER, 21 | allowNull:false 22 | }, 23 | amount:{ 24 | type:Sequelize.FLOAT, 25 | allowNull:false 26 | }, 27 | /*0未付款,1已付款未发货,2已发货未确认收到,3确认到货订单完成*/ 28 | state:{ 29 | type:Sequelize.ENUM(0,1,2,3), 30 | defaultValue: 0, 31 | allowNull:false 32 | }, 33 | updatetime:{ 34 | type:Sequelize.DATE, 35 | allowNull:false 36 | }, 37 | createtime:{ 38 | type:Sequelize.DATE, 39 | allowNull:false 40 | }, 41 | },{ 42 | timestamps:false, 43 | }); 44 | 45 | module.exports = OrderModel; -------------------------------------------------------------------------------- /server/models/ReplyModel.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const sequelize = require('../config/sequelizeBase'); 3 | 4 | const ReplyModel = sequelize.define('reply',{ 5 | id:{ 6 | type:Sequelize.BIGINT, 7 | primaryKey:true, 8 | allowNull:false, 9 | autoIncrement:true 10 | }, 11 | messageId:{ 12 | type:Sequelize.BIGINT, 13 | allowNull:false 14 | }, 15 | content:{ 16 | type:Sequelize.STRING(500), 17 | allowNull:false 18 | }, 19 | createtime:{ 20 | type:Sequelize.DATE, 21 | allowNull:false 22 | }, 23 | },{ 24 | timestamps:false, 25 | }); 26 | 27 | module.exports = ReplyModel; -------------------------------------------------------------------------------- /server/models/TypeModel.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const sequelize = require('../config/sequelizeBase'); 3 | 4 | const TypeModel = sequelize.define('type',{ 5 | id:{ 6 | type:Sequelize.BIGINT, 7 | primaryKey:true, 8 | allowNull:false, 9 | autoIncrement:true 10 | }, 11 | name:{ 12 | type:Sequelize.STRING(255), 13 | allowNull:false 14 | }, 15 | },{ 16 | timestamps:false, 17 | }); 18 | 19 | module.exports = TypeModel; -------------------------------------------------------------------------------- /server/models/UserModel.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const sequelize = require('../config/sequelizeBase'); 3 | 4 | const UserModel = sequelize.define('user',{ 5 | id:{ 6 | type:Sequelize.BIGINT, 7 | primaryKey:true, 8 | allowNull:false, 9 | autoIncrement:true 10 | }, 11 | email:{ 12 | type:Sequelize.STRING(64), 13 | unique:true, 14 | allowNull:false 15 | }, 16 | pwd:{ 17 | type:Sequelize.STRING(255), 18 | allowNull:false 19 | }, 20 | nickname:{ 21 | type:Sequelize.STRING(64), 22 | unique:true, 23 | allowNull:false 24 | }, 25 | /*0保密,1男,2女*/ 26 | sex:{ 27 | type:Sequelize.ENUM(0,1,2), 28 | defaultValue: 0, 29 | allowNull:false 30 | }, 31 | recipient:{ 32 | type:Sequelize.STRING(64), 33 | allowNull:true 34 | }, 35 | address:{ 36 | type:Sequelize.STRING(500), 37 | allowNull:true 38 | }, 39 | phone:{ 40 | type:Sequelize.STRING(64), 41 | allowNull:true 42 | }, 43 | headimg:{ 44 | type:Sequelize.STRING(500), 45 | allowNull:false, 46 | defaultValue: 'http://tvax4.sinaimg.cn/crop.0.0.480.480.180/768c39d5ly8fjje1d0teej20dc0dcq35.jpg', 47 | }, 48 | updatetime:{ 49 | type:Sequelize.DATE, 50 | allowNull:false 51 | }, 52 | createtime:{ 53 | type:Sequelize.DATE, 54 | allowNull:false 55 | }, 56 | },{ 57 | timestamps:false, 58 | }); 59 | 60 | module.exports = UserModel; -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node bin/www", 7 | "dev": "./node_modules/.bin/nodemon bin/www", 8 | "prd": "pm2 start bin/www", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "dependencies": { 12 | "bcryptjs": "^2.4.3", 13 | "debug": "^2.6.3", 14 | "jsonwebtoken": "^8.1.0", 15 | "koa": "^2.2.0", 16 | "koa-bodyparser": "^3.2.0", 17 | "koa-convert": "^1.2.0", 18 | "koa-json": "^2.0.2", 19 | "koa-jwt": "^3.2.2", 20 | "koa-logger": "^2.0.1", 21 | "koa-onerror": "^1.2.1", 22 | "koa-router": "^7.1.1", 23 | "koa-static": "^3.0.0", 24 | "koa-views": "^5.2.1", 25 | "koa2-cors": "^2.0.5", 26 | "moment": "^2.19.1", 27 | "moment-timezone": "^0.5.14", 28 | "mysql2": "^1.4.2", 29 | "pug": "^2.0.0-rc.1", 30 | "request": "^2.83.0", 31 | "sequelize": "^4.20.1", 32 | "socket.io": "^2.0.4" 33 | }, 34 | "devDependencies": { 35 | "nodemon": "^1.8.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /server/routes/admin.js: -------------------------------------------------------------------------------- 1 | const router = require('koa-router')(); 2 | const userAdmin = require('../controllers/userAdmin'); 3 | const msgAdmin = require('../controllers/msgAdmin'); 4 | const orderAdmin = require('../controllers/orderAdmin'); 5 | const goodsAdmin = require('../controllers/goodsAdmin'); 6 | 7 | 8 | //登录 9 | router.post('/api/admin/login', userAdmin.login); 10 | 11 | //查询所有用户 12 | router.get('/api/admin/allUser', userAdmin.getAllUser); 13 | 14 | //删除指定用户 15 | router.delete('/api/admin/deleteUser', userAdmin.deleteUser); 16 | 17 | //搜索用户 18 | router.get('/api/admin/searchUser', userAdmin.searchUser); 19 | 20 | //修改密码 21 | router.post('/api/admin/changePwd', userAdmin.changePwd); 22 | 23 | //查询未回复信息 24 | router.get('/api/admin/noReplyMsg', msgAdmin.getNoReplyMsg); 25 | 26 | //查询已回复信息 27 | router.get('/api/admin/repliedMsg', msgAdmin.getRepliedMsg); 28 | 29 | //回复信息 30 | router.post('/api/admin/reply', msgAdmin.reply); 31 | 32 | //获取订单s 33 | router.get('/api/admin/orders', orderAdmin.getOrders); 34 | 35 | //获取订单一些可供修改的信息 36 | router.get('/api/admin/order', orderAdmin.getOrder); 37 | 38 | //修改订单 39 | router.post('/api/admin/changeOrder', orderAdmin.changeOrder); 40 | 41 | //删除订单 42 | router.delete('/api/admin/deleteOrder', orderAdmin.deleteOrder); 43 | 44 | //得到类目 45 | router.get('/api/admin/getType', goodsAdmin.getType); 46 | 47 | //得到商品 48 | router.get('/api/admin/getGoodsByType', goodsAdmin.getGoodsByType); 49 | 50 | //增加类目 51 | router.post('/api/admin/addType', goodsAdmin.addType); 52 | 53 | //得到商品 54 | router.get('/api/admin/getGoodsInfo', goodsAdmin.getGoodsInfo); 55 | 56 | //增加商品 57 | router.post('/api/admin/addGoods', goodsAdmin.addGoods); 58 | 59 | //增加规格 60 | router.post('/api/admin/addSpec', goodsAdmin.addSpec); 61 | 62 | //删除规格 63 | router.post('/api/admin/deleteSpec', goodsAdmin.deleteSpec); 64 | 65 | //更新商品信息 66 | router.post('/api/admin/updateGoods', goodsAdmin.updateGoods); 67 | 68 | //删除商品 69 | router.delete('/api/admin/deleteGoods', goodsAdmin.deleteGoods); 70 | 71 | module.exports = router; -------------------------------------------------------------------------------- /server/routes/mall.js: -------------------------------------------------------------------------------- 1 | const router = require('koa-router')(); 2 | const goods = require('../controllers/goods'); 3 | 4 | //得到不同类目的商品 5 | router.get('/api/mall/getGoodsByType', goods.getGoodsByType); 6 | 7 | //得到商品详情页信息 8 | router.get('/api/mall/getGoodsInfo', goods.getGoodsInfo); 9 | 10 | //获得商品详情页问答区数据 11 | router.get('/api/mall/getGoodsMsg', goods.getGoodsMsg); 12 | 13 | //提问商品 14 | router.post('/api/mall/askGoodsMsg', goods.askGoodsMsg); 15 | 16 | //加入购物车或立即购买 17 | router.post('/api/mall/addOrder', goods.addOrder); 18 | 19 | //获得用户订单 20 | router.get('/api/mall/getOrderByState', goods.getOrderByState); 21 | 22 | //删除订单 23 | router.delete('/api/mall/deleteOrder', goods.deleteOrder); 24 | 25 | //确认付款 26 | router.get('/api/mall/pay', goods.pay); 27 | 28 | //确认收货 29 | router.get('/api/mall/confirmReceive', goods.confirmReceive); 30 | 31 | //购物车结算 32 | router.post('/api/mall/settleAccounts', goods.settleAccounts); 33 | 34 | //发送评价 35 | router.post('/api/mall/sendComment', goods.sendComment); 36 | 37 | //得到商品评价 38 | router.get('/api/mall/getGoodsComment', goods.getGoodsComment); 39 | 40 | //关键词搜索商品 41 | router.get('/api/mall/searchGoods', goods.searchGoods); 42 | 43 | module.exports = router; 44 | -------------------------------------------------------------------------------- /server/routes/user.js: -------------------------------------------------------------------------------- 1 | const router = require('koa-router')(); 2 | const user = require('../controllers/user'); 3 | 4 | //注册 5 | router.post('/api/user/signup', user.signup); 6 | 7 | //登录 8 | router.post('/api/user/login', user.login); 9 | 10 | //获得用户基本信息 11 | router.get('/api/user/data', user.getData); 12 | 13 | //更改用户资料 14 | router.post('/api/user/updateUserData', user.updateUserData); 15 | 16 | //更改用户密码 17 | router.post('/api/user/updatePwd', user.updatePwd); 18 | 19 | /* 20 | //修改昵称 21 | router.post('/privateApi/user/setNickname',user.setNickname); 22 | 23 | //修改性别 24 | router.post('/privateApi/user/setSex',user.setSex); 25 | 26 | //修改城市 27 | router.post('/privateApi/user/setCity',user.setCity); 28 | 29 | //修改头像 30 | router.post('/privateApi/user/setHeadimg',user.setHeadimg); 31 | 32 | //修改密码 33 | router.post('/privateApi/user/setPwd',user.setPwd); 34 | 35 | //搜索用户 36 | router.get('/api/user/search', user.searchUser);*/ 37 | 38 | module.exports = router 39 | --------------------------------------------------------------------------------