├── .gitignore ├── README.md ├── client ├── .babelrc ├── .dockerignore ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── Dockerfile ├── 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 │ ├── nginx.conf │ └── prod.env.js ├── index.html ├── package-lock.json ├── package.json ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── Header.vue │ │ ├── ManufacturerForm.vue │ │ └── products │ │ │ ├── ProductButton.vue │ │ │ ├── ProductDetail.vue │ │ │ ├── ProductForm.vue │ │ │ ├── ProductItem.vue │ │ │ └── ProductList.vue │ ├── main.js │ ├── pages │ │ ├── Cart.vue │ │ ├── Detail.vue │ │ ├── Home.vue │ │ ├── admin │ │ │ ├── Edit.vue │ │ │ ├── EditManufacturers.vue │ │ │ ├── Index.vue │ │ │ ├── Manufacturers.vue │ │ │ ├── New.vue │ │ │ ├── NewManufacturers.vue │ │ │ └── Products.vue │ │ └── user │ │ │ ├── Index.vue │ │ │ ├── Login.vue │ │ │ └── Setting.vue │ ├── router │ │ └── index.js │ └── store │ │ ├── actions.js │ │ ├── getters.js │ │ ├── index.js │ │ ├── mutation-types.js │ │ └── mutations.js ├── static │ └── .gitkeep └── yarn.lock ├── docker-compose.yml └── server ├── .dockerignore ├── .gitignore ├── Dockerfile ├── app.js ├── bin └── www ├── controllers ├── manufacturer.js └── product.js ├── data ├── manufacturers.json └── products.json ├── model └── index.js ├── package-lock.json ├── package.json ├── public └── stylesheets │ └── style.css ├── routes ├── api │ └── index.js ├── index.js └── users.js ├── views ├── error.ejs └── index.ejs └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Tuture-related files 2 | 3 | .tuture 4 | tuture-error.log 5 | 6 | tuture-build/ 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用 2 | 3 | 【已完成】这里是《从零到部署:用 Vue 和 Express 实现迷你全栈电商应用》系列教程的源代码仓库。 4 | 5 | 6 | ## 项目预览 7 | 8 | ![Lark20200323-131207.gif](https://tva1.sinaimg.cn/large/00831rSTgy1gd54z2ym70g30jg0abazr.gif) 9 | 10 | 11 | ## 项目界面说明 12 | 13 | 14 | ### 首页 15 | 16 | 主要有首页头部导航栏以及展示本地商品信息的列表,列表主要展示了本地商品的名称、介绍、价格、生产商以及添加购物车操作。 17 | 18 | ![](https://tva1.sinaimg.cn/large/00831rSTgy1gd5502geyij311y0i974t.jpg) 19 | 20 | 21 | 22 | 23 | ### 后台管理页面 24 | 25 | 主要用于对商品以及生产商的后台管理,包括查看商品(可以进行修改商品信息)、添加商品、查看生产商(可以进行修改生产商信息)以及添加生产商。 26 | 27 | 28 | #### 查看商品页面 29 | 30 | 主要展示了后台商品的名称、价格、制造商以及修改和删除操作。 31 | 32 | ![](https://tva1.sinaimg.cn/large/00831rSTgy1gd54zzphloj311y0i9wes.jpg) 33 | 34 | 35 | 36 | 37 | #### 添加/修改商品页面 38 | 39 | 展示一个表单页面,主要用于添加一个新商品或者对指定商品信息进行修改。 40 | 41 | ![](https://tva1.sinaimg.cn/large/00831rSTgy1gd54zytgmqj311y0hhglk.jpg) 42 | 43 | 44 | #### 查看制造商页面 45 | 46 | 主要展示了后台制造商的名称以及修改和删除操作。 47 | 48 | ![](https://tva1.sinaimg.cn/large/00831rSTgy1gd54zwl8yej311t0h2wel.jpg) 49 | 50 | 51 | #### 添加/修改制造商页面 52 | 53 | 展示一个表单页面,主要用于添加一个新制造商或者对指定制造商信息进行修改。
![](https://tva1.sinaimg.cn/large/00831rSTgy1gd54zv65x4j311t0h10sp.jpg) 54 | 55 | 56 | #### 购物车页面 57 | 58 | 主要用于展示添加到本地购物车的商品信息列表,列表主要展示了购物车商品的名称、介绍、价格、生产商以及移出购物车操作。
![](https://tva1.sinaimg.cn/large/00831rSTgy1gd54zu7k4uj311u0h1t95.jpg) 59 | 60 | 61 | ## 体验项目 62 | 63 | 64 | 65 | ### 克隆仓库,开启前端和后端服务服务: 66 | 67 | - 克隆仓库然后进入该仓库: 68 | ```bash 69 | git clone https://github.com/tuture-dev/vue-online-shop-frontend.git 70 | cd vue-online-shop-frontend 71 | ``` 72 | 73 | ### 使用 Docker 一键开启服务 74 | 75 | 确保安装 Docker,然后执行如下命令: 76 | 77 | ```bash 78 | docker-compose up 79 | ``` 80 | 81 | ### 手动开启服务 82 | 83 | #### 数据库 84 | 85 | 下载安装和启动 MongoDB:[https://www.mongodb.com/](https://www.mongodb.com/) 86 | 87 | ##### 前端: 88 | 89 | 在项目目录下: 90 | 91 | ```bash 92 | cd client 93 | npm install # yarn 94 | npm start # yarn start 95 | ``` 96 | 97 | ##### 后端 98 | 99 | 在项目目录下: 100 | 101 | ```bash 102 | cd server 103 | npm install # yarn 104 | npm start # yarn start 105 | ``` 106 | 107 | 108 | ##  教程内容概要 109 | 110 | 1. [ 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(一)](https://tuture.co/2019/10/17/0b662ce/)
111 | 用 Vue 搭建前端项目的骨架,实现基于嵌套、动态路由的多页面跳转。 112 | 113 | 2. [ 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(二)](https://tuture.co/2019/10/21/cb08dc8/)
114 | 我们通过基于 Node.js 平台的 Express 框架实现了后端 API 数据接口,并且将数据存储在 MongoDB 中。这样我们的网站就能够记录用户添加的商品,并且无论以后什么时候打开,都能获取我们之前的记录。 115 | 116 | 3. [ 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(三)](https://tuture.co/2019/12/18/5e10a46/)
117 | 我们讲解了 Vue 实例的 Props 和 Methods,接着我们又讲解了最常见的 Vue 模板语法,并通过实例的方式将这些模板语法都实践了一番,最后我们讲解了 Vue 组件的组合,并完成了我们的发表商品页面。 118 | 119 | 4. [ 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(四)](https://tuture.co/2020/01/10/ae8a389/)
120 | 我们使用了状态管理库 Vuex 并带大家熟悉了 Store、Mutation 和 Action 三大关键概念,然后升级了迷你商城应用的前端代码。 121 | 122 | 5. [ 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(五)](https://tuture.co/2020/02/11/6f96d15/)
123 | 我们带大家抽出了 Vue 组件从而简化页面逻辑,使用 Vuex Getters 复用本地数据获取逻辑。 124 | 125 | 6. [ 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(六)](https://tuture.co/2020/03/03/-Oixkkq/)
126 | 我们带大家一起学习了如何抽出 Getters 、 Mutations 和Actions 逻辑实现store的“减重”以及如何干掉 mutation-types 硬编码。 127 | 128 | 7. [ 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(七)](https://tuture.co/2020/03/13/tc1c9oD/)
129 | 我们基于element-ui组件库重构了项目的前端代码,改善迷你电商应用的界面效果,提高用户的体验感;并且从试错的角度带大家一起踩了代码重构造成的一系列坑。 130 | 131 | 8. [ 从零到部署:用 Vue 和 Express 实现迷你全栈电商应用(八)](https://tuture.co/2020/03/14/-td0ssr/)
132 | 我们首先使用 Docker 来容器化应用,接着教大家配置了 MongoDB 的身份验证机制,给数据库添加一份安全守护,最后我们教大家使用阿里云的容器镜像服务将整个全栈应用部署到了云端,使互联网上的用户可以访问我们的网站。 133 | 134 | ## 反馈 135 | 136 | 欢迎对此教程的内容进行反馈(无论是疑问还是改进意见),可以在文章下方留言,也可以在此仓库创建 Issue! 137 | 138 | 139 | ### 联系我们 140 | 141 | - [微信公众号](https://tuture.co/images/social/wechat.png):关注公众号,加图雀酱微信拉你进学习交流群 142 | - [掘金](https://juejin.im/user/5b33414351882574b9694d28) 143 | - [知乎专栏](https://zhuanlan.zhihu.com/tuture) 144 | - 知乎圈子:搜索 图雀社区 145 | - 也可以直接扫码下方的二维码关注微信公众号哦:
146 | ![](https://tva1.sinaimg.cn/large/00831rSTgy1gd54zrfl26j30p00dw41z.jpg) 147 | 148 | **** 149 | ## 许可证 150 | 151 | MIT。 152 | -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /client/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /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/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 12 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 13 | extends: ['plugin:vue/essential', 'airbnb-base'], 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'vue' 17 | ], 18 | // check if imports actually resolve 19 | settings: { 20 | 'import/resolver': { 21 | webpack: { 22 | config: 'build/webpack.base.conf.js' 23 | } 24 | } 25 | }, 26 | // add your custom rules here 27 | rules: { 28 | // don't require .vue extension when importing 29 | 'import/extensions': ['error', 'always', { 30 | js: 'never', 31 | vue: 'never' 32 | }], 33 | // disallow reassignment of function parameters 34 | // disallow parameter object manipulation except for specific exclusions 35 | 'no-param-reassign': ['error', { 36 | props: true, 37 | ignorePropertyModificationsFor: [ 38 | 'state', // for vuex state 39 | 'acc', // for reduce accumulators 40 | 'e' // for e.returnvalue 41 | ] 42 | }], 43 | // allow optionalDependencies 44 | 'import/no-extraneous-dependencies': ['error', { 45 | optionalDependencies: ['test/unit/index.js'] 46 | }], 47 | // allow debugger during development 48 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /client/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.13 2 | 3 | # 删除 Nginx 的默认配置 4 | RUN rm /etc/nginx/conf.d/default.conf 5 | 6 | # 添加自定义 Nginx 配置 7 | COPY config/nginx.conf /etc/nginx/conf.d/ 8 | 9 | # 将前端静态文件拷贝到容器的 /www 目录下 10 | COPY dist /www 11 | -------------------------------------------------------------------------------- /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, (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, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 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 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /client/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuture-dev/vue-online-shop-frontend/f25eb7cbe23d905594334ddeede9a1e13d2d8462/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 packageConfig = 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 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /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 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /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 | const createLintingRule = () => ({ 12 | test: /\.(js|vue)$/, 13 | loader: "eslint-loader", 14 | enforce: "pre", 15 | include: [resolve("src"), resolve("test")], 16 | options: { 17 | formatter: require("eslint-friendly-formatter"), 18 | emitWarning: !config.dev.showEslintErrorsInOverlay 19 | } 20 | }); 21 | 22 | module.exports = { 23 | context: path.resolve(__dirname, "../"), 24 | entry: { 25 | app: "./src/main.js" 26 | }, 27 | output: { 28 | path: config.build.assetsRoot, 29 | filename: "[name].js", 30 | publicPath: 31 | process.env.NODE_ENV === "production" 32 | ? config.build.assetsPublicPath 33 | : config.dev.assetsPublicPath 34 | }, 35 | resolve: { 36 | extensions: [".js", ".vue", ".json"], 37 | alias: { 38 | vue$: "vue/dist/vue.esm.js", 39 | "@": resolve("src") 40 | } 41 | }, 42 | module: { 43 | rules: [ 44 | { 45 | test: /\.vue$/, 46 | loader: "vue-loader", 47 | options: vueLoaderConfig 48 | }, 49 | { 50 | test: /\.js$/, 51 | loader: "babel-loader", 52 | include: [ 53 | resolve("src"), 54 | resolve("test"), 55 | resolve("node_modules/webpack-dev-server/client") 56 | ] 57 | }, 58 | { 59 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 60 | loader: "url-loader", 61 | options: { 62 | limit: 10000, 63 | name: utils.assetsPath("img/[name].[hash:7].[ext]") 64 | } 65 | }, 66 | { 67 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 68 | loader: "url-loader", 69 | options: { 70 | limit: 10000, 71 | name: utils.assetsPath("media/[name].[hash:7].[ext]") 72 | } 73 | }, 74 | { 75 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 76 | loader: "url-loader", 77 | options: { 78 | limit: 10000, 79 | name: utils.assetsPath("fonts/[name].[hash:7].[ext]") 80 | } 81 | } 82 | ] 83 | }, 84 | node: { 85 | // prevent webpack from injecting useless setImmediate polyfill because Vue 86 | // source contains it (although only uses it if it's native). 87 | setImmediate: false, 88 | // prevent webpack from injecting mocks to Node native modules 89 | // that does not make sense for the client 90 | dgram: "empty", 91 | fs: "empty", 92 | net: "empty", 93 | tls: "empty", 94 | child_process: "empty" 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /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 path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /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 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = require('../config/prod.env') 15 | 16 | const webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true, 21 | usePostCSS: true 22 | }) 23 | }, 24 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 25 | output: { 26 | path: config.build.assetsRoot, 27 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new UglifyJsPlugin({ 36 | uglifyOptions: { 37 | compress: { 38 | warnings: false 39 | } 40 | }, 41 | sourceMap: config.build.productionSourceMap, 42 | parallel: true 43 | }), 44 | // extract css into its own file 45 | new ExtractTextPlugin({ 46 | filename: utils.assetsPath('css/[name].[contenthash].css'), 47 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 51 | allChunks: true, 52 | }), 53 | // Compress extracted CSS. We are using this plugin so that possible 54 | // duplicated CSS from different components can be deduped. 55 | new OptimizeCSSPlugin({ 56 | cssProcessorOptions: config.build.productionSourceMap 57 | ? { safe: true, map: { inline: false } } 58 | : { safe: true } 59 | }), 60 | // generate dist index.html with correct asset hash for caching. 61 | // you can customize output by editing /index.html 62 | // see https://github.com/ampedandwired/html-webpack-plugin 63 | new HtmlWebpackPlugin({ 64 | filename: config.build.index, 65 | template: 'index.html', 66 | inject: true, 67 | minify: { 68 | removeComments: true, 69 | collapseWhitespace: true, 70 | removeAttributeQuotes: true 71 | // more options: 72 | // https://github.com/kangax/html-minifier#options-quick-reference 73 | }, 74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 75 | chunksSortMode: 'dependency' 76 | }), 77 | // keep module.id stable when vendor modules does not change 78 | new webpack.HashedModuleIdsPlugin(), 79 | // enable scope hoisting 80 | new webpack.optimize.ModuleConcatenationPlugin(), 81 | // split vendor js into its own file 82 | new webpack.optimize.CommonsChunkPlugin({ 83 | name: 'vendor', 84 | minChunks (module) { 85 | // any required modules inside node_modules are extracted to vendor 86 | return ( 87 | module.resource && 88 | /\.js$/.test(module.resource) && 89 | module.resource.indexOf( 90 | path.join(__dirname, '../node_modules') 91 | ) === 0 92 | ) 93 | } 94 | }), 95 | // extract webpack runtime and module manifest to its own file in order to 96 | // prevent vendor hash from being updated whenever app bundle is updated 97 | new webpack.optimize.CommonsChunkPlugin({ 98 | name: 'manifest', 99 | minChunks: Infinity 100 | }), 101 | // This instance extracts shared chunks from code splitted chunks and bundles them 102 | // in a separate chunk, similar to the vendor chunk 103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 104 | new webpack.optimize.CommonsChunkPlugin({ 105 | name: 'app', 106 | async: 'vendor-async', 107 | children: true, 108 | minChunks: 3 109 | }), 110 | 111 | // copy custom static assets 112 | new CopyWebpackPlugin([ 113 | { 114 | from: path.resolve(__dirname, '../static'), 115 | to: config.build.assetsSubDirectory, 116 | ignore: ['.*'] 117 | } 118 | ]) 119 | ] 120 | }) 121 | 122 | if (config.build.productionGzip) { 123 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 124 | 125 | webpackConfig.plugins.push( 126 | new CompressionWebpackPlugin({ 127 | asset: '[path].gz[query]', 128 | algorithm: 'gzip', 129 | test: new RegExp( 130 | '\\.(' + 131 | config.build.productionGzipExtensions.join('|') + 132 | ')$' 133 | ), 134 | threshold: 10240, 135 | minRatio: 0.8 136 | }) 137 | ) 138 | } 139 | 140 | if (config.build.bundleAnalyzerReport) { 141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 143 | } 144 | 145 | module.exports = webpackConfig 146 | -------------------------------------------------------------------------------- /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.3.1 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: 'cheap-module-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 | cssSourceMap: true 44 | }, 45 | 46 | build: { 47 | // Template for index.html 48 | index: path.resolve(__dirname, '../dist/index.html'), 49 | 50 | // Paths 51 | assetsRoot: path.resolve(__dirname, '../dist'), 52 | assetsSubDirectory: 'static', 53 | assetsPublicPath: '/', 54 | 55 | /** 56 | * Source Maps 57 | */ 58 | 59 | productionSourceMap: true, 60 | // https://webpack.js.org/configuration/devtool/#production 61 | devtool: '#source-map', 62 | 63 | // Gzip off by default as many popular static hosts such as 64 | // Surge or Netlify already gzip all static assets for you. 65 | // Before setting to `true`, make sure to: 66 | // npm install --save-dev compression-webpack-plugin 67 | productionGzip: false, 68 | productionGzipExtensions: ['js', 'css'], 69 | 70 | // Run the build command with an extra argument to 71 | // View the bundle analyzer report after build finishes: 72 | // `npm run build --report` 73 | // Set to `true` or `false` to always turn it on or off 74 | bundleAnalyzerReport: process.env.npm_config_report 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /client/config/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | root /www; 4 | index index.html; 5 | sendfile on; 6 | sendfile_max_chunk 1M; 7 | tcp_nopush on; 8 | gzip_static on; 9 | 10 | location /api/v1 { 11 | proxy_pass http://api:3000; 12 | } 13 | 14 | location / { 15 | try_files $uri $uri/ /index.html; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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 | vue-online-shop 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-online-shop", 3 | "version": "1.0.0", 4 | "description": "Mini e-commerce project", 5 | "author": "pftom <1043269994@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 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "authing-js-sdk": "^3.18.7", 15 | "axios": "^0.19.0", 16 | "element-ui": "^2.13.0", 17 | "vee-validate": "^3.0.11", 18 | "vue": "^2.5.2", 19 | "vue-router": "^3.0.1", 20 | "vuex": "^3.1.1" 21 | }, 22 | "devDependencies": { 23 | "autoprefixer": "^7.1.2", 24 | "babel-core": "^6.22.1", 25 | "babel-eslint": "^8.2.1", 26 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 27 | "babel-loader": "^7.1.1", 28 | "babel-plugin-component": "^1.1.1", 29 | "babel-plugin-syntax-jsx": "^6.18.0", 30 | "babel-plugin-transform-runtime": "^6.22.0", 31 | "babel-plugin-transform-vue-jsx": "^3.5.0", 32 | "babel-preset-env": "^1.3.2", 33 | "babel-preset-stage-2": "^6.22.0", 34 | "chalk": "^2.0.1", 35 | "copy-webpack-plugin": "^4.0.1", 36 | "css-loader": "^0.28.0", 37 | "eslint": "^4.15.0", 38 | "eslint-config-airbnb-base": "^11.3.0", 39 | "eslint-friendly-formatter": "^3.0.0", 40 | "eslint-import-resolver-webpack": "^0.8.3", 41 | "eslint-loader": "^1.7.1", 42 | "eslint-plugin-import": "^2.7.0", 43 | "eslint-plugin-vue": "^4.0.0", 44 | "extract-text-webpack-plugin": "^3.0.0", 45 | "file-loader": "^1.1.4", 46 | "friendly-errors-webpack-plugin": "^1.6.1", 47 | "html-webpack-plugin": "^2.30.1", 48 | "node-notifier": "^5.1.2", 49 | "optimize-css-assets-webpack-plugin": "^3.2.0", 50 | "ora": "^1.2.0", 51 | "portfinder": "^1.0.13", 52 | "postcss-import": "^11.0.0", 53 | "postcss-loader": "^2.0.8", 54 | "postcss-url": "^7.2.1", 55 | "rimraf": "^2.6.0", 56 | "semver": "^5.3.0", 57 | "shelljs": "^0.7.6", 58 | "uglifyjs-webpack-plugin": "^1.1.1", 59 | "url-loader": "^0.5.8", 60 | "vue-loader": "^13.3.0", 61 | "vue-style-loader": "^3.0.1", 62 | "vue-template-compiler": "^2.5.2", 63 | "webpack": "^3.6.0", 64 | "webpack-bundle-analyzer": "^2.9.0", 65 | "webpack-dev-server": "^2.9.1", 66 | "webpack-merge": "^4.1.0" 67 | }, 68 | "engines": { 69 | "node": ">= 6.0.0", 70 | "npm": ">= 3.0.0" 71 | }, 72 | "browserslist": [ 73 | "> 1%", 74 | "last 2 versions", 75 | "not ie <= 8" 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 43 | 44 | 53 | -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuture-dev/vue-online-shop-frontend/f25eb7cbe23d905594334ddeede9a1e13d2d8462/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 91 | 92 | 145 | -------------------------------------------------------------------------------- /client/src/components/ManufacturerForm.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 70 | 82 | -------------------------------------------------------------------------------- /client/src/components/products/ProductButton.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 97 | -------------------------------------------------------------------------------- /client/src/components/products/ProductDetail.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 28 | 29 | 38 | -------------------------------------------------------------------------------- /client/src/components/products/ProductForm.vue: -------------------------------------------------------------------------------- 1 | 112 | 113 | 156 | 168 | -------------------------------------------------------------------------------- /client/src/components/products/ProductItem.vue: -------------------------------------------------------------------------------- 1 | 51 | 64 | -------------------------------------------------------------------------------- /client/src/components/products/ProductList.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 34 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from "vue"; 4 | import { ValidationProvider } from "vee-validate"; 5 | 6 | import App from "./App"; 7 | import router from "./router"; 8 | import store from "./store"; 9 | import ElementUI from "element-ui"; 10 | import "element-ui/lib/theme-chalk/index.css"; 11 | 12 | Vue.config.productionTip = false; 13 | Vue.component("ValidationProvider", ValidationProvider); 14 | Vue.use(ElementUI); 15 | 16 | /* eslint-disable no-new */ 17 | new Vue({ 18 | el: "#app", 19 | router, 20 | store, 21 | components: { App }, 22 | template: "" 23 | }); 24 | -------------------------------------------------------------------------------- /client/src/pages/Cart.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 43 | -------------------------------------------------------------------------------- /client/src/pages/Detail.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 29 | -------------------------------------------------------------------------------- /client/src/pages/Home.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 29 | -------------------------------------------------------------------------------- /client/src/pages/admin/Edit.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 54 | -------------------------------------------------------------------------------- /client/src/pages/admin/EditManufacturers.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 38 | -------------------------------------------------------------------------------- /client/src/pages/admin/Index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 40 | -------------------------------------------------------------------------------- /client/src/pages/admin/Manufacturers.vue: -------------------------------------------------------------------------------- 1 | 38 | 63 | -------------------------------------------------------------------------------- /client/src/pages/admin/New.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 40 | -------------------------------------------------------------------------------- /client/src/pages/admin/NewManufacturers.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | -------------------------------------------------------------------------------- /client/src/pages/admin/Products.vue: -------------------------------------------------------------------------------- 1 | 52 | 80 | 102 | -------------------------------------------------------------------------------- /client/src/pages/user/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /client/src/pages/user/Login.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 46 | -------------------------------------------------------------------------------- /client/src/pages/user/Setting.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 72 | 73 | 164 | -------------------------------------------------------------------------------- /client/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Router from "vue-router"; 3 | 4 | import Home from "@/pages/Home"; 5 | import Cart from "@/pages/Cart"; 6 | import Detail from "@/pages/Detail"; 7 | 8 | // Admin Components 9 | import Index from "@/pages/admin/Index"; 10 | import New from "@/pages/admin/New"; 11 | import Products from "@/pages/admin/Products"; 12 | import Edit from "@/pages/admin/Edit"; 13 | import Manufacturers from "@/pages/admin/Manufacturers"; 14 | import NewManufacturers from "@/pages/admin/NewManufacturers"; 15 | import EditManufacturers from "@/pages/admin/EditManufacturers"; 16 | import UserIndex from "@/pages/user/Index"; 17 | import Login from "@/pages/user/Login"; 18 | import Setting from "@/pages/user/Setting"; 19 | 20 | Vue.use(Router); 21 | 22 | const router = new Router({ 23 | routes: [ 24 | { 25 | path: "/", 26 | name: "Home", 27 | component: Home 28 | }, 29 | { 30 | path: "/admin", 31 | name: "Admin", 32 | component: Index, 33 | children: [ 34 | { 35 | path: "new", 36 | name: "New", 37 | component: New 38 | }, 39 | { 40 | path: "", 41 | name: "Products", 42 | component: Products 43 | }, 44 | { 45 | path: "edit/:id", 46 | name: "Edit", 47 | component: Edit 48 | }, 49 | { 50 | path: "manufacturers", 51 | name: "Manufacturers", 52 | component: Manufacturers 53 | }, 54 | { 55 | path: "manufacturers/new", 56 | name: "NewManufacturers", 57 | component: NewManufacturers 58 | }, 59 | { 60 | path: "manufacturers/edit/:id", 61 | name: "EditManufacturers", 62 | component: EditManufacturers 63 | } 64 | ] 65 | }, 66 | { 67 | path: "/cart", 68 | name: "Cart", 69 | component: Cart 70 | }, 71 | { 72 | path: "/detail/:id", 73 | name: "Detail", 74 | component: Detail 75 | }, 76 | { 77 | path: "/user", 78 | name: "User", 79 | component: UserIndex, 80 | children: [ 81 | { 82 | path: "login", 83 | name: "Login", 84 | component: Login 85 | }, 86 | { 87 | path: "settings", 88 | name: "Settings", 89 | component: Setting 90 | } 91 | ] 92 | } 93 | ] 94 | }); 95 | 96 | export default router; 97 | -------------------------------------------------------------------------------- /client/src/store/actions.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | import { 4 | ADD_PRODUCT, 5 | ADD_PRODUCT_SUCCESS, 6 | PRODUCT_BY_ID, 7 | PRODUCT_BY_ID_SUCCESS, 8 | UPDATE_PRODUCT, 9 | UPDATE_PRODUCT_SUCCESS, 10 | REMOVE_PRODUCT, 11 | REMOVE_PRODUCT_SUCCESS, 12 | ALL_PRODUCTS, 13 | ALL_PRODUCTS_SUCCESS, 14 | ALL_MANUFACTURERS, 15 | ALL_MANUFACTURERS_SUCCESS, 16 | MANUFACTURER_BY_ID, 17 | MANUFACTURER_BY_ID_SUCCESS, 18 | ADD_MANUFACTURER, 19 | ADD_MANUFACTURER_SUCCESS, 20 | UPDATE_MANUFACTURER, 21 | UPDATE_MANUFACTURER_SUCCESS, 22 | REMOVE_MANUFACTURER, 23 | REMOVE_MANUFACTURER_SUCCESS 24 | } from "./mutation-types"; 25 | import { Message } from "element-ui"; 26 | 27 | const API_BASE = "http://localhost:3000/api/v1"; 28 | 29 | export const productActions = { 30 | allProducts({ commit }) { 31 | commit(ALL_PRODUCTS); 32 | 33 | axios.get(`${API_BASE}/products`).then(response => { 34 | commit(ALL_PRODUCTS_SUCCESS, { 35 | products: response.data 36 | }); 37 | }); 38 | }, 39 | productById({ commit }, payload) { 40 | commit(PRODUCT_BY_ID); 41 | 42 | const { productId } = payload; 43 | axios.get(`${API_BASE}/products/${productId}`).then(response => { 44 | commit(PRODUCT_BY_ID_SUCCESS, { 45 | product: response.data 46 | }); 47 | }); 48 | }, 49 | removeProduct({ commit }, payload) { 50 | commit(REMOVE_PRODUCT); 51 | 52 | const { productId } = payload; 53 | axios 54 | .delete(`${API_BASE}/products/${productId}`) 55 | .then(() => { 56 | // 返回 productId,用于删除本地对应的商品 57 | commit(REMOVE_PRODUCT_SUCCESS, { 58 | productId 59 | }); 60 | Message({ 61 | message: "恭喜你,商品删除成功!", 62 | type: "success" 63 | }); 64 | }) 65 | .catch(() => { 66 | Message.error("不好意思,商品删除失败!"); 67 | }); 68 | }, 69 | updateProduct({ commit }, payload) { 70 | commit(UPDATE_PRODUCT); 71 | 72 | const { product } = payload; 73 | axios 74 | .put(`${API_BASE}/products/${product._id}`, product) 75 | .then(response => { 76 | commit(UPDATE_PRODUCT_SUCCESS, { 77 | product: product 78 | }); 79 | Message({ 80 | message: "恭喜你,商品更新成功!", 81 | type: "success" 82 | }); 83 | }) 84 | .catch(() => { 85 | Message.error("不好意思,商品更新失败!"); 86 | }); 87 | }, 88 | addProduct({ commit, state }, payload) { 89 | commit(ADD_PRODUCT); 90 | 91 | const { product } = payload; 92 | const _id = state.user._id; 93 | axios 94 | .post(`${API_BASE}/products`, { 95 | ...product, 96 | user: _id, 97 | manufacturer: product.manufacturer._id 98 | }) 99 | .then(response => { 100 | commit(ADD_PRODUCT_SUCCESS, { 101 | product: response.data 102 | }); 103 | Message({ 104 | message: "恭喜你,商品添加成功!", 105 | type: "success" 106 | }); 107 | }) 108 | .catch(() => { 109 | Message.error("不好意思,商品添加失败!"); 110 | }); 111 | } 112 | }; 113 | 114 | export const manufacturerActions = { 115 | allManufacturers({ commit }) { 116 | commit(ALL_MANUFACTURERS); 117 | 118 | axios.get(`${API_BASE}/manufacturers`).then(response => { 119 | commit(ALL_MANUFACTURERS_SUCCESS, { 120 | manufacturers: response.data 121 | }); 122 | }); 123 | }, 124 | manufacturerById({ commit }, payload) { 125 | commit(MANUFACTURER_BY_ID); 126 | 127 | const { manufacturerId } = payload; 128 | axios.get(`${API_BASE}/manufacturers/${manufacturerId}`).then(response => { 129 | commit(MANUFACTURER_BY_ID_SUCCESS, { 130 | manufacturer: response.data 131 | }); 132 | }); 133 | }, 134 | removeManufacturer({ commit }, payload) { 135 | commit(REMOVE_MANUFACTURER); 136 | 137 | const { manufacturerId } = payload; 138 | axios 139 | .delete(`${API_BASE}/manufacturers/${manufacturerId}`) 140 | .then(() => { 141 | // 返回 manufacturerId,用于删除本地对应的制造商 142 | commit(REMOVE_MANUFACTURER_SUCCESS, { 143 | manufacturerId 144 | }); 145 | Message({ 146 | message: "恭喜你,制造商删除成功!", 147 | type: "success" 148 | }); 149 | }) 150 | .catch(() => { 151 | Message.error("不好意思,制造商删除失败!"); 152 | }); 153 | }, 154 | updateManufacturer({ commit }, payload) { 155 | commit(UPDATE_MANUFACTURER); 156 | 157 | const { manufacturer } = payload; 158 | axios 159 | .put(`${API_BASE}/manufacturers/${manufacturer._id}`, manufacturer) 160 | .then(response => { 161 | commit(UPDATE_MANUFACTURER_SUCCESS, { 162 | manufacturer: manufacturer 163 | }); 164 | Message({ 165 | message: "恭喜你,制造商更新成功!", 166 | type: "success" 167 | }); 168 | }) 169 | .catch(() => { 170 | Message.error("不好意思,制造商更新失败!"); 171 | }); 172 | }, 173 | addManufacturer({ commit, state }, payload) { 174 | commit(ADD_MANUFACTURER); 175 | const { manufacturer } = payload; 176 | const _id = state.user._id; 177 | 178 | axios 179 | .post(`${API_BASE}/manufacturers`, { ...manufacturer, user: _id }) 180 | .then(response => { 181 | commit(ADD_MANUFACTURER_SUCCESS, { 182 | manufacturer: response.data 183 | }); 184 | Message({ 185 | message: "恭喜你,制造商添加成功!", 186 | type: "success" 187 | }); 188 | }) 189 | .catch(() => { 190 | Message.error("不好意思,制造商添加失败!"); 191 | }); 192 | } 193 | }; 194 | -------------------------------------------------------------------------------- /client/src/store/getters.js: -------------------------------------------------------------------------------- 1 | export const productGetters = { 2 | allProducts(state) { 3 | return state.products 4 | }, 5 | productById: (state, getters) => id => { 6 | if (getters.allProducts.length > 0) { 7 | return getters.allProducts.filter(product => product._id === id)[0] 8 | } else { 9 | return state.product; 10 | } 11 | } 12 | } 13 | 14 | export const manufacturerGetters = { 15 | allManufacturers(state) { 16 | return state.manufacturers; 17 | }, 18 | manufacturerById: (state, getters) => id => { 19 | if (getters.allManufacturers.length > 0) { 20 | return getters.allManufacturers.filter(manufacturer => manufacturer._id === id)[0] 21 | } else { 22 | return state.manufacturer; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | 4 | import { productGetters, manufacturerGetters } from "./getters"; 5 | import { 6 | productMutations, 7 | cartMutations, 8 | manufacturerMutations, 9 | userMutations 10 | } from "./mutations"; 11 | import { productActions, manufacturerActions } from "./actions"; 12 | 13 | Vue.use(Vuex); 14 | 15 | export default new Vuex.Store({ 16 | strict: true, 17 | state: { 18 | // bought items 19 | cart: [], 20 | // ajax loader 21 | showLoader: false, 22 | // selected product 23 | product: {}, 24 | // all products 25 | products: [], 26 | // all manufacturers 27 | manufacturers: [], 28 | // selected manufacturer 29 | manufacturer: {}, 30 | // userInfo 31 | user: {} 32 | }, 33 | mutations: { 34 | ...productMutations, 35 | ...cartMutations, 36 | ...manufacturerMutations, 37 | ...userMutations 38 | }, 39 | getters: { 40 | ...productGetters, 41 | ...manufacturerGetters 42 | }, 43 | actions: { 44 | ...productActions, 45 | ...manufacturerActions 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /client/src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const ALL_PRODUCTS = "ALL_PRODUCTS"; 2 | export const ALL_PRODUCTS_SUCCESS = "ALL_PRODUCTS_SUCCESS"; 3 | 4 | export const PRODUCT_BY_ID = "PRODUCT_BY_ID"; 5 | export const PRODUCT_BY_ID_SUCCESS = "PRODUCT_BY_ID_SUCCESS"; 6 | 7 | export const ADD_PRODUCT = "ADD_PRODUCT"; 8 | export const ADD_PRODUCT_SUCCESS = "ADD_PRODUCT_SUCCESS"; 9 | 10 | export const UPDATE_PRODUCT = "UPDATE_PRODUCT"; 11 | export const UPDATE_PRODUCT_SUCCESS = "UPDATE_PRODUCT_SUCCESS"; 12 | 13 | export const REMOVE_PRODUCT = "REMOVE_PRODUCT"; 14 | export const REMOVE_PRODUCT_SUCCESS = "REMOVE_PRODUCT_SUCCESS"; 15 | 16 | export const ADD_TO_CART = "ADD_TO_CART"; 17 | export const REMOVE_FROM_CART = "REMOVE_FROM_CART"; 18 | 19 | export const ALL_MANUFACTURERS = "ALL_MANUFACTURER"; 20 | export const ALL_MANUFACTURERS_SUCCESS = "ALL_MANUFACTURER_S"; 21 | 22 | export const MANUFACTURER_BY_ID = "MANUFACTURER_BY_ID"; 23 | export const MANUFACTURER_BY_ID_SUCCESS = "MANUFACTURER_BY_ID_SUCCESS"; 24 | 25 | export const ADD_MANUFACTURER = "ADD_MANUFACTURER"; 26 | export const ADD_MANUFACTURER_SUCCESS = "ADD_MANUFACTURER_SUCCESS"; 27 | 28 | export const UPDATE_MANUFACTURER = "UPDATE_MANUFACTURER"; 29 | export const UPDATE_MANUFACTURER_SUCCESS = "UPDATE_MANUFACTURER_SUCCESS"; 30 | 31 | export const REMOVE_MANUFACTURER = "REMOVE_MANUFACTURER"; 32 | export const REMOVE_MANUFACTURER_SUCCESS = "REMOVE_MANUFACTURER_SUCCESS"; 33 | 34 | export const SET_USER = "SET_USER"; 35 | export const UPDATE_USER = "UPDATE_USER"; 36 | export const LOGOUT = "LOGOUT"; 37 | -------------------------------------------------------------------------------- /client/src/store/mutations.js: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_PRODUCT, 3 | ADD_PRODUCT_SUCCESS, 4 | PRODUCT_BY_ID, 5 | PRODUCT_BY_ID_SUCCESS, 6 | UPDATE_PRODUCT, 7 | UPDATE_PRODUCT_SUCCESS, 8 | REMOVE_PRODUCT, 9 | REMOVE_PRODUCT_SUCCESS, 10 | ADD_TO_CART, 11 | REMOVE_FROM_CART, 12 | ALL_PRODUCTS, 13 | ALL_PRODUCTS_SUCCESS, 14 | ALL_MANUFACTURERS, 15 | ALL_MANUFACTURERS_SUCCESS, 16 | MANUFACTURER_BY_ID, 17 | MANUFACTURER_BY_ID_SUCCESS, 18 | ADD_MANUFACTURER, 19 | ADD_MANUFACTURER_SUCCESS, 20 | UPDATE_MANUFACTURER, 21 | UPDATE_MANUFACTURER_SUCCESS, 22 | REMOVE_MANUFACTURER, 23 | REMOVE_MANUFACTURER_SUCCESS, 24 | SET_USER, 25 | UPDATE_USER, 26 | LOGOUT 27 | } from "./mutation-types"; 28 | import { Message } from "element-ui"; 29 | 30 | export const userMutations = { 31 | [SET_USER](state, payload) { 32 | state.user = payload; 33 | }, 34 | [LOGOUT](state) { 35 | state.user = {}; 36 | } 37 | }; 38 | 39 | export const productMutations = { 40 | [ALL_PRODUCTS](state) { 41 | state.showLoader = true; 42 | }, 43 | [ALL_PRODUCTS_SUCCESS](state, payload) { 44 | const { products } = payload; 45 | 46 | state.showLoader = false; 47 | state.products = products; 48 | }, 49 | [PRODUCT_BY_ID](state) { 50 | state.showLoader = true; 51 | }, 52 | [PRODUCT_BY_ID_SUCCESS](state, payload) { 53 | state.showLoader = false; 54 | 55 | const { product } = payload; 56 | state.product = product; 57 | }, 58 | [REMOVE_PRODUCT](state) { 59 | state.showLoader = true; 60 | }, 61 | [REMOVE_PRODUCT_SUCCESS](state, payload) { 62 | state.showLoader = false; 63 | 64 | const { productId } = payload; 65 | state.products = state.products.filter( 66 | product => product._id !== productId 67 | ); 68 | }, 69 | [UPDATE_PRODUCT](state) { 70 | state.showLoader = true; 71 | }, 72 | [UPDATE_PRODUCT_SUCCESS](state, payload) { 73 | state.showLoader = false; 74 | 75 | const { product: newProduct } = payload; 76 | state.product = newProduct; 77 | state.products = state.products.map(product => { 78 | if (product._id === newProduct._id) { 79 | return newProduct; 80 | } 81 | return product; 82 | }); 83 | 84 | state.product = newProduct; 85 | }, 86 | [ADD_PRODUCT](state) { 87 | state.showLoader = true; 88 | }, 89 | [ADD_PRODUCT_SUCCESS](state, payload) { 90 | state.showLoader = false; 91 | 92 | const { product } = payload; 93 | state.products = state.products.concat(product); 94 | } 95 | }; 96 | 97 | export const cartMutations = { 98 | [ADD_TO_CART](state, payload) { 99 | const { product } = payload; 100 | state.cart.push(product); 101 | Message({ 102 | message: "恭喜你,成功加入购物车!", 103 | type: "success" 104 | }); 105 | }, 106 | [REMOVE_FROM_CART](state, payload) { 107 | const { productId } = payload; 108 | state.cart = state.cart.filter(product => product._id !== productId); 109 | Message({ 110 | message: "恭喜你,成功移除购物车!", 111 | type: "success" 112 | }); 113 | } 114 | }; 115 | 116 | export const manufacturerMutations = { 117 | [ALL_MANUFACTURERS](state) { 118 | state.showLoader = true; 119 | }, 120 | [ALL_MANUFACTURERS_SUCCESS](state, payload) { 121 | const { manufacturers } = payload; 122 | 123 | state.showLoader = false; 124 | state.manufacturers = manufacturers; 125 | }, 126 | [MANUFACTURER_BY_ID](state) { 127 | state.showLoader = true; 128 | }, 129 | [MANUFACTURER_BY_ID_SUCCESS](state, payload) { 130 | state.showLoader = false; 131 | 132 | const { manufacturer } = payload; 133 | state.manufacturer = manufacturer; 134 | }, 135 | [REMOVE_MANUFACTURER](state) { 136 | state.showLoader = true; 137 | }, 138 | [REMOVE_MANUFACTURER_SUCCESS](state, payload) { 139 | state.showLoader = false; 140 | 141 | const { manufacturerId } = payload; 142 | state.manufacturers = state.manufacturers.filter( 143 | manufacturer => manufacturer._id !== manufacturerId 144 | ); 145 | }, 146 | [UPDATE_MANUFACTURER](state) { 147 | state.showLoader = true; 148 | }, 149 | [UPDATE_MANUFACTURER_SUCCESS](state, payload) { 150 | state.showLoader = false; 151 | 152 | const { manufacturer: newManufacturer } = payload; 153 | state.manufacturers = state.manufacturers.map(manufacturer => { 154 | if (manufacturer._id === newManufacturer._id) { 155 | return newManufacturer; 156 | } 157 | return manufacturer; 158 | }); 159 | 160 | state.manufacturer = newManufacturer; 161 | }, 162 | [ADD_MANUFACTURER](state) { 163 | state.showLoader = true; 164 | }, 165 | [ADD_MANUFACTURER_SUCCESS](state, payload) { 166 | state.showLoader = false; 167 | 168 | const { manufacturer } = payload; 169 | state.manufacturers = state.manufacturers.concat(manufacturer); 170 | } 171 | }; 172 | -------------------------------------------------------------------------------- /client/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tuture-dev/vue-online-shop-frontend/f25eb7cbe23d905594334ddeede9a1e13d2d8462/client/static/.gitkeep -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | db: 5 | image: mongo 6 | restart: always 7 | environment: 8 | MONGO_INITDB_ROOT_USERNAME: mongoadmin 9 | MONGO_INITDB_ROOT_PASSWORD: secret 10 | api: 11 | image: registry.cn-shanghai.aliyuncs.com/vue-online-shop/api 12 | restart: always 13 | nginx: 14 | image: registry.cn-shanghai.aliyuncs.com/vue-online-shop/nginx 15 | restart: always 16 | ports: 17 | - 8080:80 18 | -------------------------------------------------------------------------------- /server/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | # Tuture-related files 3 | 4 | .tuture 5 | tuture-error.log 6 | tuture-build/** 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | # 指定工作目录为 /usr/src/app,接下来的命令全部在这个目录下操作 4 | WORKDIR /usr/src/app 5 | 6 | # 将 package.json 拷贝到工作目录 7 | COPY package.json . 8 | 9 | # 安装 npm 依赖 10 | RUN npm config set registry https://registry.npm.taobao.org && npm install 11 | 12 | # 拷贝源代码 13 | COPY . . 14 | 15 | # 设置环境变量 16 | ENV NODE_ENV=production 17 | ENV MONGO_URI=mongodb://db:27017/admin 18 | ENV MONGO_USER=mongoadmin 19 | ENV MONGO_PASSWORD=secret 20 | ENV HOST=0.0.0.0 21 | ENV PORT=3000 22 | 23 | # 开放 3000 端口 24 | EXPOSE 3000 25 | 26 | # 设置镜像运行命令 27 | CMD [ "node", "./bin/www" ] 28 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var logger = require('morgan'); 4 | var cookieParser = require('cookie-parser'); 5 | var bodyParser = require('body-parser'); 6 | const mongoose = require('mongoose'); 7 | 8 | var index = require('./routes/index'); 9 | var users = require('./routes/users'); 10 | const api = require('./routes/api'); 11 | 12 | var app = express(); 13 | 14 | // view engine setup 15 | app.set('views', path.join(__dirname, 'views')); 16 | app.set('view engine', 'ejs'); 17 | 18 | // Datbase connection here 19 | mongoose.connect(process.env.MONGO_URI || `mongodb://localhost:27017/test`, { 20 | useNewUrlParser: true, 21 | useUnifiedTopology: true, 22 | user: process.env.MONGO_USER, 23 | pass: process.env.MONGO_PASSWORD, 24 | }); 25 | 26 | // CORS config here 27 | app.all('/*', function(req, res, next) { 28 | // CORS headers 29 | res.header("Access-Control-Allow-Origin", "*"); // restrict it to the required domain 30 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); 31 | // Set custom headers for CORS 32 | res.header('Access-Control-Allow-Headers', 'Content-type,Accept,X-Access-Token,X-Key'); 33 | if (req.method == 'OPTIONS') { 34 | res.status(200).end(); 35 | } else { 36 | next(); 37 | } 38 | }); 39 | 40 | 41 | // uncomment after placing your favicon in /public 42 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 43 | app.use(logger('dev')); 44 | app.use(bodyParser.json()); 45 | app.use(bodyParser.urlencoded({ extended: false })); 46 | app.use(cookieParser()); 47 | app.use(express.static(path.join(__dirname, 'public'))); 48 | 49 | app.use('/', index); 50 | app.use('/users', users); 51 | app.use('/api/v1', api); 52 | 53 | // catch 404 and forward to error handler 54 | app.use(function(req, res, next) { 55 | var err = new Error('Not Found'); 56 | err.status = 404; 57 | next(err); 58 | }); 59 | 60 | // error handler 61 | app.use(function(err, req, res, next) { 62 | // set locals, only providing error in development 63 | res.locals.message = err.message; 64 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 65 | 66 | // render the error page 67 | res.status(err.status || 500); 68 | res.render('error'); 69 | }); 70 | 71 | module.exports = app; 72 | -------------------------------------------------------------------------------- /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')('api: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); 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/controllers/manufacturer.js: -------------------------------------------------------------------------------- 1 | const Model = require('../model'); 2 | const { Manufacturer } = Model; 3 | 4 | const manufacturerController = { 5 | all(req, res) { 6 | Manufacturer.find({}) 7 | .exec((err, manfacturers) => res.json(manfacturers)) 8 | }, 9 | byId(req, res) { 10 | const idParams = req.params.id; 11 | 12 | Manufacturer 13 | .findOne({ _id: idParams }) 14 | .exec((err, manufacturer) => res.json(manufacturer)); 15 | }, 16 | create(req, res) { 17 | const requestBody = req.body; 18 | const newManufacturer = new Manufacturer(requestBody); 19 | 20 | newManufacturer.save((err, saved) => { 21 | Manufacturer 22 | .findOne({ _id: newManufacturer._id }) 23 | .exec((err, manfacturer) => res.json(manfacturer)) 24 | }) 25 | }, 26 | update(req, res) { 27 | const idParams = req.params.id; 28 | let manufacturer = req.body; 29 | 30 | Manufacturer.updateOne({ _id: idParams }, { ...manufacturer }, (err, updated) => { 31 | res.json(updated); 32 | }) 33 | }, 34 | remove(req, res) { 35 | const idParams = req.params.id; 36 | 37 | Manufacturer.findOne({ _id: idParams }).remove( (err, removed) => res.json(idParams) ) 38 | } 39 | } 40 | 41 | module.exports = manufacturerController; -------------------------------------------------------------------------------- /server/controllers/product.js: -------------------------------------------------------------------------------- 1 | const Model = require("../model"); 2 | const { Product } = Model; 3 | 4 | const productController = { 5 | all(req, res) { 6 | Product.find({}) 7 | .populate("manufacturer") 8 | .exec((err, products) => res.json(products)); 9 | }, 10 | byId(req, res) { 11 | const idParams = req.params.id; 12 | 13 | Product.findOne({ _id: idParams }) 14 | .populate("manufacturer") 15 | .exec((err, product) => res.json(product)); 16 | }, 17 | create(req, res) { 18 | const requestBody = req.body; 19 | const newProduct = new Product(requestBody); 20 | 21 | newProduct.save((err, saved) => { 22 | Product.findOne({ _id: newProduct._id }) 23 | .populate("manufacturer") 24 | .exec((err, product) => res.json(product)); 25 | }); 26 | }, 27 | update(req, res) { 28 | const idParams = req.params.id; 29 | const product = req.body; 30 | 31 | Product.updateOne({ _id: idParams }, { ...product }, (err, updated) => { 32 | res.json(updated); 33 | }); 34 | }, 35 | remove(req, res) { 36 | const idParams = req.params.id; 37 | 38 | Product.findOne({ _id: idParams }).remove((err, removed) => 39 | res.json(idParams) 40 | ); 41 | }, 42 | }; 43 | 44 | module.exports = productController; 45 | -------------------------------------------------------------------------------- /server/data/manufacturers.json: -------------------------------------------------------------------------------- 1 | {"_id":{"$oid":"5e194b192dfe031505deaf62"},"name":"Apple Inc","__v":0} 2 | {"_id":{"$oid":"5e194b252dfe031505deaf63"},"name":"华为","__v":0} 3 | {"_id":{"$oid":"5e194b2d2dfe031505deaf64"},"name":"小米","__v":0} 4 | {"_id":{"$oid":"5e194b352dfe031505deaf65"},"name":"Vivo","__v":0} 5 | {"_id":{"$oid":"5e194b402dfe031505deaf66"},"name":"OPPO","__v":0} 6 | -------------------------------------------------------------------------------- /server/data/products.json: -------------------------------------------------------------------------------- 1 | {"_id":{"$oid":"5e194c1b2dfe031505deaf67"},"name":"iPhone","description":"iPhone是美国苹果公司研发的智能手机系列,搭载苹果公司研发的iOS操作系统","image":"https://i.gadgets360cdn.com/large/iPhone11_leak_1567592422045.jpg","price":2000,"manufacturer":{"$oid":"5e194b192dfe031505deaf62"},"__v":0} 2 | {"_id":{"$oid":"5e194c532dfe031505deaf68"},"name":"荣耀20","description":"李现同款 4800万超广角AI四摄 3200W美颜自拍 麒麟Kirin980全网通版8GB+128GB 蓝水翡翠 全面屏手机","image":"https://article-fd.zol-img.com.cn/t_s640x2000/g4/M08/0E/0E/ChMlzF2myueILMN_AAGSPzoz23wAAYJ3QADttsAAZJX090.jpg","price":2499,"manufacturer":{"$oid":"5e194b252dfe031505deaf63"},"__v":0} 3 | {"_id":{"$oid":"5e194c762dfe031505deaf69"},"name":"MIX2S","description":"骁龙845 全面屏NFC 游戏智能拍照手机 白色 全网通 6+128","image":"http://himg2.huanqiu.com/attachment2010/2018/0129/08/39/20180129083933823.jpg","price":1688,"manufacturer":{"$oid":"5e194b2d2dfe031505deaf64"},"__v":0} 4 | {"_id":{"$oid":"5e194cc42dfe031505deaf6a"},"name":"Reno2","description":"【12期免息1年碎屏险】4800万变焦四摄8+128G防抖6.5英寸全面屏新 深海夜光(8GB+128GB) 官方标配","image":"https://news.maxabout.com/wp-content/uploads/2019/08/OPPO-Reno-2-1.jpg","price":2999,"manufacturer":{"$oid":"5e194b402dfe031505deaf66"},"__v":0} 5 | -------------------------------------------------------------------------------- /server/model/index.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | const model = mongoose.model.bind(mongoose); 4 | const ObjectId = mongoose.Schema.Types.ObjectId; 5 | 6 | const productSchema = Schema({ 7 | id: ObjectId, 8 | name: String, 9 | image: String, 10 | price: String, 11 | description: String, 12 | user: String, 13 | manufacturer: { type: ObjectId, ref: "Manufacturer" }, 14 | }); 15 | 16 | const manufacturerSchema = Schema({ 17 | id: ObjectId, 18 | name: String, 19 | user: String, 20 | }); 21 | 22 | const Product = model("Product", productSchema); 23 | const Manufacturer = model("Manufacturer", manufacturerSchema); 24 | 25 | module.exports = { Product, Manufacturer }; 26 | -------------------------------------------------------------------------------- /server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "0.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz", 10 | "integrity": "sha1-UxvHJlF6OytB+FACHGzBXqq1B80=", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "http://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "basic-auth": { 22 | "version": "2.0.1", 23 | "resolved": "https://registry.npm.taobao.org/basic-auth/download/basic-auth-2.0.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbasic-auth%2Fdownload%2Fbasic-auth-2.0.1.tgz", 24 | "integrity": "sha1-uZgnm/R844NEtPPPkW1Gebv1Hjo=", 25 | "requires": { 26 | "safe-buffer": "5.1.2" 27 | } 28 | }, 29 | "bluebird": { 30 | "version": "3.5.1", 31 | "resolved": "https://registry.npm.taobao.org/bluebird/download/bluebird-3.5.1.tgz?cache=0&sync_timestamp=1569957137990&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbluebird%2Fdownload%2Fbluebird-3.5.1.tgz", 32 | "integrity": "sha1-2VUfnemPH82h5oPRfukaBgLuLrk=" 33 | }, 34 | "body-parser": { 35 | "version": "1.18.3", 36 | "resolved": "https://registry.npm.taobao.org/body-parser/download/body-parser-1.18.3.tgz", 37 | "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", 38 | "requires": { 39 | "bytes": "3.0.0", 40 | "content-type": "~1.0.4", 41 | "debug": "2.6.9", 42 | "depd": "~1.1.2", 43 | "http-errors": "~1.6.3", 44 | "iconv-lite": "0.4.23", 45 | "on-finished": "~2.3.0", 46 | "qs": "6.5.2", 47 | "raw-body": "2.3.3", 48 | "type-is": "~1.6.16" 49 | } 50 | }, 51 | "bson": { 52 | "version": "1.1.1", 53 | "resolved": "https://registry.npm.taobao.org/bson/download/bson-1.1.1.tgz", 54 | "integrity": "sha1-QzD16ZEExOdR5zUYWeLUCCefLxM=" 55 | }, 56 | "bytes": { 57 | "version": "3.0.0", 58 | "resolved": "http://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz", 59 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 60 | }, 61 | "content-disposition": { 62 | "version": "0.5.2", 63 | "resolved": "http://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.2.tgz", 64 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 65 | }, 66 | "content-type": { 67 | "version": "1.0.4", 68 | "resolved": "http://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz", 69 | "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" 70 | }, 71 | "cookie": { 72 | "version": "0.3.1", 73 | "resolved": "https://registry.npm.taobao.org/cookie/download/cookie-0.3.1.tgz", 74 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 75 | }, 76 | "cookie-parser": { 77 | "version": "1.4.4", 78 | "resolved": "https://registry.npm.taobao.org/cookie-parser/download/cookie-parser-1.4.4.tgz", 79 | "integrity": "sha1-5jY95OqYw975aXuTQhwJ8wz10Yg=", 80 | "requires": { 81 | "cookie": "0.3.1", 82 | "cookie-signature": "1.0.6" 83 | } 84 | }, 85 | "cookie-signature": { 86 | "version": "1.0.6", 87 | "resolved": "http://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz", 88 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 89 | }, 90 | "debug": { 91 | "version": "2.6.9", 92 | "resolved": "http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz", 93 | "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", 94 | "requires": { 95 | "ms": "2.0.0" 96 | } 97 | }, 98 | "depd": { 99 | "version": "1.1.2", 100 | "resolved": "http://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz", 101 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 102 | }, 103 | "destroy": { 104 | "version": "1.0.4", 105 | "resolved": "http://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz", 106 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 107 | }, 108 | "ee-first": { 109 | "version": "1.1.1", 110 | "resolved": "http://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz", 111 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 112 | }, 113 | "ejs": { 114 | "version": "2.5.9", 115 | "resolved": "https://registry.npm.taobao.org/ejs/download/ejs-2.5.9.tgz", 116 | "integrity": "sha1-e6JUWCpWDSZ0NxCaaDVBEkdbDOU=" 117 | }, 118 | "encodeurl": { 119 | "version": "1.0.2", 120 | "resolved": "http://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz", 121 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 122 | }, 123 | "escape-html": { 124 | "version": "1.0.3", 125 | "resolved": "http://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz", 126 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 127 | }, 128 | "etag": { 129 | "version": "1.8.1", 130 | "resolved": "http://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz", 131 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 132 | }, 133 | "express": { 134 | "version": "4.15.5", 135 | "resolved": "https://registry.npm.taobao.org/express/download/express-4.15.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fexpress%2Fdownload%2Fexpress-4.15.5.tgz", 136 | "integrity": "sha1-ZwI1ypWYiQpa6BcLg9tyK4Qu2Sc=", 137 | "requires": { 138 | "accepts": "~1.3.3", 139 | "array-flatten": "1.1.1", 140 | "content-disposition": "0.5.2", 141 | "content-type": "~1.0.2", 142 | "cookie": "0.3.1", 143 | "cookie-signature": "1.0.6", 144 | "debug": "2.6.9", 145 | "depd": "~1.1.1", 146 | "encodeurl": "~1.0.1", 147 | "escape-html": "~1.0.3", 148 | "etag": "~1.8.0", 149 | "finalhandler": "~1.0.6", 150 | "fresh": "0.5.2", 151 | "merge-descriptors": "1.0.1", 152 | "methods": "~1.1.2", 153 | "on-finished": "~2.3.0", 154 | "parseurl": "~1.3.1", 155 | "path-to-regexp": "0.1.7", 156 | "proxy-addr": "~1.1.5", 157 | "qs": "6.5.0", 158 | "range-parser": "~1.2.0", 159 | "send": "0.15.6", 160 | "serve-static": "1.12.6", 161 | "setprototypeof": "1.0.3", 162 | "statuses": "~1.3.1", 163 | "type-is": "~1.6.15", 164 | "utils-merge": "1.0.0", 165 | "vary": "~1.1.1" 166 | }, 167 | "dependencies": { 168 | "qs": { 169 | "version": "6.5.0", 170 | "resolved": "https://registry.npm.taobao.org/qs/download/qs-6.5.0.tgz?cache=0&sync_timestamp=1569207136481&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fqs%2Fdownload%2Fqs-6.5.0.tgz", 171 | "integrity": "sha1-jQSVTTZN7z78VbWgeT4eLIsebkk=" 172 | }, 173 | "setprototypeof": { 174 | "version": "1.0.3", 175 | "resolved": "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.0.3.tgz?cache=0&sync_timestamp=1563425414995&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsetprototypeof%2Fdownload%2Fsetprototypeof-1.0.3.tgz", 176 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 177 | }, 178 | "statuses": { 179 | "version": "1.3.1", 180 | "resolved": "http://registry.npm.taobao.org/statuses/download/statuses-1.3.1.tgz", 181 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 182 | } 183 | } 184 | }, 185 | "finalhandler": { 186 | "version": "1.0.6", 187 | "resolved": "https://registry.npm.taobao.org/finalhandler/download/finalhandler-1.0.6.tgz", 188 | "integrity": "sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8=", 189 | "requires": { 190 | "debug": "2.6.9", 191 | "encodeurl": "~1.0.1", 192 | "escape-html": "~1.0.3", 193 | "on-finished": "~2.3.0", 194 | "parseurl": "~1.3.2", 195 | "statuses": "~1.3.1", 196 | "unpipe": "~1.0.0" 197 | }, 198 | "dependencies": { 199 | "statuses": { 200 | "version": "1.3.1", 201 | "resolved": "http://registry.npm.taobao.org/statuses/download/statuses-1.3.1.tgz", 202 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 203 | } 204 | } 205 | }, 206 | "forwarded": { 207 | "version": "0.1.2", 208 | "resolved": "http://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz", 209 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 210 | }, 211 | "fresh": { 212 | "version": "0.5.2", 213 | "resolved": "http://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz", 214 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 215 | }, 216 | "http-errors": { 217 | "version": "1.6.3", 218 | "resolved": "https://registry.npm.taobao.org/http-errors/download/http-errors-1.6.3.tgz", 219 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 220 | "requires": { 221 | "depd": "~1.1.2", 222 | "inherits": "2.0.3", 223 | "setprototypeof": "1.1.0", 224 | "statuses": ">= 1.4.0 < 2" 225 | } 226 | }, 227 | "iconv-lite": { 228 | "version": "0.4.23", 229 | "resolved": "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.23.tgz?cache=0&sync_timestamp=1561588160612&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ficonv-lite%2Fdownload%2Ficonv-lite-0.4.23.tgz", 230 | "integrity": "sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM=", 231 | "requires": { 232 | "safer-buffer": ">= 2.1.2 < 3" 233 | } 234 | }, 235 | "inherits": { 236 | "version": "2.0.3", 237 | "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz?cache=0&sync_timestamp=1560975547815&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.3.tgz", 238 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 239 | }, 240 | "ipaddr.js": { 241 | "version": "1.4.0", 242 | "resolved": "https://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.4.0.tgz", 243 | "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=" 244 | }, 245 | "kareem": { 246 | "version": "2.3.1", 247 | "resolved": "https://registry.npm.taobao.org/kareem/download/kareem-2.3.1.tgz", 248 | "integrity": "sha1-3vEtnJQQF/q/sA+HOvlenJnhvoc=" 249 | }, 250 | "media-typer": { 251 | "version": "0.3.0", 252 | "resolved": "https://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz", 253 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 254 | }, 255 | "merge-descriptors": { 256 | "version": "1.0.1", 257 | "resolved": "http://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz", 258 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 259 | }, 260 | "methods": { 261 | "version": "1.1.2", 262 | "resolved": "http://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz", 263 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 264 | }, 265 | "mime": { 266 | "version": "1.3.4", 267 | "resolved": "https://registry.npm.taobao.org/mime/download/mime-1.3.4.tgz", 268 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 269 | }, 270 | "mime-db": { 271 | "version": "1.40.0", 272 | "resolved": "https://registry.npm.taobao.org/mime-db/download/mime-db-1.40.0.tgz", 273 | "integrity": "sha1-plBX6ZjbCQ9zKmj2wnbTh9QSbDI=" 274 | }, 275 | "mime-types": { 276 | "version": "2.1.24", 277 | "resolved": "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.24.tgz", 278 | "integrity": "sha1-tvjQs+lR77d97eyhlM/20W9nb4E=", 279 | "requires": { 280 | "mime-db": "1.40.0" 281 | } 282 | }, 283 | "mongodb": { 284 | "version": "3.3.2", 285 | "resolved": "https://registry.npm.taobao.org/mongodb/download/mongodb-3.3.2.tgz", 286 | "integrity": "sha1-/whrX1Us8H4kzgmGlCEPPULWaLI=", 287 | "requires": { 288 | "bson": "^1.1.1", 289 | "require_optional": "^1.0.1", 290 | "safe-buffer": "^5.1.2" 291 | } 292 | }, 293 | "mongoose": { 294 | "version": "5.7.5", 295 | "resolved": "https://registry.npm.taobao.org/mongoose/download/mongoose-5.7.5.tgz?cache=0&sync_timestamp=1571064159828&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmongoose%2Fdownload%2Fmongoose-5.7.5.tgz", 296 | "integrity": "sha1-t4e0chbt9iA2qjWMPvDxhpxGzcI=", 297 | "requires": { 298 | "bson": "~1.1.1", 299 | "kareem": "2.3.1", 300 | "mongodb": "3.3.2", 301 | "mongoose-legacy-pluralize": "1.0.2", 302 | "mpath": "0.6.0", 303 | "mquery": "3.2.2", 304 | "ms": "2.1.2", 305 | "regexp-clone": "1.0.0", 306 | "safe-buffer": "5.1.2", 307 | "sift": "7.0.1", 308 | "sliced": "1.0.1" 309 | }, 310 | "dependencies": { 311 | "ms": { 312 | "version": "2.1.2", 313 | "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.1.2.tgz", 314 | "integrity": "sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk=" 315 | } 316 | } 317 | }, 318 | "mongoose-legacy-pluralize": { 319 | "version": "1.0.2", 320 | "resolved": "http://registry.npm.taobao.org/mongoose-legacy-pluralize/download/mongoose-legacy-pluralize-1.0.2.tgz", 321 | "integrity": "sha1-O6n5H6UHtRhtOZ+0CFS/8Y+1Y+Q=" 322 | }, 323 | "morgan": { 324 | "version": "1.9.1", 325 | "resolved": "https://registry.npm.taobao.org/morgan/download/morgan-1.9.1.tgz", 326 | "integrity": "sha1-Co0Wc0odmvvIJLmd+H5zjlji2lk=", 327 | "requires": { 328 | "basic-auth": "~2.0.0", 329 | "debug": "2.6.9", 330 | "depd": "~1.1.2", 331 | "on-finished": "~2.3.0", 332 | "on-headers": "~1.0.1" 333 | } 334 | }, 335 | "mpath": { 336 | "version": "0.6.0", 337 | "resolved": "https://registry.npm.taobao.org/mpath/download/mpath-0.6.0.tgz", 338 | "integrity": "sha1-qpIgKfyk8PZB82DnTFwbakxHB44=" 339 | }, 340 | "mquery": { 341 | "version": "3.2.2", 342 | "resolved": "https://registry.npm.taobao.org/mquery/download/mquery-3.2.2.tgz", 343 | "integrity": "sha1-4Tg6OVGFLOI+N/YZqbNQ8fs2ZOc=", 344 | "requires": { 345 | "bluebird": "3.5.1", 346 | "debug": "3.1.0", 347 | "regexp-clone": "^1.0.0", 348 | "safe-buffer": "5.1.2", 349 | "sliced": "1.0.1" 350 | }, 351 | "dependencies": { 352 | "debug": { 353 | "version": "3.1.0", 354 | "resolved": "http://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz", 355 | "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", 356 | "requires": { 357 | "ms": "2.0.0" 358 | } 359 | } 360 | } 361 | }, 362 | "ms": { 363 | "version": "2.0.0", 364 | "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.0.0.tgz", 365 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 366 | }, 367 | "negotiator": { 368 | "version": "0.6.2", 369 | "resolved": "https://registry.npm.taobao.org/negotiator/download/negotiator-0.6.2.tgz", 370 | "integrity": "sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs=" 371 | }, 372 | "on-finished": { 373 | "version": "2.3.0", 374 | "resolved": "http://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz", 375 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 376 | "requires": { 377 | "ee-first": "1.1.1" 378 | } 379 | }, 380 | "on-headers": { 381 | "version": "1.0.2", 382 | "resolved": "http://registry.npm.taobao.org/on-headers/download/on-headers-1.0.2.tgz", 383 | "integrity": "sha1-dysK5qqlJcOZ5Imt+tkMQD6zwo8=" 384 | }, 385 | "parseurl": { 386 | "version": "1.3.3", 387 | "resolved": "https://registry.npm.taobao.org/parseurl/download/parseurl-1.3.3.tgz", 388 | "integrity": "sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ=" 389 | }, 390 | "path-to-regexp": { 391 | "version": "0.1.7", 392 | "resolved": "https://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpath-to-regexp%2Fdownload%2Fpath-to-regexp-0.1.7.tgz", 393 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 394 | }, 395 | "proxy-addr": { 396 | "version": "1.1.5", 397 | "resolved": "https://registry.npm.taobao.org/proxy-addr/download/proxy-addr-1.1.5.tgz", 398 | "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", 399 | "requires": { 400 | "forwarded": "~0.1.0", 401 | "ipaddr.js": "1.4.0" 402 | } 403 | }, 404 | "qs": { 405 | "version": "6.5.2", 406 | "resolved": "https://registry.npm.taobao.org/qs/download/qs-6.5.2.tgz?cache=0&sync_timestamp=1569207136481&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fqs%2Fdownload%2Fqs-6.5.2.tgz", 407 | "integrity": "sha1-yzroBuh0BERYTvFUzo7pjUA/PjY=" 408 | }, 409 | "range-parser": { 410 | "version": "1.2.1", 411 | "resolved": "https://registry.npm.taobao.org/range-parser/download/range-parser-1.2.1.tgz", 412 | "integrity": "sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE=" 413 | }, 414 | "raw-body": { 415 | "version": "2.3.3", 416 | "resolved": "https://registry.npm.taobao.org/raw-body/download/raw-body-2.3.3.tgz", 417 | "integrity": "sha1-GzJOzmtXBuFThVvBFIxlu39uoMM=", 418 | "requires": { 419 | "bytes": "3.0.0", 420 | "http-errors": "1.6.3", 421 | "iconv-lite": "0.4.23", 422 | "unpipe": "1.0.0" 423 | } 424 | }, 425 | "regexp-clone": { 426 | "version": "1.0.0", 427 | "resolved": "https://registry.npm.taobao.org/regexp-clone/download/regexp-clone-1.0.0.tgz", 428 | "integrity": "sha1-Ii25Z2IydwViYLmSYmNUoEzpv2M=" 429 | }, 430 | "require_optional": { 431 | "version": "1.0.1", 432 | "resolved": "http://registry.npm.taobao.org/require_optional/download/require_optional-1.0.1.tgz", 433 | "integrity": "sha1-TPNaQkf2TKPfjC7yCMxJSxyo/C4=", 434 | "requires": { 435 | "resolve-from": "^2.0.0", 436 | "semver": "^5.1.0" 437 | } 438 | }, 439 | "resolve-from": { 440 | "version": "2.0.0", 441 | "resolved": "https://registry.npm.taobao.org/resolve-from/download/resolve-from-2.0.0.tgz", 442 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 443 | }, 444 | "safe-buffer": { 445 | "version": "5.1.2", 446 | "resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz", 447 | "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" 448 | }, 449 | "safer-buffer": { 450 | "version": "2.1.2", 451 | "resolved": "http://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz", 452 | "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" 453 | }, 454 | "semver": { 455 | "version": "5.7.1", 456 | "resolved": "https://registry.npm.taobao.org/semver/download/semver-5.7.1.tgz?cache=0&sync_timestamp=1565627367398&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-5.7.1.tgz", 457 | "integrity": "sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=" 458 | }, 459 | "send": { 460 | "version": "0.15.6", 461 | "resolved": "https://registry.npm.taobao.org/send/download/send-0.15.6.tgz", 462 | "integrity": "sha1-IPI6nJJbdiq4JwX+L52yUqzkfjQ=", 463 | "requires": { 464 | "debug": "2.6.9", 465 | "depd": "~1.1.1", 466 | "destroy": "~1.0.4", 467 | "encodeurl": "~1.0.1", 468 | "escape-html": "~1.0.3", 469 | "etag": "~1.8.1", 470 | "fresh": "0.5.2", 471 | "http-errors": "~1.6.2", 472 | "mime": "1.3.4", 473 | "ms": "2.0.0", 474 | "on-finished": "~2.3.0", 475 | "range-parser": "~1.2.0", 476 | "statuses": "~1.3.1" 477 | }, 478 | "dependencies": { 479 | "statuses": { 480 | "version": "1.3.1", 481 | "resolved": "http://registry.npm.taobao.org/statuses/download/statuses-1.3.1.tgz", 482 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 483 | } 484 | } 485 | }, 486 | "serve-favicon": { 487 | "version": "2.4.5", 488 | "resolved": "https://registry.npm.taobao.org/serve-favicon/download/serve-favicon-2.4.5.tgz", 489 | "integrity": "sha1-SdmkaGMVOpJAaRyJPSsOfYXW1DY=", 490 | "requires": { 491 | "etag": "~1.8.1", 492 | "fresh": "0.5.2", 493 | "ms": "2.0.0", 494 | "parseurl": "~1.3.2", 495 | "safe-buffer": "5.1.1" 496 | }, 497 | "dependencies": { 498 | "safe-buffer": { 499 | "version": "5.1.1", 500 | "resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.1.tgz", 501 | "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" 502 | } 503 | } 504 | }, 505 | "serve-static": { 506 | "version": "1.12.6", 507 | "resolved": "https://registry.npm.taobao.org/serve-static/download/serve-static-1.12.6.tgz", 508 | "integrity": "sha1-uXN3P2NEmTTaVOW+ul4x2fQhFXc=", 509 | "requires": { 510 | "encodeurl": "~1.0.1", 511 | "escape-html": "~1.0.3", 512 | "parseurl": "~1.3.2", 513 | "send": "0.15.6" 514 | } 515 | }, 516 | "setprototypeof": { 517 | "version": "1.1.0", 518 | "resolved": "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.0.tgz?cache=0&sync_timestamp=1563425414995&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsetprototypeof%2Fdownload%2Fsetprototypeof-1.1.0.tgz", 519 | "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=" 520 | }, 521 | "sift": { 522 | "version": "7.0.1", 523 | "resolved": "https://registry.npm.taobao.org/sift/download/sift-7.0.1.tgz", 524 | "integrity": "sha1-R9YsULFZ0xbxNy+LU/nBDNIaSwg=" 525 | }, 526 | "sliced": { 527 | "version": "1.0.1", 528 | "resolved": "http://registry.npm.taobao.org/sliced/download/sliced-1.0.1.tgz", 529 | "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" 530 | }, 531 | "statuses": { 532 | "version": "1.5.0", 533 | "resolved": "http://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz", 534 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 535 | }, 536 | "type-is": { 537 | "version": "1.6.18", 538 | "resolved": "https://registry.npm.taobao.org/type-is/download/type-is-1.6.18.tgz", 539 | "integrity": "sha1-TlUs0F3wlGfcvE73Od6J8s83wTE=", 540 | "requires": { 541 | "media-typer": "0.3.0", 542 | "mime-types": "~2.1.24" 543 | } 544 | }, 545 | "unpipe": { 546 | "version": "1.0.0", 547 | "resolved": "http://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz", 548 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 549 | }, 550 | "utils-merge": { 551 | "version": "1.0.0", 552 | "resolved": "http://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.0.tgz", 553 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 554 | }, 555 | "vary": { 556 | "version": "1.1.2", 557 | "resolved": "http://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz", 558 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 559 | } 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.18.2", 10 | "cookie-parser": "~1.4.3", 11 | "debug": "~2.6.9", 12 | "ejs": "~2.5.7", 13 | "express": "~4.15.5", 14 | "mongoose": "^5.7.5", 15 | "morgan": "~1.9.0", 16 | "serve-favicon": "~2.4.5" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /server/routes/api/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const productController = require('../../controllers/product'); 4 | const manufacturerController = require('../../controllers/manufacturer'); 5 | 6 | router.get('/manufacturers', manufacturerController.all); 7 | router.get('/manufacturers/:id', manufacturerController.byId); 8 | router.post('/manufacturers', manufacturerController.create); 9 | router.put('/manufacturers/:id', manufacturerController.update); 10 | router.delete('/manufacturers/:id', manufacturerController.remove); 11 | 12 | router.get('/products', productController.all); 13 | router.get('/products/:id', productController.byId); 14 | router.post('/products', productController.create); 15 | router.put('/products/:id', productController.update); 16 | router.delete('/products/:id', productController.remove); 17 | 18 | module.exports = router; -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: 'Express' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /server/routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res, next) { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /server/views/error.ejs: -------------------------------------------------------------------------------- 1 |

<%= message %>

2 |

<%= error.status %>

3 |
<%= error.stack %>
4 | -------------------------------------------------------------------------------- /server/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 |

<%= title %>

9 |

Welcome to <%= title %>

10 | 11 | 12 | -------------------------------------------------------------------------------- /server/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@~1.3.3: 6 | version "1.3.7" 7 | resolved "https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 8 | integrity sha1-UxvHJlF6OytB+FACHGzBXqq1B80= 9 | dependencies: 10 | mime-types "~2.1.24" 11 | negotiator "0.6.2" 12 | 13 | array-flatten@1.1.1: 14 | version "1.1.1" 15 | resolved "https://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz?cache=0&sync_timestamp=1574313315299&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Farray-flatten%2Fdownload%2Farray-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 16 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 17 | 18 | basic-auth@~2.0.0: 19 | version "2.0.1" 20 | resolved "https://registry.npm.taobao.org/basic-auth/download/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" 21 | integrity sha1-uZgnm/R844NEtPPPkW1Gebv1Hjo= 22 | dependencies: 23 | safe-buffer "5.1.2" 24 | 25 | bl@^2.2.0: 26 | version "2.2.0" 27 | resolved "https://registry.npm.taobao.org/bl/download/bl-2.2.0.tgz?cache=0&sync_timestamp=1583337750539&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbl%2Fdownload%2Fbl-2.2.0.tgz#e1a574cdf528e4053019bb800b041c0ac88da493" 28 | integrity sha1-4aV0zfUo5AUwGbuACwQcCsiNpJM= 29 | dependencies: 30 | readable-stream "^2.3.5" 31 | safe-buffer "^5.1.1" 32 | 33 | bluebird@3.5.1: 34 | version "3.5.1" 35 | resolved "https://registry.npm.taobao.org/bluebird/download/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" 36 | integrity sha1-2VUfnemPH82h5oPRfukaBgLuLrk= 37 | 38 | body-parser@~1.18.2: 39 | version "1.18.3" 40 | resolved "https://registry.npm.taobao.org/body-parser/download/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" 41 | integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= 42 | dependencies: 43 | bytes "3.0.0" 44 | content-type "~1.0.4" 45 | debug "2.6.9" 46 | depd "~1.1.2" 47 | http-errors "~1.6.3" 48 | iconv-lite "0.4.23" 49 | on-finished "~2.3.0" 50 | qs "6.5.2" 51 | raw-body "2.3.3" 52 | type-is "~1.6.16" 53 | 54 | bson@^1.1.1, bson@~1.1.1: 55 | version "1.1.3" 56 | resolved "https://registry.npm.taobao.org/bson/download/bson-1.1.3.tgz?cache=0&sync_timestamp=1578577060818&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbson%2Fdownload%2Fbson-1.1.3.tgz#aa82cb91f9a453aaa060d6209d0675114a8154d3" 57 | integrity sha1-qoLLkfmkU6qgYNYgnQZ1EUqBVNM= 58 | 59 | bytes@3.0.0: 60 | version "3.0.0" 61 | resolved "https://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" 62 | integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= 63 | 64 | content-disposition@0.5.2: 65 | version "0.5.2" 66 | resolved "https://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 67 | integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= 68 | 69 | content-type@~1.0.2, content-type@~1.0.4: 70 | version "1.0.4" 71 | resolved "https://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 72 | integrity sha1-4TjMdeBAxyexlm/l5fjJruJW/js= 73 | 74 | cookie-parser@~1.4.3: 75 | version "1.4.4" 76 | resolved "https://registry.npm.taobao.org/cookie-parser/download/cookie-parser-1.4.4.tgz#e6363de4ea98c3def9697b93421c09f30cf5d188" 77 | integrity sha1-5jY95OqYw975aXuTQhwJ8wz10Yg= 78 | dependencies: 79 | cookie "0.3.1" 80 | cookie-signature "1.0.6" 81 | 82 | cookie-signature@1.0.6: 83 | version "1.0.6" 84 | resolved "https://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 85 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 86 | 87 | cookie@0.3.1: 88 | version "0.3.1" 89 | resolved "https://registry.npm.taobao.org/cookie/download/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 90 | integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= 91 | 92 | core-util-is@~1.0.0: 93 | version "1.0.2" 94 | resolved "https://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 95 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 96 | 97 | debug@2.6.9, debug@~2.6.9: 98 | version "2.6.9" 99 | resolved "https://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 100 | integrity sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8= 101 | dependencies: 102 | ms "2.0.0" 103 | 104 | debug@3.1.0: 105 | version "3.1.0" 106 | resolved "https://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" 107 | integrity sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE= 108 | dependencies: 109 | ms "2.0.0" 110 | 111 | denque@^1.4.1: 112 | version "1.4.1" 113 | resolved "https://registry.npm.taobao.org/denque/download/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" 114 | integrity sha1-Z0T/dkHBSMP4ppwwflEjXB9KN88= 115 | 116 | depd@~1.1.1, depd@~1.1.2: 117 | version "1.1.2" 118 | resolved "https://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 119 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 120 | 121 | destroy@~1.0.4: 122 | version "1.0.4" 123 | resolved "https://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 124 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 125 | 126 | ee-first@1.1.1: 127 | version "1.1.1" 128 | resolved "https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 129 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 130 | 131 | ejs@~2.5.7: 132 | version "2.5.9" 133 | resolved "https://registry.npm.taobao.org/ejs/download/ejs-2.5.9.tgz#7ba254582a560d267437109a68354112475b0ce5" 134 | integrity sha1-e6JUWCpWDSZ0NxCaaDVBEkdbDOU= 135 | 136 | encodeurl@~1.0.1: 137 | version "1.0.2" 138 | resolved "https://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 139 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 140 | 141 | escape-html@~1.0.3: 142 | version "1.0.3" 143 | resolved "https://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 144 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 145 | 146 | etag@~1.8.0, etag@~1.8.1: 147 | version "1.8.1" 148 | resolved "https://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 149 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 150 | 151 | express@~4.15.5: 152 | version "4.15.5" 153 | resolved "https://registry.npm.taobao.org/express/download/express-4.15.5.tgz#670235ca9598890a5ae8170b83db722b842ed927" 154 | integrity sha1-ZwI1ypWYiQpa6BcLg9tyK4Qu2Sc= 155 | dependencies: 156 | accepts "~1.3.3" 157 | array-flatten "1.1.1" 158 | content-disposition "0.5.2" 159 | content-type "~1.0.2" 160 | cookie "0.3.1" 161 | cookie-signature "1.0.6" 162 | debug "2.6.9" 163 | depd "~1.1.1" 164 | encodeurl "~1.0.1" 165 | escape-html "~1.0.3" 166 | etag "~1.8.0" 167 | finalhandler "~1.0.6" 168 | fresh "0.5.2" 169 | merge-descriptors "1.0.1" 170 | methods "~1.1.2" 171 | on-finished "~2.3.0" 172 | parseurl "~1.3.1" 173 | path-to-regexp "0.1.7" 174 | proxy-addr "~1.1.5" 175 | qs "6.5.0" 176 | range-parser "~1.2.0" 177 | send "0.15.6" 178 | serve-static "1.12.6" 179 | setprototypeof "1.0.3" 180 | statuses "~1.3.1" 181 | type-is "~1.6.15" 182 | utils-merge "1.0.0" 183 | vary "~1.1.1" 184 | 185 | finalhandler@~1.0.6: 186 | version "1.0.6" 187 | resolved "https://registry.npm.taobao.org/finalhandler/download/finalhandler-1.0.6.tgz#007aea33d1a4d3e42017f624848ad58d212f814f" 188 | integrity sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8= 189 | dependencies: 190 | debug "2.6.9" 191 | encodeurl "~1.0.1" 192 | escape-html "~1.0.3" 193 | on-finished "~2.3.0" 194 | parseurl "~1.3.2" 195 | statuses "~1.3.1" 196 | unpipe "~1.0.0" 197 | 198 | forwarded@~0.1.0: 199 | version "0.1.2" 200 | resolved "https://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 201 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= 202 | 203 | fresh@0.5.2: 204 | version "0.5.2" 205 | resolved "https://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 206 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 207 | 208 | http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: 209 | version "1.6.3" 210 | resolved "https://registry.npm.taobao.org/http-errors/download/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" 211 | integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= 212 | dependencies: 213 | depd "~1.1.2" 214 | inherits "2.0.3" 215 | setprototypeof "1.1.0" 216 | statuses ">= 1.4.0 < 2" 217 | 218 | iconv-lite@0.4.23: 219 | version "0.4.23" 220 | resolved "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.23.tgz?cache=0&sync_timestamp=1579333981154&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ficonv-lite%2Fdownload%2Ficonv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" 221 | integrity sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM= 222 | dependencies: 223 | safer-buffer ">= 2.1.2 < 3" 224 | 225 | inherits@2.0.3: 226 | version "2.0.3" 227 | resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 228 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 229 | 230 | inherits@~2.0.3: 231 | version "2.0.4" 232 | resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 233 | integrity sha1-D6LGT5MpF8NDOg3tVTY6rjdBa3w= 234 | 235 | ipaddr.js@1.4.0: 236 | version "1.4.0" 237 | resolved "https://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0" 238 | integrity sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA= 239 | 240 | isarray@~1.0.0: 241 | version "1.0.0" 242 | resolved "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 243 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 244 | 245 | kareem@2.3.1: 246 | version "2.3.1" 247 | resolved "https://registry.npm.taobao.org/kareem/download/kareem-2.3.1.tgz#def12d9c941017fabfb00f873af95e9c99e1be87" 248 | integrity sha1-3vEtnJQQF/q/sA+HOvlenJnhvoc= 249 | 250 | media-typer@0.3.0: 251 | version "0.3.0" 252 | resolved "https://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 253 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 254 | 255 | memory-pager@^1.0.2: 256 | version "1.5.0" 257 | resolved "https://registry.npm.taobao.org/memory-pager/download/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" 258 | integrity sha1-2HUWVdItOEaCdByXLyw9bfo+ZrU= 259 | 260 | merge-descriptors@1.0.1: 261 | version "1.0.1" 262 | resolved "https://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 263 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 264 | 265 | methods@~1.1.2: 266 | version "1.1.2" 267 | resolved "https://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 268 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 269 | 270 | mime-db@1.43.0: 271 | version "1.43.0" 272 | resolved "https://registry.npm.taobao.org/mime-db/download/mime-db-1.43.0.tgz?cache=0&sync_timestamp=1578281193492&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-db%2Fdownload%2Fmime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" 273 | integrity sha1-ChLgUCZQ5HPXNVNQUOfI9OtPrlg= 274 | 275 | mime-types@~2.1.24: 276 | version "2.1.26" 277 | resolved "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.26.tgz?cache=0&sync_timestamp=1578282566609&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-types%2Fdownload%2Fmime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" 278 | integrity sha1-nJIfwJt+FJpl39wNpNIJlyALCgY= 279 | dependencies: 280 | mime-db "1.43.0" 281 | 282 | mime@1.3.4: 283 | version "1.3.4" 284 | resolved "https://registry.npm.taobao.org/mime/download/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" 285 | integrity sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM= 286 | 287 | mongodb@3.5.4: 288 | version "3.5.4" 289 | resolved "https://registry.npm.taobao.org/mongodb/download/mongodb-3.5.4.tgz#f7609cfa9f8c56c35e844b4216ddc3a1b1ec5bef" 290 | integrity sha1-92Cc+p+MVsNehEtCFt3DobHsW+8= 291 | dependencies: 292 | bl "^2.2.0" 293 | bson "^1.1.1" 294 | denque "^1.4.1" 295 | require_optional "^1.0.1" 296 | safe-buffer "^5.1.2" 297 | optionalDependencies: 298 | saslprep "^1.0.0" 299 | 300 | mongoose-legacy-pluralize@1.0.2: 301 | version "1.0.2" 302 | resolved "https://registry.npm.taobao.org/mongoose-legacy-pluralize/download/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4" 303 | integrity sha1-O6n5H6UHtRhtOZ+0CFS/8Y+1Y+Q= 304 | 305 | mongoose@^5.7.5: 306 | version "5.9.4" 307 | resolved "https://registry.npm.taobao.org/mongoose/download/mongoose-5.9.4.tgz#e80a58fdb066b815b1e87c2daf389f9fbb516f49" 308 | integrity sha1-6ApY/bBmuBWx6Hwtrzifn7tRb0k= 309 | dependencies: 310 | bson "~1.1.1" 311 | kareem "2.3.1" 312 | mongodb "3.5.4" 313 | mongoose-legacy-pluralize "1.0.2" 314 | mpath "0.6.0" 315 | mquery "3.2.2" 316 | ms "2.1.2" 317 | regexp-clone "1.0.0" 318 | safe-buffer "5.1.2" 319 | sift "7.0.1" 320 | sliced "1.0.1" 321 | 322 | morgan@~1.9.0: 323 | version "1.9.1" 324 | resolved "https://registry.npm.taobao.org/morgan/download/morgan-1.9.1.tgz#0a8d16734a1d9afbc824b99df87e738e58e2da59" 325 | integrity sha1-Co0Wc0odmvvIJLmd+H5zjlji2lk= 326 | dependencies: 327 | basic-auth "~2.0.0" 328 | debug "2.6.9" 329 | depd "~1.1.2" 330 | on-finished "~2.3.0" 331 | on-headers "~1.0.1" 332 | 333 | mpath@0.6.0: 334 | version "0.6.0" 335 | resolved "https://registry.npm.taobao.org/mpath/download/mpath-0.6.0.tgz#aa922029fca4f0f641f360e74c5c1b6a4c47078e" 336 | integrity sha1-qpIgKfyk8PZB82DnTFwbakxHB44= 337 | 338 | mquery@3.2.2: 339 | version "3.2.2" 340 | resolved "https://registry.npm.taobao.org/mquery/download/mquery-3.2.2.tgz#e1383a3951852ce23e37f619a9b350f1fb3664e7" 341 | integrity sha1-4Tg6OVGFLOI+N/YZqbNQ8fs2ZOc= 342 | dependencies: 343 | bluebird "3.5.1" 344 | debug "3.1.0" 345 | regexp-clone "^1.0.0" 346 | safe-buffer "5.1.2" 347 | sliced "1.0.1" 348 | 349 | ms@2.0.0: 350 | version "2.0.0" 351 | resolved "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 352 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 353 | 354 | ms@2.1.2: 355 | version "2.1.2" 356 | resolved "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 357 | integrity sha1-0J0fNXtEP0kzgqjrPM0YOHKuYAk= 358 | 359 | negotiator@0.6.2: 360 | version "0.6.2" 361 | resolved "https://registry.npm.taobao.org/negotiator/download/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 362 | integrity sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs= 363 | 364 | on-finished@~2.3.0: 365 | version "2.3.0" 366 | resolved "https://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 367 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 368 | dependencies: 369 | ee-first "1.1.1" 370 | 371 | on-headers@~1.0.1: 372 | version "1.0.2" 373 | resolved "https://registry.npm.taobao.org/on-headers/download/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" 374 | integrity sha1-dysK5qqlJcOZ5Imt+tkMQD6zwo8= 375 | 376 | parseurl@~1.3.1, parseurl@~1.3.2: 377 | version "1.3.3" 378 | resolved "https://registry.npm.taobao.org/parseurl/download/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 379 | integrity sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ= 380 | 381 | path-to-regexp@0.1.7: 382 | version "0.1.7" 383 | resolved "https://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 384 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 385 | 386 | process-nextick-args@~2.0.0: 387 | version "2.0.1" 388 | resolved "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 389 | integrity sha1-eCDZsWEgzFXKmud5JoCufbptf+I= 390 | 391 | proxy-addr@~1.1.5: 392 | version "1.1.5" 393 | resolved "https://registry.npm.taobao.org/proxy-addr/download/proxy-addr-1.1.5.tgz?cache=0&sync_timestamp=1582556112011&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fproxy-addr%2Fdownload%2Fproxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918" 394 | integrity sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg= 395 | dependencies: 396 | forwarded "~0.1.0" 397 | ipaddr.js "1.4.0" 398 | 399 | qs@6.5.0: 400 | version "6.5.0" 401 | resolved "https://registry.npm.taobao.org/qs/download/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49" 402 | integrity sha1-jQSVTTZN7z78VbWgeT4eLIsebkk= 403 | 404 | qs@6.5.2: 405 | version "6.5.2" 406 | resolved "https://registry.npm.taobao.org/qs/download/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" 407 | integrity sha1-yzroBuh0BERYTvFUzo7pjUA/PjY= 408 | 409 | range-parser@~1.2.0: 410 | version "1.2.1" 411 | resolved "https://registry.npm.taobao.org/range-parser/download/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 412 | integrity sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE= 413 | 414 | raw-body@2.3.3: 415 | version "2.3.3" 416 | resolved "https://registry.npm.taobao.org/raw-body/download/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" 417 | integrity sha1-GzJOzmtXBuFThVvBFIxlu39uoMM= 418 | dependencies: 419 | bytes "3.0.0" 420 | http-errors "1.6.3" 421 | iconv-lite "0.4.23" 422 | unpipe "1.0.0" 423 | 424 | readable-stream@^2.3.5: 425 | version "2.3.7" 426 | resolved "https://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.7.tgz?cache=0&sync_timestamp=1581622984924&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freadable-stream%2Fdownload%2Freadable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" 427 | integrity sha1-Hsoc9xGu+BTAT2IlKjamL2yyO1c= 428 | dependencies: 429 | core-util-is "~1.0.0" 430 | inherits "~2.0.3" 431 | isarray "~1.0.0" 432 | process-nextick-args "~2.0.0" 433 | safe-buffer "~5.1.1" 434 | string_decoder "~1.1.1" 435 | util-deprecate "~1.0.1" 436 | 437 | regexp-clone@1.0.0, regexp-clone@^1.0.0: 438 | version "1.0.0" 439 | resolved "https://registry.npm.taobao.org/regexp-clone/download/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63" 440 | integrity sha1-Ii25Z2IydwViYLmSYmNUoEzpv2M= 441 | 442 | require_optional@^1.0.1: 443 | version "1.0.1" 444 | resolved "https://registry.npm.taobao.org/require_optional/download/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e" 445 | integrity sha1-TPNaQkf2TKPfjC7yCMxJSxyo/C4= 446 | dependencies: 447 | resolve-from "^2.0.0" 448 | semver "^5.1.0" 449 | 450 | resolve-from@^2.0.0: 451 | version "2.0.0" 452 | resolved "https://registry.npm.taobao.org/resolve-from/download/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" 453 | integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c= 454 | 455 | safe-buffer@5.1.1: 456 | version "5.1.1" 457 | resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 458 | integrity sha1-iTMSr2myEj3vcfV4iQAWce6yyFM= 459 | 460 | safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: 461 | version "5.1.2" 462 | resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 463 | integrity sha1-mR7GnSluAxN0fVm9/St0XDX4go0= 464 | 465 | safe-buffer@^5.1.1, safe-buffer@^5.1.2: 466 | version "5.2.0" 467 | resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" 468 | integrity sha1-t02uxJsRSPiMZLaNSbHoFcHy9Rk= 469 | 470 | "safer-buffer@>= 2.1.2 < 3": 471 | version "2.1.2" 472 | resolved "https://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 473 | integrity sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo= 474 | 475 | saslprep@^1.0.0: 476 | version "1.0.3" 477 | resolved "https://registry.npm.taobao.org/saslprep/download/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" 478 | integrity sha1-TAL5RrVs9UKX40e6EJPnrKxM8iY= 479 | dependencies: 480 | sparse-bitfield "^3.0.3" 481 | 482 | semver@^5.1.0: 483 | version "5.7.1" 484 | resolved "https://registry.npm.taobao.org/semver/download/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 485 | integrity sha1-qVT5Ma66UI0we78Gnv8MAclhFvc= 486 | 487 | send@0.15.6: 488 | version "0.15.6" 489 | resolved "https://registry.npm.taobao.org/send/download/send-0.15.6.tgz#20f23a9c925b762ab82705fe2f9db252ace47e34" 490 | integrity sha1-IPI6nJJbdiq4JwX+L52yUqzkfjQ= 491 | dependencies: 492 | debug "2.6.9" 493 | depd "~1.1.1" 494 | destroy "~1.0.4" 495 | encodeurl "~1.0.1" 496 | escape-html "~1.0.3" 497 | etag "~1.8.1" 498 | fresh "0.5.2" 499 | http-errors "~1.6.2" 500 | mime "1.3.4" 501 | ms "2.0.0" 502 | on-finished "~2.3.0" 503 | range-parser "~1.2.0" 504 | statuses "~1.3.1" 505 | 506 | serve-favicon@~2.4.5: 507 | version "2.4.5" 508 | resolved "https://registry.npm.taobao.org/serve-favicon/download/serve-favicon-2.4.5.tgz#49d9a46863153a9240691c893d2b0e7d85d6d436" 509 | integrity sha1-SdmkaGMVOpJAaRyJPSsOfYXW1DY= 510 | dependencies: 511 | etag "~1.8.1" 512 | fresh "0.5.2" 513 | ms "2.0.0" 514 | parseurl "~1.3.2" 515 | safe-buffer "5.1.1" 516 | 517 | serve-static@1.12.6: 518 | version "1.12.6" 519 | resolved "https://registry.npm.taobao.org/serve-static/download/serve-static-1.12.6.tgz#b973773f63449934da54e5beba5e31d9f4211577" 520 | integrity sha1-uXN3P2NEmTTaVOW+ul4x2fQhFXc= 521 | dependencies: 522 | encodeurl "~1.0.1" 523 | escape-html "~1.0.3" 524 | parseurl "~1.3.2" 525 | send "0.15.6" 526 | 527 | setprototypeof@1.0.3: 528 | version "1.0.3" 529 | resolved "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" 530 | integrity sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ= 531 | 532 | setprototypeof@1.1.0: 533 | version "1.1.0" 534 | resolved "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" 535 | integrity sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY= 536 | 537 | sift@7.0.1: 538 | version "7.0.1" 539 | resolved "https://registry.npm.taobao.org/sift/download/sift-7.0.1.tgz#47d62c50b159d316f1372f8b53f9c10cd21a4b08" 540 | integrity sha1-R9YsULFZ0xbxNy+LU/nBDNIaSwg= 541 | 542 | sliced@1.0.1: 543 | version "1.0.1" 544 | resolved "https://registry.npm.taobao.org/sliced/download/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" 545 | integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E= 546 | 547 | sparse-bitfield@^3.0.3: 548 | version "3.0.3" 549 | resolved "https://registry.npm.taobao.org/sparse-bitfield/download/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" 550 | integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE= 551 | dependencies: 552 | memory-pager "^1.0.2" 553 | 554 | "statuses@>= 1.4.0 < 2": 555 | version "1.5.0" 556 | resolved "https://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 557 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 558 | 559 | statuses@~1.3.1: 560 | version "1.3.1" 561 | resolved "https://registry.npm.taobao.org/statuses/download/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" 562 | integrity sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4= 563 | 564 | string_decoder@~1.1.1: 565 | version "1.1.1" 566 | resolved "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" 567 | integrity sha1-nPFhG6YmhdcDCunkujQUnDrwP8g= 568 | dependencies: 569 | safe-buffer "~5.1.0" 570 | 571 | type-is@~1.6.15, type-is@~1.6.16: 572 | version "1.6.18" 573 | resolved "https://registry.npm.taobao.org/type-is/download/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 574 | integrity sha1-TlUs0F3wlGfcvE73Od6J8s83wTE= 575 | dependencies: 576 | media-typer "0.3.0" 577 | mime-types "~2.1.24" 578 | 579 | unpipe@1.0.0, unpipe@~1.0.0: 580 | version "1.0.0" 581 | resolved "https://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 582 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 583 | 584 | util-deprecate@~1.0.1: 585 | version "1.0.2" 586 | resolved "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 587 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 588 | 589 | utils-merge@1.0.0: 590 | version "1.0.0" 591 | resolved "https://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" 592 | integrity sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg= 593 | 594 | vary@~1.1.1: 595 | version "1.1.2" 596 | resolved "https://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 597 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 598 | --------------------------------------------------------------------------------