├── .gitignore ├── README.md ├── client ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── README.md ├── build │ ├── build.js │ ├── check-versions.js │ ├── dev-client.js │ ├── dev-server.js │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ ├── webpack.prod.conf.js │ └── webpack.test.conf.js ├── config │ ├── dev.env.js │ ├── index.js │ ├── prod.env.js │ └── test.env.js ├── index.html ├── package.json ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── Hello.vue │ │ ├── cart │ │ │ └── cart.vue │ │ ├── cate │ │ │ ├── cate.vue │ │ │ └── detail.vue │ │ ├── center │ │ │ └── center.vue │ │ ├── com │ │ │ ├── header.vue │ │ │ ├── jam.js │ │ │ ├── loading.vue │ │ │ ├── localDB.js │ │ │ ├── sidebar.vue │ │ │ └── swiper.vue │ │ └── page │ │ │ └── index.vue │ ├── css │ │ ├── base.scss │ │ ├── cart.scss │ │ ├── cate.scss │ │ ├── center.scss │ │ ├── detail.scss │ │ └── index.scss │ ├── main.js │ ├── router │ │ └── router.js │ └── store │ │ └── store.js ├── static │ ├── .gitkeep │ └── data │ │ ├── cart.json │ │ ├── cate.json │ │ └── index.json └── test │ ├── e2e │ ├── custom-assertions │ │ └── elementCount.js │ ├── nightwatch.conf.js │ ├── runner.js │ └── specs │ │ └── test.js │ └── unit │ ├── .eslintrc │ ├── index.js │ ├── karma.conf.js │ └── specs │ └── Hello.spec.js ├── s1_serverNodeBegin ├── index.js ├── requestHandlers.js ├── router.js └── server.js ├── s2_serverExpress ├── index.js ├── package.json └── routes │ └── routers.js ├── s3_Mongodb └── test.js ├── s4_mongoose ├── package.json └── test.js ├── s5_server ├── api.js ├── db.js ├── index.js ├── newDb.js └── package.json └── server ├── api.js ├── db.js ├── index.js ├── initCarts.json ├── initGoods.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log 4 | .idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-node-proj 2 | my first project of learning vue-node 3 | 4 | **简介:** 5 | 6 | 之前刚入门vue并做好了一个简而全的纯vue2全家桶的项目,数据都是本地 json 模拟请求的;详情请移步这里:[vue-proj-demo](https://github.com/gjincai/vue-proj-demo) 7 | 8 | 为了真正做到数据库的真实存取,于是又开始入门了 node+express+mongoose 、并以此来为之前的vue页面写后台数据接口。 9 | 10 | ## 代码目录说明: 11 | 12 | ``` 13 | |--vue-node-proj 14 | |--client //前端vue页面:http://gjincai.github.io/tags/vue/ 15 | |--s1_serverNodeBegin //《Node入门》学习练习代码,地址:https://www.nodebeginner.org/index-zh-cn.html 16 | |--s2_serverExpress //express入门学习练习 17 | |--s3_Mongodb //mongodb入门学习练习:http://gjincai.github.io/tags/mongodb/ 18 | |--s4_mongoose //mongoose入门学习练习:http://gjincai.github.io/tags/mongodb/ 19 | |--s5_server //express与mongoose整合,实现注册登录的数据在mongodb的存取 20 | |--server //前端client页面的正式后台: 21 | |--api.js //所有接口 22 | |--db.js //数据库初始化、Schema数据模型 23 | |--index.js //后台服务启动入口 24 | |--initCarts.json //首次连接数据库,购物车数据的初始化 25 | |--initGoods.json //首次连接数据库,所有商品数据的初始化 26 | |--package.json //安装依赖:express,mongoose 27 | ``` 28 | 29 | ## 项目说明: 30 | 31 | 前端:`client` 目录;主要技术:`vue-cli + vue2 + vue-router2 + vuex2 + axios + es6 + sass + eslint` 32 | 33 | 后台:`server` 目录;主要技术:`express+mongoose` 34 | 35 | (前后端分离,路由跳转在前端通过 vue-router 控制,后台只负责数据接口) 36 | 37 | ## 项目运行: 38 | ### 环境配置: 39 | **node.js 与 express 入门:** 40 | 41 | 学习练习代码:参考本项目中的文件夹 `vue-node-proj/s1_serverNodeBegin` 和 `vue-node-proj/s2_serverExpress`; 42 | 43 | **mongodb的安装与配置、mongoose的基本使用:** 44 | 45 | blog学习笔记:[http://gjincai.github.io/categories/mongodb/](http://gjincai.github.io/categories/mongodb/); 46 | 47 | 学习练习代码:参考本项目中的文件夹 `vue-node-proj/s3_Mongodb` 和 `vue-node-proj/s3_Mongodb`; 48 | 49 | ### 运行顺序: 50 | 51 | 新建命令行窗口1,开启本地mongodb服务: 52 | 53 | ``` 54 | mongod 55 | ``` 56 | 57 | 新建命令行窗口2,开启本地后台node服务器: 58 | 59 | ``` 60 | cd vue-node-proj/server 61 | cnpm install --save 62 | node index.js 63 | ``` 64 | 65 | 新建命令行窗口3,开启本地前端vue的dev模式: 66 | 67 | ``` 68 | cd vue-node-proj/client 69 | cnpm install --save 70 | npm run dev --color 71 | ``` 72 | 73 | 然后在浏览器打开: 74 | 75 | ``` 76 | localhost:8080 77 | ``` 78 | 79 | ## 相关学习笔记 80 | [express+mongoose 实现简易后台数据接口](http://gjincai.github.io/2017/07/26/express-mongoose-%E5%AE%9E%E7%8E%B0%E7%AE%80%E6%98%93%E5%90%8E%E5%8F%B0%E6%95%B0%E6%8D%AE%E6%8E%A5%E5%8F%A3/) 81 | 82 | ## 效果呈现: 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-2" 5 | ], 6 | "plugins": ["transform-runtime"], 7 | "comments": false, 8 | "env": { 9 | "test": { 10 | "presets": ["env", "stage-2"], 11 | "plugins": [ "istanbul" ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /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/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | 'rules': { 20 | // allow paren-less arrow functions 21 | 'arrow-parens': 0, 22 | // allow async-await 23 | 'generator-star-spacing': 0, 24 | // allow debugger during development 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | test/unit/coverage 8 | test/e2e/reports 9 | selenium-debug.log 10 | -------------------------------------------------------------------------------- /client/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserlist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # client 2 | 3 | > A Vue.js project width Node 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | 20 | # run unit tests 21 | npm run unit 22 | 23 | # run e2e tests 24 | npm run e2e 25 | 26 | # run all tests 27 | npm test 28 | ``` 29 | 30 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 31 | -------------------------------------------------------------------------------- /client/build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /client/build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /client/build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /client/build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = process.env.NODE_ENV === 'testing' 14 | ? require('./webpack.prod.conf') 15 | : require('./webpack.dev.conf') 16 | 17 | // default port where dev server listens for incoming traffic 18 | var port = process.env.PORT || config.dev.port 19 | // automatically open browser, if not set will be false 20 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 21 | // Define HTTP proxies to your custom API backend 22 | // https://github.com/chimurai/http-proxy-middleware 23 | var proxyTable = config.dev.proxyTable 24 | 25 | var app = express() 26 | var compiler = webpack(webpackConfig) 27 | 28 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 29 | publicPath: webpackConfig.output.publicPath, 30 | quiet: true 31 | }) 32 | 33 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 34 | log: () => {} 35 | }) 36 | // force page reload when html-webpack-plugin template changes 37 | compiler.plugin('compilation', function (compilation) { 38 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 39 | hotMiddleware.publish({ action: 'reload' }) 40 | cb() 41 | }) 42 | }) 43 | 44 | // proxy api requests 45 | Object.keys(proxyTable).forEach(function (context) { 46 | var options = proxyTable[context] 47 | if (typeof options === 'string') { 48 | options = { target: options } 49 | } 50 | app.use(proxyMiddleware(options.filter || context, options)) 51 | }) 52 | 53 | // handle fallback for HTML5 history API 54 | app.use(require('connect-history-api-fallback')()) 55 | 56 | // serve webpack bundle output 57 | app.use(devMiddleware) 58 | 59 | // enable hot-reload and state-preserving 60 | // compilation error display 61 | app.use(hotMiddleware) 62 | 63 | // serve pure static assets 64 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 65 | app.use(staticPath, express.static('./static')) 66 | 67 | var uri = 'http://localhost:' + port 68 | 69 | var _resolve 70 | var readyPromise = new Promise(resolve => { 71 | _resolve = resolve 72 | }) 73 | 74 | console.log('> Starting dev server...') 75 | devMiddleware.waitUntilValid(() => { 76 | console.log('> Listening at ' + uri + '\n') 77 | // when env is testing, don't need open it 78 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 79 | opn(uri) 80 | } 81 | _resolve() 82 | }) 83 | 84 | var server = app.listen(port) 85 | 86 | module.exports = { 87 | ready: readyPromise, 88 | close: () => { 89 | server.close() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /client/build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader] 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }) 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /client/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /client/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js' 13 | }, 14 | output: { 15 | path: config.build.assetsRoot, 16 | filename: '[name].js', 17 | publicPath: process.env.NODE_ENV === 'production' 18 | ? config.build.assetsPublicPath 19 | : config.dev.assetsPublicPath 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.vue', '.json'], 23 | alias: { 24 | 'vue$': 'vue/dist/vue.esm.js', 25 | '@': resolve('src') 26 | } 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.(js|vue)$/, 32 | loader: 'eslint-loader', 33 | enforce: 'pre', 34 | include: [resolve('src'), resolve('test')], 35 | options: { 36 | formatter: require('eslint-friendly-formatter') 37 | } 38 | }, 39 | { 40 | test: /\.vue$/, 41 | loader: 'vue-loader', 42 | options: { 43 | loaders: { 44 | // Since sass-loader (weirdly) has SCSS as its default parse mode, we map 45 | // the "scss" and "sass" values for the lang attribute to the right configs here. 46 | // other preprocessors should work out of the box, no loader config like this necessary. 47 | 'scss': 'vue-style-loader!css-loader!sass-loader', 48 | 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax' 49 | // 'scss': 'sass-loader', 50 | // 'sass': 'sass-loader?indentedSyntax' 51 | } 52 | // other vue-loader options go here 53 | } 54 | }, 55 | // { 56 | // test: /\.vue$/, 57 | // loader: 'vue-loader', 58 | // options: vueLoaderConfig 59 | // }, 60 | { 61 | test: /\.js$/, 62 | loader: 'babel-loader', 63 | include: [resolve('src'), resolve('test')] 64 | }, 65 | { 66 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 67 | loader: 'url-loader', 68 | options: { 69 | limit: 10000, 70 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 71 | } 72 | }, 73 | { 74 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 75 | loader: 'url-loader', 76 | options: { 77 | limit: 10000, 78 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 79 | } 80 | } 81 | ] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /client/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }), 33 | new FriendlyErrorsPlugin() 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /client/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = process.env.NODE_ENV === 'testing' 13 | ? require('../config/test.env') 14 | : config.build.env 15 | 16 | var webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true 21 | }) 22 | }, 23 | devtool: config.build.productionSourceMap ? '#source-map' : false, 24 | output: { 25 | path: config.build.assetsRoot, 26 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | }, 38 | sourceMap: true 39 | }), 40 | // extract css into its own file 41 | new ExtractTextPlugin({ 42 | filename: utils.assetsPath('css/[name].[contenthash].css') 43 | }), 44 | // Compress extracted CSS. We are using this plugin so that possible 45 | // duplicated CSS from different components can be deduped. 46 | new OptimizeCSSPlugin({ 47 | cssProcessorOptions: { 48 | safe: true 49 | } 50 | }), 51 | // generate dist index.html with correct asset hash for caching. 52 | // you can customize output by editing /index.html 53 | // see https://github.com/ampedandwired/html-webpack-plugin 54 | new HtmlWebpackPlugin({ 55 | filename: process.env.NODE_ENV === 'testing' 56 | ? 'index.html' 57 | : config.build.index, 58 | template: 'index.html', 59 | inject: true, 60 | minify: { 61 | removeComments: true, 62 | collapseWhitespace: true, 63 | removeAttributeQuotes: true 64 | // more options: 65 | // https://github.com/kangax/html-minifier#options-quick-reference 66 | }, 67 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 68 | chunksSortMode: 'dependency' 69 | }), 70 | // split vendor js into its own file 71 | new webpack.optimize.CommonsChunkPlugin({ 72 | name: 'vendor', 73 | minChunks: function (module, count) { 74 | // any required modules inside node_modules are extracted to vendor 75 | return ( 76 | module.resource && 77 | /\.js$/.test(module.resource) && 78 | module.resource.indexOf( 79 | path.join(__dirname, '../node_modules') 80 | ) === 0 81 | ) 82 | } 83 | }), 84 | // extract webpack runtime and module manifest to its own file in order to 85 | // prevent vendor hash from being updated whenever app bundle is updated 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'manifest', 88 | chunks: ['vendor'] 89 | }), 90 | // copy custom static assets 91 | new CopyWebpackPlugin([ 92 | { 93 | from: path.resolve(__dirname, '../static'), 94 | to: config.build.assetsSubDirectory, 95 | ignore: ['.*'] 96 | } 97 | ]) 98 | ] 99 | }) 100 | 101 | if (config.build.productionGzip) { 102 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 103 | 104 | webpackConfig.plugins.push( 105 | new CompressionWebpackPlugin({ 106 | asset: '[path].gz[query]', 107 | algorithm: 'gzip', 108 | test: new RegExp( 109 | '\\.(' + 110 | config.build.productionGzipExtensions.join('|') + 111 | ')$' 112 | ), 113 | threshold: 10240, 114 | minRatio: 0.8 115 | }) 116 | ) 117 | } 118 | 119 | if (config.build.bundleAnalyzerReport) { 120 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 121 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 122 | } 123 | 124 | module.exports = webpackConfig 125 | -------------------------------------------------------------------------------- /client/build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | // This is the webpack config used for unit tests. 2 | 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseConfig = require('./webpack.base.conf') 7 | 8 | var webpackConfig = merge(baseConfig, { 9 | // use inline sourcemap for karma-sourcemap-loader 10 | module: { 11 | rules: utils.styleLoaders() 12 | }, 13 | devtool: '#inline-source-map', 14 | resolveLoader: { 15 | alias: { 16 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 17 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 18 | 'scss-loader': 'sass-loader' 19 | } 20 | }, 21 | plugins: [ 22 | new webpack.DefinePlugin({ 23 | 'process.env': require('../config/test.env') 24 | }) 25 | ] 26 | }) 27 | 28 | // no need for app entry during tests 29 | delete webpackConfig.entry 30 | 31 | module.exports = webpackConfig 32 | -------------------------------------------------------------------------------- /client/config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /client/config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '/', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 8080, 27 | autoOpenBrowser: false, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: { 31 | // proxy all requests starting with /api to jsonplaceholder 32 | '/api': { 33 | target: 'http://127.0.0.1:8889/api', 34 | changeOrigin: true, 35 | pathRewrite: { 36 | '^/api': '' // 若target中没有/api、这里又为空,则404; 37 | } 38 | } 39 | }, 40 | // CSS Sourcemaps off by default because relative paths are "buggy" 41 | // with this option, according to the CSS-Loader README 42 | // (https://github.com/webpack/css-loader#sourcemaps) 43 | // In our experience, they generally work as expected, 44 | // just be aware of this issue when enabling this option. 45 | cssSourceMap: true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /client/config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | client 8 | 11 | 12 | 13 |
14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project width Node", 5 | "author": "Jam ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "node build/dev-server.js", 10 | "build": "node build/build.js", 11 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", 12 | "e2e": "node test/e2e/runner.js", 13 | "test": "npm run unit && npm run e2e", 14 | "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs" 15 | }, 16 | "dependencies": { 17 | "axios": "^0.16.2", 18 | "vue": "^2.3.3", 19 | "vue-infinite-loading": "^2.1.3", 20 | "vue-lazyload": "^1.0.6", 21 | "vue-router": "^2.3.1", 22 | "vue-swipe": "^2.0.3", 23 | "vuex": "^2.3.1" 24 | }, 25 | "devDependencies": { 26 | "autoprefixer": "^6.7.2", 27 | "babel-core": "^6.22.1", 28 | "babel-eslint": "^7.1.1", 29 | "babel-loader": "^6.2.10", 30 | "babel-plugin-istanbul": "^4.1.1", 31 | "babel-plugin-transform-runtime": "^6.22.0", 32 | "babel-preset-env": "^1.3.2", 33 | "babel-preset-stage-2": "^6.22.0", 34 | "babel-register": "^6.22.0", 35 | "chai": "^3.5.0", 36 | "chalk": "^1.1.3", 37 | "chromedriver": "^2.27.2", 38 | "connect-history-api-fallback": "^1.3.0", 39 | "copy-webpack-plugin": "^4.0.1", 40 | "cross-env": "^4.0.0", 41 | "cross-spawn": "^5.0.1", 42 | "css-loader": "^0.28.0", 43 | "eslint": "^3.19.0", 44 | "eslint-config-standard": "^6.2.1", 45 | "eslint-friendly-formatter": "^2.0.7", 46 | "eslint-loader": "^1.7.1", 47 | "eslint-plugin-html": "^2.0.0", 48 | "eslint-plugin-promise": "^3.4.0", 49 | "eslint-plugin-standard": "^2.0.1", 50 | "eventsource-polyfill": "^0.9.6", 51 | "express": "^4.14.1", 52 | "extract-text-webpack-plugin": "^2.0.0", 53 | "file-loader": "^0.11.1", 54 | "friendly-errors-webpack-plugin": "^1.1.3", 55 | "html-webpack-plugin": "^2.28.0", 56 | "http-proxy-middleware": "^0.17.3", 57 | "inject-loader": "^3.0.0", 58 | "karma": "^1.4.1", 59 | "karma-coverage": "^1.1.1", 60 | "karma-mocha": "^1.3.0", 61 | "karma-phantomjs-launcher": "^1.0.2", 62 | "karma-phantomjs-shim": "^1.4.0", 63 | "karma-sinon-chai": "^1.3.1", 64 | "karma-sourcemap-loader": "^0.3.7", 65 | "karma-spec-reporter": "0.0.30", 66 | "karma-webpack": "^2.0.2", 67 | "lolex": "^1.5.2", 68 | "mocha": "^3.2.0", 69 | "nightwatch": "^0.9.12", 70 | "node-sass": "^4.5.3", 71 | "opn": "^4.0.2", 72 | "optimize-css-assets-webpack-plugin": "^1.3.0", 73 | "ora": "^1.2.0", 74 | "phantomjs-prebuilt": "^2.1.14", 75 | "rimraf": "^2.6.0", 76 | "sass-loader": "^6.0.5", 77 | "selenium-server": "^3.0.1", 78 | "semver": "^5.3.0", 79 | "shelljs": "^0.7.6", 80 | "sinon": "^2.1.0", 81 | "sinon-chai": "^2.8.0", 82 | "url-loader": "^0.5.8", 83 | "vue-loader": "^12.1.0", 84 | "vue-style-loader": "^3.0.1", 85 | "vue-template-compiler": "^2.3.3", 86 | "webpack": "^2.6.1", 87 | "webpack-bundle-analyzer": "^2.2.1", 88 | "webpack-dev-middleware": "^1.10.0", 89 | "webpack-hot-middleware": "^2.18.0", 90 | "webpack-merge": "^4.1.0" 91 | }, 92 | "engines": { 93 | "node": ">= 4.0.0", 94 | "npm": ">= 3.0.0" 95 | }, 96 | "browserslist": [ 97 | "> 1%", 98 | "last 2 versions", 99 | "not ie <= 8" 100 | ] 101 | } 102 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 31 | 32 | 37 | -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flydoing/vue-node-proj/f6c4f5ef1c6d2b7a2497ce73c7a3c0fc7f3aaf98/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/components/Hello.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 34 | 35 | 36 | 55 | -------------------------------------------------------------------------------- /client/src/components/cart/cart.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 162 | 163 | 166 | -------------------------------------------------------------------------------- /client/src/components/cate/cate.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 129 | 130 | 133 | -------------------------------------------------------------------------------- /client/src/components/cate/detail.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 124 | 125 | 128 | -------------------------------------------------------------------------------- /client/src/components/center/center.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 208 | 209 | 212 | -------------------------------------------------------------------------------- /client/src/components/com/header.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 33 | 34 | 71 | -------------------------------------------------------------------------------- /client/src/components/com/jam.js: -------------------------------------------------------------------------------- 1 | export default class jam { 2 | // constructor () { 3 | // } 4 | locDbSet (name, obj) { 5 | if (!name || !obj) { 6 | console.log('name,obj:参数不能为空!') 7 | return 8 | } 9 | window.localStorage.setItem(name, JSON.stringify(obj)) 10 | } 11 | locDbGet (name) { 12 | if (!name) { 13 | console.log('locDbGet(name): name参数不能为空!') 14 | return 15 | } 16 | // return JSON.parse(window.localStorage.getItem(name)) || {error: '不存在'} 17 | return JSON.parse(window.localStorage.getItem(name)) 18 | } 19 | isPhone (str) { 20 | var pattern1 = /^(0|86|17951)?(13[0-9]|15[012356789]|17[0-9]|18[0-9]|14[57])[0-9]{8}$/ 21 | if (pattern1.test(str)) { 22 | return true 23 | } else { 24 | return false 25 | } 26 | } 27 | isPass (str) { 28 | var pattern2 = /^\w{6}$/ 29 | if (pattern2.test(str)) { 30 | return true 31 | } else { 32 | return false 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /client/src/components/com/loading.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flydoing/vue-node-proj/f6c4f5ef1c6d2b7a2497ce73c7a3c0fc7f3aaf98/client/src/components/com/loading.vue -------------------------------------------------------------------------------- /client/src/components/com/localDB.js: -------------------------------------------------------------------------------- 1 | export default class todoDb { 2 | constructor (name) { 3 | this.name = name 4 | if (JSON.stringify(this.get(this.name)) === '{}') { 5 | this.set([]) 6 | } 7 | } 8 | set (val) { 9 | window.localStorage.setItem(this.name, JSON.stringify(val)) 10 | } 11 | get () { 12 | return JSON.parse(window.localStorage.getItem(this.name)) || {} 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client/src/components/com/sidebar.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 40 | 41 | 115 | -------------------------------------------------------------------------------- /client/src/components/com/swiper.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 38 | 39 | 52 | -------------------------------------------------------------------------------- /client/src/components/page/index.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 157 | 158 | 161 | -------------------------------------------------------------------------------- /client/src/css/base.scss: -------------------------------------------------------------------------------- 1 | /*base*/ 2 | body,div,ul,li,h3,h4,h5,form,input,button,textarea,p {margin: 0;padding: 0;font-family: "Microsoft YaHei";font-size: 12px;border:none;} 3 | address,em,strong,h3,h4,h5,i{font-style: normal;font-weight: normal;} 4 | ul,li{list-style: none;} 5 | a{color: black;text-decoration: none;} 6 | img{border: 0;} 7 | html{font-size: 40px;background: #ffffff;-webkit-overflow-scrolling: touch;} 8 | body{position:relative;max-width: 640px;min-width: 320px;margin:0 auto;background: #ffffff;} 9 | .shop{ 10 | position:relative; 11 | padding:50px 0; 12 | } 13 | //路由的active 14 | -------------------------------------------------------------------------------- /client/src/css/cart.scss: -------------------------------------------------------------------------------- 1 | .s-cart{ 2 | position: relative; 3 | } 4 | .cart-cont{ 5 | margin: .25rem 0 .5rem; 6 | padding: 0 .25rem; 7 | border-top: 1px solid #eee; 8 | background: #ffffff; 9 | .cont-one{ 10 | height: 2rem; 11 | overflow: hidden; 12 | padding: .25rem; 13 | border-bottom: 1px solid #eee; 14 | .goods-checkbox{ 15 | float: left; 16 | height: 2rem; 17 | line-height: 2rem; 18 | } 19 | .goods-a{ 20 | float: left; 21 | width: 1.8rem; 22 | height: 2rem; 23 | margin: 0 .25rem; 24 | .goods-img{ 25 | display: inline-block; 26 | width: 1.8rem; 27 | height: 2rem; 28 | } 29 | } 30 | .goods-info{ 31 | float: left; 32 | width: 7.2rem; 33 | // background: yellow; 34 | .goods-name{ 35 | min-width: 7.5rem; 36 | overflow: hidden; 37 | white-space: nowrap; 38 | text-overflow: ellipsis; 39 | font-size: .38rem; 40 | margin-bottom: .2rem; 41 | } 42 | .goods-counter{ 43 | a{ 44 | padding: .125rem .8rem; 45 | } 46 | .goods-num{ 47 | width: 1.4rem; 48 | border: 1px solid #eee; 49 | } 50 | } 51 | } 52 | .goods-price{ 53 | display: inline-block; 54 | height: 2rem; 55 | line-height: 2rem; 56 | font-size: .5rem; 57 | color: #fc8637; 58 | } 59 | .goods-delete{ 60 | float: right; 61 | height: 2rem; 62 | padding: 0 .3rem; 63 | font-size: 0.4rem; 64 | line-height: 2rem; 65 | } 66 | } 67 | } 68 | .cart-counter{ 69 | position: fixed; 70 | bottom: 0; 71 | z-index: 100; 72 | height: 50px; 73 | line-height: 50px; 74 | width:16rem; 75 | max-width: 16rem; 76 | min-width: 320px; 77 | margin:0 auto; 78 | border-top: 1px solid #eee; 79 | .all-checkbox,.all-price,.btn-counter{ 80 | display: inline-block; 81 | height: 50px; 82 | font-size: .4rem; 83 | } 84 | .all-checkbox{ 85 | padding-left: .8rem; 86 | } 87 | .all-price .price-p{ 88 | margin-left: .8rem; 89 | font-size: .4rem; 90 | .price{ 91 | color: #fc8637; 92 | } 93 | } 94 | .btn-counter{ 95 | float: right; 96 | color: red; 97 | margin-right: .8rem; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /client/src/css/cate.scss: -------------------------------------------------------------------------------- 1 | .cate-nav{ 2 | position: fixed; 3 | width: 4.5rem; 4 | height: 8.5rem; //动态变动 5 | overflow: hidden; 6 | background: #f6f6f6; 7 | .nav-out{ 8 | // width: 4.5rem; 9 | width: 4.8rem; 10 | height: 8.5rem; //动态变动 11 | overflow-x: hidden; 12 | overflow-y: auto; 13 | border-top: 1px solid #eee; 14 | border-bottom: 1px solid #eee; 15 | .nav-a{ 16 | display: block; 17 | height: 1.5rem; 18 | line-height: 1.5rem; 19 | text-align: center; 20 | font-size: .625rem; 21 | color: #666666; 22 | border-bottom: 1px solid #eee; 23 | } 24 | .nav-a-act{ 25 | background: #ffffff; 26 | &:before{ 27 | content: ''; 28 | float: left; 29 | width: 1px; 30 | height: 100%; 31 | border-right: 5px solid #282828; 32 | } 33 | } 34 | } 35 | } 36 | .cate-cont{ 37 | position: relative; 38 | overflow: hidden; 39 | margin-top: .25rem; 40 | margin-left: 4.5rem; 41 | background: #ffffff; 42 | li{ 43 | display: inline-block; 44 | float: left; 45 | width: 33.3%; 46 | } 47 | .cont-li{ 48 | display: inline-block; 49 | margin-bottom: .4rem; 50 | margin-left: .2rem; 51 | padding: .275rem; 52 | border-radius: .2rem; 53 | border: 1px solid #eee; 54 | background: #ffffff; 55 | .pic{ 56 | display: block; 57 | width: 2.8rem; 58 | height: 3rem; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /client/src/css/center.scss: -------------------------------------------------------------------------------- 1 | $border:#ccc; 2 | div{ 3 | // color: red; 4 | } 5 | fieldset{ 6 | text-align: center; 7 | color: #dbdbdb; 8 | border: none; 9 | border-top: 1px solid #dbdbdb; 10 | } 11 | 12 | .s-center{ 13 | position: relative; 14 | } 15 | .ban{ 16 | height: 5rem; 17 | text-align: center; 18 | background: rgba(0,0,0,0.1); 19 | .uname{ 20 | padding-top: 2rem; 21 | font-size: .4rem; 22 | color: #555; 23 | span{ 24 | display: inline-block; 25 | padding: 0 .8rem; 26 | cursor: pointer; 27 | font-size: .8rem; 28 | } 29 | } 30 | } 31 | .cont-center{ 32 | .item{ 33 | margin: .375rem 0 .75rem; 34 | border-top: 1px solid $border; 35 | border-bottom: 1px solid $border; 36 | a{ 37 | display: block; 38 | margin: 0 .375rem; 39 | padding: .375rem 0; 40 | font-size: .8rem; 41 | border-bottom: 1px solid $border; 42 | i{ 43 | float: right; 44 | } 45 | } 46 | &>a:last-child{ 47 | border-bottom: none; 48 | } 49 | } 50 | } 51 | .form-item{ 52 | position: relative; 53 | height: 1.8rem; 54 | line-height: 1.8rem; 55 | padding: 0 .5rem; 56 | border-bottom: 1px solid $border; 57 | label,input,button,p{ 58 | font-size: .8rem; 59 | outline: none; 60 | } 61 | input{ 62 | width: 7rem; 63 | padding: 0 0 0 .375rem; 64 | border: none; 65 | } 66 | .form-tips{ 67 | color: red; 68 | } 69 | .btn-get{ 70 | position: absolute; 71 | right: 0; 72 | width: 5.2rem; 73 | height: 1.8rem; 74 | text-align: center; 75 | line-height: 1.8rem; 76 | font-size: .8rem; 77 | color: #4a90e2; 78 | background-color: #ececec; 79 | } 80 | } 81 | .btn-div{ 82 | text-align: center; 83 | .btn-a{ 84 | display: inline-block; 85 | height: 1.8rem; 86 | line-height: 1.8rem; 87 | margin: 1.2rem 0 .8rem; 88 | padding: 0 2.5rem; 89 | color: #4a90e2; 90 | font-size: .8rem; 91 | border: 1px solid #4a90e2; 92 | border-radius: .425rem; 93 | } 94 | } 95 | .form-fieldset{ 96 | margin: .5rem 0 .8rem; 97 | legend{ 98 | margin-bottom: .25rem; 99 | } 100 | } 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /client/src/css/detail.scss: -------------------------------------------------------------------------------- 1 | .s-detail{ 2 | position: relative; 3 | } 4 | .s-detail{ 5 | .ban{ 6 | height: auto; 7 | width: 100%; 8 | img{ 9 | width: 100%; 10 | height: auto; 11 | } 12 | } 13 | } 14 | .cont{ 15 | margin: .3rem 0; 16 | padding: 0 .375rem; 17 | .name{ 18 | font-size: .45rem; 19 | } 20 | .price{ 21 | text-align: center; 22 | font-size: .5rem; 23 | color: red; 24 | } 25 | .goods-counter{ 26 | height: .75rem; 27 | a{ 28 | display: inline-block; 29 | height: .75rem; 30 | line-height: .75rem; 31 | font-size: .3rem; 32 | padding: 0 .8rem; 33 | border: 1px solid #eee; 34 | } 35 | .goods-num{ 36 | height: .75rem; 37 | line-height: .75rem; 38 | width: 1.4rem; 39 | text-align: center; 40 | font-size: .4rem; 41 | border: 1px solid #eee; 42 | } 43 | } 44 | } 45 | .bot{ 46 | position: fixed; 47 | bottom: 0; 48 | z-index: 100; 49 | height: 50px; 50 | width:16rem; 51 | max-width: 16rem; 52 | min-width: 320px; 53 | margin:0 auto; 54 | color: #fff; 55 | border-top: 1px solid #eee; 56 | background: #ffffff; 57 | .add-cart{ 58 | float: right; 59 | height: 34px; 60 | margin-top: 8px; 61 | margin-right: .5rem; 62 | padding: 0 15px; 63 | line-height: 34px; 64 | font-size: .5rem; 65 | color: red; 66 | border: 1px solid red; 67 | border-radius: 4px; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /client/src/css/index.scss: -------------------------------------------------------------------------------- 1 | .s-index{ 2 | position: relative; 3 | background-color: #f6f6f6; 4 | } 5 | .cont{ 6 | margin-top: .4rem; 7 | } 8 | .cont-head{ 9 | height: 1.5rem; 10 | line-height: 1.5rem; 11 | padding: 0 .25rem; 12 | font-size: .7rem; 13 | border-top: 1px solid #eee; 14 | border-bottom: 1px solid #eee; 15 | background-color: #ffffff; 16 | &:before{ 17 | content: ''; 18 | width: 1px; 19 | height: 100%; 20 | margin-right: .125rem; 21 | border-right: 5px solid #282828; 22 | } 23 | .head-title{ 24 | // font-size: .4rem; 25 | } 26 | .head-right{ 27 | float: right; 28 | color: #666; 29 | // font-size: .4rem; 30 | } 31 | } 32 | .cont-main{ 33 | background-color: #ffffff; 34 | .name{ 35 | font-size: .7rem; 36 | } 37 | .desc{ 38 | font-size: .6rem; 39 | color: #666; 40 | } 41 | .price{ 42 | font-size: .7rem; 43 | color: #fc8637; 44 | } 45 | } 46 | .cont-temai{ 47 | overflow: hidden; 48 | padding: .375rem 0; 49 | text-align: center; 50 | .cont-one{ 51 | float: left; 52 | width: 33%; 53 | overflow: hidden; 54 | border-right: 1px solid #eee; 55 | span{ 56 | display: block; 57 | } 58 | .desc{ 59 | margin: .125rem 0 .25rem; 60 | } 61 | .pic{ 62 | width: 80%; 63 | } 64 | } 65 | &>:last-child{ 66 | border-right: none; 67 | } 68 | } 69 | .cont-rexiao{ 70 | height: 9rem; 71 | overflow: hidden; 72 | text-align: center; 73 | .cont-left{ 74 | float: left; 75 | width: 5.6rem; 76 | height: 8.6rem; 77 | padding: 0.2rem; 78 | border-right: 1px solid #eee; 79 | span{ 80 | display: block; 81 | } 82 | .desc{ 83 | margin: .125rem 0 .25rem; 84 | } 85 | .pic{ 86 | width: 5rem; 87 | height: 6rem; 88 | } 89 | } 90 | .cont-right{ 91 | float: left; 92 | width: 9.2rem; 93 | height: 9rem; 94 | &>:first-child{ 95 | border-bottom: 1px solid #eee; 96 | } 97 | .cont-right-one{ 98 | display: block; 99 | height: 4.1rem; 100 | padding: 0.2rem; 101 | .text{ 102 | float: left; 103 | width: 4.4rem; 104 | text-align: right; 105 | span{ 106 | display: block; 107 | } 108 | .name{ 109 | margin: .85rem 0 .125rem; 110 | } 111 | } 112 | .pic{ 113 | float: right; 114 | width: 4.1rem; 115 | height: 4.1rem; 116 | } 117 | } 118 | } 119 | } 120 | .cont-jingpin{ 121 | position: relative; 122 | overflow: hidden; 123 | padding-bottom: .4rem; 124 | // background-color: #f6f6f6; 125 | li{ 126 | display: inline-block; 127 | float: left; 128 | width: 50%; 129 | } 130 | .cont-li{ 131 | display: inline-block; 132 | margin-top: .4rem; 133 | margin-left: .3rem; 134 | padding: .375rem; 135 | border-radius: .2rem; 136 | border: 1px solid #eee; 137 | background: #ffffff; 138 | .pic{ 139 | display: block; 140 | width: 6.5rem; 141 | height: 8.125rem; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import router from './router/router.js' 3 | import store from './store/store.js' 4 | import axios from 'axios' 5 | Vue.prototype.$http = axios 6 | 7 | import VueLazyload from 'vue-lazyload' 8 | Vue.use(VueLazyload) 9 | 10 | const app = new Vue({ 11 | router, 12 | store 13 | }).$mount('#app') 14 | // const app = new Vue({ 15 | // router, 16 | // store, 17 | // render: h => h('.app', App) 18 | // }) 19 | 20 | export default app 21 | -------------------------------------------------------------------------------- /client/src/router/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | Vue.use(VueRouter) 4 | 5 | import App from '../App.vue' 6 | import Index from '../components/page/index.vue' 7 | import Cate from '../components/cate/cate.vue' 8 | import Detail from '../components/cate/detail.vue' 9 | import Center from '../components/center/center.vue' 10 | import Cart from '../components/cart/cart.vue' 11 | 12 | export default new VueRouter({ 13 | routes: [ 14 | { 15 | path: '/', 16 | redirect: '/index', 17 | component: App, 18 | children: [ 19 | {path: 'index', name: 'index', component: Index}, 20 | {path: 'cate', name: 'cate', component: Cate}, 21 | // {path: 'detail', name: 'detail', component: Detail}, 22 | {path: 'detail/:id', name: 'detail', component: Detail}, 23 | {path: 'center', name: 'center', component: Center}, 24 | {path: 'cart', name: 'cart', component: Cart} 25 | ] 26 | } 27 | ], 28 | linkActiveClass: 'footer-act' 29 | }) 30 | -------------------------------------------------------------------------------- /client/src/store/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | Vue.use(Vuex) 4 | export default new Vuex.Store({ 5 | state: { 6 | sideBarState: false, 7 | headerTitle: '默认的头部标题' 8 | }, 9 | mutations: { 10 | changeSideBarState (state, boolean) { 11 | state.sideBarState = boolean 12 | }, 13 | changeHeaderTitle (state, str) { 14 | state.headerTitle = str 15 | } 16 | }, 17 | actions: { 18 | // changeSideBarState (context, status) { 19 | // context.commit('changeSideBarState', status) 20 | // } 21 | // es6解构写法 22 | changeSideBarState ({commit}, status) { 23 | commit('changeSideBarState', status) 24 | }, 25 | changeHeaderTitle ({commit}, str) { 26 | commit('changeHeaderTitle', str) 27 | } 28 | }, 29 | getters: { 30 | getSideBarState (state) { 31 | return state.sideBarState 32 | }, 33 | getHeaderTitle (state) { 34 | return state.headerTitle 35 | } 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /client/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flydoing/vue-node-proj/f6c4f5ef1c6d2b7a2497ce73c7a3c0fc7f3aaf98/client/static/.gitkeep -------------------------------------------------------------------------------- /client/static/data/cart.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": 1, 3 | "msg": "success", 4 | "data": { 5 | "allSelect": false, 6 | "allSeclectPrice": 0, 7 | "carts": [ 8 | { 9 | "id": 100048, 10 | "type": "type_man", 11 | "isSelect": false, 12 | "cart_img": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg", 13 | "cart_name": "商品名字1", 14 | "cart_num": 1, 15 | "cart_price": 19 16 | }, 17 | { 18 | "id": 100048, 19 | "type": "type_man", 20 | "isSelect": false, 21 | "cart_img": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg", 22 | "cart_name": "商品名字2", 23 | "cart_num": 2, 24 | "cart_price": 29 25 | }, 26 | { 27 | "id": 100048, 28 | "type": "type_man", 29 | "isSelect": false, 30 | "cart_img": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg", 31 | "cart_name": "商品名字3", 32 | "cart_num": 1, 33 | "cart_price": 39 34 | } 35 | 36 | ] 37 | } 38 | } -------------------------------------------------------------------------------- /client/static/data/cate.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": 1, 3 | "msg": "success", 4 | "data": { 5 | "types": [ 6 | { 7 | "type_name": "全部", 8 | "type_now": "type_all" 9 | }, 10 | { 11 | "type_name": "男装", 12 | "type_now": "type_man" 13 | }, 14 | { 15 | "type_name": "女装", 16 | "type_now": "type_girl" 17 | }, 18 | { 19 | "type_name": "男装", 20 | "type_now": "type_man" 21 | }, 22 | { 23 | "type_name": "男装", 24 | "type_now": "type_man" 25 | }, 26 | { 27 | "type_name": "女装", 28 | "type_now": "type_girl" 29 | }, 30 | { 31 | "type_name": "女装", 32 | "type_now": "type_girl" 33 | } 34 | ], 35 | "allBrand": [ 36 | { 37 | "id": 100048, 38 | "type": "type_man", 39 | "brand_price": 79.9, 40 | "brand_name": "商品名字1", 41 | "brand_desc": "商品描述商品描述", 42 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 43 | }, 44 | { 45 | "id": 100047, 46 | "type": "type_man", 47 | "brand_price": 89.9, 48 | "brand_name": "商品名字2", 49 | "brand_desc": "商品描述商品描述", 50 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 51 | }, 52 | { 53 | "id": 100041, 54 | "type": "type_girl", 55 | "brand_price": 99.9, 56 | "brand_name": "商品名字3", 57 | "brand_desc": "商品描述商品描述", 58 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 59 | }, 60 | { 61 | "id": 100048, 62 | "type": "type_girl", 63 | "brand_price": 49.9, 64 | "brand_name": "商品名字4", 65 | "brand_desc": "商品描述商品描述", 66 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 67 | }, 68 | { 69 | "id": 100047, 70 | "type": "type_girl", 71 | "brand_price": 59.9, 72 | "brand_name": "商品名字5", 73 | "brand_desc": "商品描述商品", 74 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 75 | }, 76 | { 77 | "id": 100041, 78 | "type": "type_girl", 79 | "brand_price": 69.9, 80 | "brand_name": "商品名字6", 81 | "brand_desc": "商品描述商品", 82 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 83 | }, 84 | { 85 | "id": 100048, 86 | "type": "type_girl", 87 | "brand_price": 19.9, 88 | "brand_name": "商品名1字", 89 | "brand_desc": "商品描述商品描述", 90 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 91 | }, 92 | { 93 | "id": 100047, 94 | "type": "type_girl", 95 | "brand_price": 29.9, 96 | "brand_name": "商品名2字", 97 | "brand_desc": "商品描述商品描述", 98 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 99 | }, 100 | { 101 | "id": 100041, 102 | "type": "type_man", 103 | "brand_price": 39.9, 104 | "brand_name": "商品名3字", 105 | "brand_desc": "商品描述商品描述", 106 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 107 | }, 108 | { 109 | "id": 100042, 110 | "type": "type_man", 111 | "brand_price": 49.9, 112 | "brand_name": "商品名4字", 113 | "brand_desc": "商品描述商品描述", 114 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 115 | }, 116 | { 117 | "id": 100042, 118 | "type": "type_man", 119 | "brand_price": 49.9, 120 | "brand_name": "商品名4字", 121 | "brand_desc": "商品描述商品描述", 122 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 123 | }, 124 | { 125 | "id": 100042, 126 | "type": "type_man", 127 | "brand_price": 49.9, 128 | "brand_name": "商品名4字", 129 | "brand_desc": "商品描述商品描述", 130 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 131 | }, 132 | { 133 | "id": 100042, 134 | "type": "type_man", 135 | "brand_price": 49.9, 136 | "brand_name": "商品名4字", 137 | "brand_desc": "商品描述商品描述", 138 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 139 | }, 140 | { 141 | "id": 100042, 142 | "type": "type_man", 143 | "brand_price": 49.9, 144 | "brand_name": "商品名4字", 145 | "brand_desc": "商品描述商品描述", 146 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 147 | }, 148 | { 149 | "id": 100042, 150 | "type": "type_man", 151 | "brand_price": 49.9, 152 | "brand_name": "商品名4字", 153 | "brand_desc": "商品描述商品描述", 154 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 155 | }, 156 | { 157 | "id": 100042, 158 | "type": "type_man", 159 | "brand_price": 49.9, 160 | "brand_name": "商品名4字", 161 | "brand_desc": "商品描述商品描述", 162 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 163 | }, 164 | { 165 | "id": 100042, 166 | "type": "type_man", 167 | "brand_price": 49.9, 168 | "brand_name": "商品名4字", 169 | "brand_desc": "商品描述商品描述", 170 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 171 | }, 172 | { 173 | "id": 100042, 174 | "type": "type_man", 175 | "brand_price": 49.9, 176 | "brand_name": "商品名4字end", 177 | "brand_desc": "商品描述商品描述", 178 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 179 | } 180 | ] 181 | } 182 | } -------------------------------------------------------------------------------- /client/static/data/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": 1, 3 | "msg": "success", 4 | "data": { 5 | "temai": [ 6 | { 7 | "id": 100048, 8 | "type": "type_man", 9 | "brand_price": 79.9, 10 | "brand_name": "商品名字1", 11 | "brand_desc": "商品描述商品描述", 12 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 13 | }, 14 | { 15 | "id": 100047, 16 | "type": "type_man", 17 | "brand_price": 89.9, 18 | "brand_name": "商品名字2", 19 | "brand_desc": "商品描述商品描述", 20 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 21 | }, 22 | { 23 | "id": 100041, 24 | "type": "type_man", 25 | "brand_price": 99.9, 26 | "brand_name": "商品名字3", 27 | "brand_desc": "商品描述商品描述", 28 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 29 | } 30 | ], 31 | "rexiao": [ 32 | { 33 | "id": 100048, 34 | "type": "type_man", 35 | "brand_price": 49.9, 36 | "brand_name": "商品名字4", 37 | "brand_desc": "商品描述商品描述", 38 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 39 | }, 40 | { 41 | "id": 100047, 42 | "type": "type_man", 43 | "brand_price": 59.9, 44 | "brand_name": "商品名字5", 45 | "brand_desc": "商品描述商品", 46 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 47 | }, 48 | { 49 | "id": 100041, 50 | "type": "type_man", 51 | "brand_price": 69.9, 52 | "brand_name": "商品名字6", 53 | "brand_desc": "商品描述商品", 54 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 55 | } 56 | ], 57 | "jingpin": [ 58 | { 59 | "id": 100048, 60 | "type": "type_man", 61 | "brand_price": 19.9, 62 | "brand_name": "商品名1字", 63 | "brand_desc": "商品描述商品描述", 64 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 65 | }, 66 | { 67 | "id": 100047, 68 | "type": "type_man", 69 | "brand_price": 29.9, 70 | "brand_name": "商品名2字", 71 | "brand_desc": "商品描述商品描述", 72 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 73 | }, 74 | { 75 | "id": 100041, 76 | "type": "type_man", 77 | "brand_price": 39.9, 78 | "brand_name": "商品名3字", 79 | "brand_desc": "商品描述商品描述", 80 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 81 | }, 82 | { 83 | "id": 100042, 84 | "type": "type_man", 85 | "brand_price": 49.9, 86 | "brand_name": "商品名4字", 87 | "brand_desc": "商品描述商品描述", 88 | "brand_pic_url": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 89 | } 90 | ] 91 | } 92 | } -------------------------------------------------------------------------------- /client/test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count 11 | this.expected = count 12 | this.pass = function (val) { 13 | return val === this.expected 14 | } 15 | this.value = function (res) { 16 | return res.value 17 | } 18 | this.command = function (cb) { 19 | var self = this 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length 22 | }, [selector], function (res) { 23 | cb.call(self, res) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /client/test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | var server = require('../../build/dev-server.js') 4 | 5 | server.ready.then(() => { 6 | // 2. run the nightwatch test suite against it 7 | // to run in additional browsers: 8 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 9 | // 2. add it to the --env flag below 10 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 11 | // For more information on Nightwatch's config file, see 12 | // http://nightwatchjs.org/guide#settings-file 13 | var opts = process.argv.slice(2) 14 | if (opts.indexOf('--config') === -1) { 15 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 16 | } 17 | if (opts.indexOf('--env') === -1) { 18 | opts = opts.concat(['--env', 'chrome']) 19 | } 20 | 21 | var spawn = require('cross-spawn') 22 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 23 | 24 | runner.on('exit', function (code) { 25 | server.close() 26 | process.exit(code) 27 | }) 28 | 29 | runner.on('error', function (err) { 30 | server.close() 31 | throw err 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /client/test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /client/test/unit/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /client/test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var webpackConfig = require('../../build/webpack.test.conf') 7 | 8 | module.exports = function (config) { 9 | config.set({ 10 | // to run in additional browsers: 11 | // 1. install corresponding karma launcher 12 | // http://karma-runner.github.io/0.13/config/browsers.html 13 | // 2. add it to the `browsers` array below. 14 | browsers: ['PhantomJS'], 15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], 16 | reporters: ['spec', 'coverage'], 17 | files: ['./index.js'], 18 | preprocessors: { 19 | './index.js': ['webpack', 'sourcemap'] 20 | }, 21 | webpack: webpackConfig, 22 | webpackMiddleware: { 23 | noInfo: true 24 | }, 25 | coverageReporter: { 26 | dir: './coverage', 27 | reporters: [ 28 | { type: 'lcov', subdir: '.' }, 29 | { type: 'text-summary' } 30 | ] 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /client/test/unit/specs/Hello.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Hello from '@/components/Hello' 3 | 4 | describe('Hello.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(Hello) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .to.equal('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /s1_serverNodeBegin/index.js: -------------------------------------------------------------------------------- 1 | var server = require('./server') 2 | var router = require('./router') 3 | var requestHandlers = require('./requestHandlers') 4 | 5 | var handles = { 6 | '/': requestHandlers.start, 7 | '/start': requestHandlers.start, 8 | '/getJson': requestHandlers.getJson 9 | } 10 | 11 | 12 | 13 | server.start(router.route, handles) -------------------------------------------------------------------------------- /s1_serverNodeBegin/requestHandlers.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec 2 | 3 | function start (res) { 4 | console.log('requestHandlers start was called!') 5 | exec("ls -lah", function(error, stdout, stderr) { 6 | res.writeHead(200, {"Content-Type": "text/plain"}) 7 | res.write(stdout) 8 | res.end() 9 | }) 10 | } 11 | function getJson (res) { 12 | console.log('requesstHandlers getJson wad called!') 13 | res.writeHead(200, {"Content-Type": "application/json"}) 14 | // res.write('hello getJson') 15 | // res.toJSON({'name': 'guojincai'}) 16 | res.end(JSON.stringify({'name': 'guojincai', 'age': 99, 'hobby': 'movie'})) 17 | } 18 | 19 | exports.start = start 20 | exports.getJson = getJson -------------------------------------------------------------------------------- /s1_serverNodeBegin/router.js: -------------------------------------------------------------------------------- 1 | function route (handles, pathname, res) { 2 | console.log('route for: ' + pathname) 3 | if (typeof handles[pathname] === 'function') { 4 | // handles.pathname // 读出来的是 '/start',不存在该方法 5 | handles[pathname](res) 6 | } else { 7 | console.log("No request handler found for " + pathname) 8 | res.writeHead(404, {"Content-Type": "text/plain"}) 9 | res.write("404 Not found"); 10 | res.end(); 11 | } 12 | } 13 | 14 | exports.route = route -------------------------------------------------------------------------------- /s1_serverNodeBegin/server.js: -------------------------------------------------------------------------------- 1 | var http = require("http"); 2 | var url = require('url'); 3 | 4 | function start(route, handles) { 5 | function onRequest(req, res) { 6 | var pathname = url.parse(req.url).pathname 7 | console.log("Request received pathname:" + pathname) 8 | 9 | route(handles, pathname, res) 10 | 11 | // res.writeHead(200, {"Content-Type": "text/plain"}) 12 | // res.write("Hello node") 13 | // res.end() 14 | } 15 | http.createServer(onRequest).listen(8888); 16 | console.log("Server has started."); 17 | } 18 | exports.start = start; -------------------------------------------------------------------------------- /s2_serverExpress/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | 4 | var routers = require('./routes/routers') 5 | routers(app) 6 | 7 | app.listen(8888); -------------------------------------------------------------------------------- /s2_serverExpress/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server_express", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.15.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /s2_serverExpress/routes/routers.js: -------------------------------------------------------------------------------- 1 | module.exports = function (app) { 2 | app.get('/', function (req, res) { 3 | res.end('home page'); 4 | }); 5 | app.get('/home', function(req, res){ 6 | res.end('home page'); 7 | }); 8 | app.get('/detail/:id?', function(req, res){ 9 | res.end('center page: id :' + req.params.id); 10 | }); 11 | app.get('/center', function(req, res){ 12 | res.end('center page'); 13 | }); 14 | app.get('/api/send', function(req, res){ 15 | res.send({name: 'guojc', age: 99, hobby: 'movie'}); 16 | }); 17 | app.get('/api/json', function(req, res){ 18 | res.json({code: 200, data: {name: 'guojc9', age: 999, hobby: 'movie'}}); 19 | // res.status(200).json({name: 'guojc9', age: 999, hobby: 'movie'}); 20 | }); 21 | app.get('*', function(req, res){ 22 | res.end('404'); 23 | }); 24 | } -------------------------------------------------------------------------------- /s3_Mongodb/test.js: -------------------------------------------------------------------------------- 1 | // 全局安装了node驱动库:cnpm install mongodb 2 | var MongoClient = require('mongodb').MongoClient; 3 | 4 | var url_test = 'mongodb://localhost:27017/test'; 5 | var insertData = function(db){ 6 | db.collection('site').insertOne({name: 'guojc1', age: 991, hobby: 'movie1'}, function(err, result){ 7 | console.log('inserted successly'); 8 | console.log(result); 9 | db.close(); 10 | console.log('close'); 11 | }); 12 | } 13 | 14 | MongoClient.connect(url_test, function(err, db) { 15 | console.log('Connected successly to server.'); 16 | insertData(db); 17 | }); -------------------------------------------------------------------------------- /s4_mongoose/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "mongoose": "^4.10.6" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /s4_mongoose/test.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var db = mongoose.connect('mongodb://127.0.0.1:27017/test'); 3 | db.connection.on('error', function(error){ 4 | console.log('数据库test连接失败:' + error); 5 | }); 6 | db.connection.on('open', function(){ 7 | console.log('数据库test连接成功'); 8 | }); 9 | 10 | var testSchema = new mongoose.Schema({ 11 | name: {type: String}, 12 | age: {type: Number, default: 0}, 13 | email: {type: String}, 14 | time: {type: Date, default: Date.now} 15 | }); 16 | var testModel = db.model('test1', testSchema); // 集合名称;集合的结构对象 17 | // Document文档(关联数组式的对象) < Collection集合 < 数据库 18 | 19 | // step1: 插入数据 20 | // 插入数据,一次执行完后注释掉 start 21 | // 插入保存一段数据 22 | // testModel.create([ 23 | // {name: "test1", age: 8}, 24 | // {name: "test2", age: 18}, 25 | // {name: "test3", age: 28}, 26 | // {name: "test4", age: 38}, 27 | // {name: "test5", age: 48}, 28 | // {name: "test6", age: 58, email:"tttt@qq.com"}, 29 | // {name: "test7", age: 68, email:"ssss@qq.com"}, 30 | // {name: "test8", age: 18}, 31 | // {name: "test9", age: 18, email:"rrrr@qq.com"}, 32 | // {name: "test10",age: 18} 33 | // ], function (error, docs) { 34 | // if(error) { 35 | // console.log(error); 36 | // } else { 37 | // console.log('save ok'); 38 | // console.log(docs); 39 | // } 40 | // }); 41 | // 插入数据,一次执行完后注释掉 end 42 | 43 | // step2: 查询 44 | // find(Conditions,fields,callback); 45 | // 省略或为空、返回所有记录;只包含name,age字段,去掉默认的_id字段;执行回调函数 46 | testModel.find({}, {name:1, age:1, _id:0}, function(err, docs){ 47 | if (err) { 48 | console.log('查询出错:' + err); 49 | } else { 50 | console.log('{}查询结果为:'); 51 | console.log(docs); 52 | } 53 | }); 54 | // 查询age大于等于28,小于等于48 55 | testModel.find({age: {$gte: 28, $lte: 48}}, {name:1, age:1, _id:0}, function(err, docs){ 56 | if (err) { 57 | console.log('查询出错:' + err); 58 | } else { 59 | console.log('$gte,$lte查询结果为:'); 60 | console.log(docs); 61 | } 62 | }); 63 | // 查询age为58、68的2条数据 64 | testModel.find({age: {$in: [58, 68]}}, {name:1, age:1, _id:0}, function(err, docs){ 65 | if (err) { 66 | console.log('查询出错:' + err); 67 | } else { 68 | console.log('$in查询结果为:'); 69 | console.log(docs); 70 | } 71 | }); 72 | // 查询name为test3、或者age为18的全部数据 73 | testModel.find({$or: [{name: 'test3'}, {age: 18}]}, {name:1, age:1, _id:0}, function(err, docs){ 74 | if (err) { 75 | console.log('查询出错:' + err); 76 | } else { 77 | console.log('$or查询结果为:'); 78 | console.log(docs); 79 | } 80 | }); 81 | 82 | // step3:游标查询 83 | // 查询name为test3、或者age为18的全部数据;但限制只查询2条数据 84 | testModel.find({$or: [{name: 'test3'}, {age: 18}]}, {name:1, age:1, _id:0}, {limit: 2}, function(err, docs){ 85 | if (err) { 86 | console.log('查询出错:' + err); 87 | } else { 88 | console.log('limit查询结果为:'); 89 | console.log(docs); 90 | } 91 | }); 92 | // 查询age大于等于28,小于等于48;降序输出 93 | testModel.find({age: {$gte: 28, $lte: 48}}, {name:1, age:1, _id:0}, {sort: {age: -1}}, function(err, docs){ 94 | if (err) { 95 | console.log('查询出错:' + err); 96 | } else { 97 | console.log('sort查询结果为:'); 98 | console.log(docs); 99 | } 100 | }); 101 | 102 | // step4: 数据更新 103 | var conditions = {name: 'test1'}; 104 | var update = {$set: {age: 11 }}; 105 | testModel.update(conditions, update, function(error){ 106 | if(error) { 107 | console.log(error); 108 | } else { 109 | console.log('Update success!'); 110 | testModel.find({name: 'test1'}, {name:1, age:1, _id:0}, function(err, docs){ 111 | if (err) { 112 | console.log('查询出错:' + err); 113 | } else { 114 | console.log('更新test1后的查询结果为:'); 115 | console.log(docs); // 更新test_update后的查询结果为空数组:[ ]; 116 | // 更新test1后的查询结果为: [ { name: 'test1', age: 11 } ] 117 | // 只能更新本来已存在的数据 118 | } 119 | }); 120 | } 121 | }); 122 | 123 | // step5: 数据删除 124 | var conditions = {name: 'test2'}; 125 | testModel.remove(conditions, function(error){ 126 | if(error) { 127 | console.log(error); 128 | } else { 129 | console.log('Delete success!'); 130 | testModel.find({name: 'test2'}, {name:1, age:1, _id:0}, function(err, docs){ 131 | if (err) { 132 | console.log('查询出错:' + err); 133 | } else { 134 | console.log('删除test2后的查询结果为:'); 135 | console.log(docs); // 删除test2后的查询结果为空数组:[ ]; 136 | } 137 | }); 138 | } 139 | }); 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /s5_server/api.js: -------------------------------------------------------------------------------- 1 | const db = require('./db') 2 | 3 | module.exports = function (app) { 4 | app.all("*", function(req, res, next) { 5 | // res.writeHead(200, { "Content-Type": "text/plain" }) 6 | // db.init(next) 7 | next() 8 | }); 9 | app.get('/api/user/login', function (req, res) { 10 | // 对发来的登录数据进行验证 11 | if (!req.query.name) { 12 | res.json({code: 600, msg:'name 不能为空!'}) 13 | return 14 | } 15 | if (!req.query.pwd) { 16 | res.json({code: 600, msg:'pwd 不能为空!'}) 17 | return 18 | } 19 | db.userModel.findOne({name: req.query.name}, function(err, doc){ 20 | if (err) { 21 | console.log('查询出错:' + err); 22 | } else { 23 | if (!doc) { 24 | res.json({code: 700, msg:'不存在该用户名:' + req.query.name}) 25 | return 26 | } else { 27 | if (req.query.pwd != doc.pwd) { 28 | res.json({code: 700, msg:'密码不正确!'}) 29 | return 30 | } else { 31 | res.json({code: 700, msg:'密码正确,登录成功'}) 32 | return 33 | } 34 | } 35 | 36 | } 37 | }) 38 | // 查询数据库验证账号、密码 39 | // 返回登录状态 40 | // res.send(JSON.stringify({code: 200, data: {account: 'guojc', pass: 111111}})) 41 | }) 42 | app.get('/api/user/register', function (req, res) { 43 | // 对发来的注册数据进行验证 44 | // 查询数据库验证注册账号、密码 45 | console.log(req.url) 46 | db.userModel.create({ 47 | name: 'guojctest1', 48 | pwd: 111111 49 | }, function (err, doc) { 50 | if (err) { 51 | res.end('err:' + err) 52 | } else { 53 | // console.log(doc) 54 | res.send(doc) 55 | } 56 | }) 57 | // 返回注册状态 58 | // res.send(JSON.stringify({code: 200, data: {account: 'guojcres', pass: 111111}})) 59 | }) 60 | app.get('*', function(req, res){ 61 | res.end('404') 62 | }) 63 | } -------------------------------------------------------------------------------- /s5_server/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const database = mongoose.connect('mongodb://127.0.0.1:27017/test_nodeVue') 5 | database.connection.on('error', function(error){ 6 | console.log('数据库test_nodeVue连接失败:' + error) 7 | return 8 | }) 9 | database.connection.once('open', function(){ 10 | console.log('数据库test_nodeVue连接成功') 11 | // callback() 12 | }) 13 | 14 | // const init = function (callback) { 15 | // // const mongoose = require('mongoose') 16 | // database = mongoose.connect('mongodb://127.0.0.1:27017/test_nodeVue') 17 | // database.connection.on('error', function(error){ 18 | // console.log('数据库test_nodeVue连接失败:' + error) 19 | // // return 20 | // }) 21 | // database.connection.on('open', function(){ 22 | // console.log('数据库test_nodeVue连接成功') 23 | // callback() 24 | // }) 25 | // } 26 | 27 | const userSchema = new Schema({ 28 | name: {type: String}, 29 | pwd: {type: String}, 30 | time: {type: Date, default: Date.now} 31 | }) 32 | 33 | const db = { 34 | // init: init, 35 | userModel: database.model('userModel', userSchema) 36 | } 37 | 38 | module.exports = db 39 | -------------------------------------------------------------------------------- /s5_server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | 4 | const api = require('./api') 5 | api(app) 6 | 7 | app.listen(8889) -------------------------------------------------------------------------------- /s5_server/newDb.js: -------------------------------------------------------------------------------- 1 | // 全局安装了node驱动库:cnpm install mongodb 2 | const MongoClient = require('mongodb').MongoClient; 3 | 4 | const url_test = 'mongodb://localhost:27017/test_node'; 5 | 6 | MongoClient.connect(url_test, function(err, db) { 7 | console.log('Connected successly to server.'); 8 | }); -------------------------------------------------------------------------------- /s5_server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "s5_server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "express": "^4.15.3", 8 | "mongoose": "^4.10.6" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "author": "", 15 | "license": "ISC" 16 | } 17 | -------------------------------------------------------------------------------- /server/api.js: -------------------------------------------------------------------------------- 1 | const db = require('./db') 2 | 3 | module.exports = function (app) { 4 | app.all("*", function(req, res, next) { 5 | // res.writeHead(200, { "Content-Type": "text/plain", "Access-Control-Allow-Origin":"*" }) 6 | // res.header('Access-Control-Allow-Origin', '*') 7 | // res.header('Access-Control-Allow-Headers', 'X-Requested-With') 8 | // res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS') 9 | // res.header("X-Powered-By",' 3.2.1') 10 | // res.header("Content-Type", "application/json;charset=utf-8") 11 | // next() 12 | // res.header('Access-Control-Allow-Origin', '*'); 13 | // res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With"); 14 | // res.header("Access-Control-Allow-Methods","*"); 15 | 16 | next(); 17 | // if (req.method == 'OPTIONS') { 18 | // res.send(200); 19 | // } 20 | // else { 21 | // next(); 22 | // } 23 | }); 24 | // api login 25 | app.get('/api/user/login', function (req, res) { 26 | // 对发来的登录数据进行验证 27 | if (!req.query.name) { 28 | // res.writeHead('Access-Control-Allow-Origin', '*'); 29 | // res.writeHead('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild, x-access-token'); 30 | // res.writeHead('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS'); 31 | // res.writeHead('Access-Control-Allow-Credentials', 'true'); 32 | res.json({code: 600, msg:'name 不能为空!'}) 33 | return 34 | } 35 | if (!req.query.pwd) { 36 | res.json({code: 600, msg:'pwd 不能为空!'}) 37 | return 38 | } 39 | db.userModel.findOne({name: req.query.name}, function(err, doc){ 40 | if (err) { 41 | console.log('查询出错:' + err); 42 | res.json({code: 700, msg:'查询出错:' + err}) 43 | return 44 | } else { 45 | if (!doc) { 46 | res.json({code: 700, msg:'不存在该用户名:' + req.query.name}) 47 | return 48 | } else { 49 | if (req.query.pwd != doc.pwd) { 50 | res.json({code: 700, msg:'密码不正确!'}) 51 | return 52 | } else { 53 | res.json({code: 200, msg:'密码正确,登录成功'}) 54 | return 55 | } 56 | } 57 | 58 | } 59 | }) 60 | }) 61 | // api register 62 | app.get('/api/user/register', function (req, res) { 63 | // 对发来的注册数据进行验证 64 | let name = req.query.name 65 | let pwd = req.query.pwd 66 | if (!name) { 67 | res.json({code: 600, msg:'name 不能为空!'}) 68 | return 69 | } 70 | if (!pwd) { 71 | res.json({code: 600, msg:'pwd 不能为空!'}) 72 | return 73 | } 74 | // 查询数据库验证注册账号、密码 75 | // 是否存在账号 76 | db.userModel.findOne({name: req.query.name}, function(err, doc){ 77 | if (err) { 78 | console.log('查询出错:' + err); 79 | res.json({code: 700, msg:'查询出错:' + err}) 80 | return 81 | } else { 82 | if (doc) { 83 | res.json({code: 700, msg:'该用户名名已经被注册:' + name}) 84 | return 85 | } else { 86 | db.userModel.create({ 87 | name: name, 88 | pwd: pwd 89 | }, function (err, doc) { 90 | if (err) { 91 | res.end('注册失败:' + err) 92 | } else { 93 | res.json({code: 200, msg:'用户注册成功:' + name}) 94 | return 95 | } 96 | }) 97 | } 98 | 99 | } 100 | }) 101 | // 返回注册状态 102 | // res.send(JSON.stringify({code: 200, data: {account: 'guojcres', pass: 111111}})) 103 | }) 104 | // api index 105 | app.get('/api/goods/index', function (req, res) { 106 | let temai = [], 107 | rexiao = [], 108 | jingpin = []; 109 | // // 1.temai 110 | // db.goodsModel.find( 111 | // {brand_status: "temai"}, 112 | // {brand_id:1, brand_name:1, brand_price:1, brand_pic:1, _id:0}, 113 | // {limit: 3}, 114 | // function(err, doc){ 115 | // if (err) { 116 | // console.log('temai find error!'); 117 | // } else { 118 | // if (!doc) { 119 | // temai = []; 120 | // } else { 121 | // temai = doc; 122 | // } 123 | // } 124 | // }) 125 | // // 2.rexiao 126 | // db.goodsModel.find( 127 | // {brand_status: "rexiao"}, 128 | // {brand_id:1, brand_name:1, brand_desc:1, brand_pic:1, _id:0}, 129 | // {limit: 3}, 130 | // function(err, doc){ 131 | // if (err) { 132 | // console.log('rexiao find error!'); 133 | // } else { 134 | // if (!doc) { 135 | // rexiao = []; 136 | // } else { 137 | // rexiao = doc; 138 | // } 139 | // } 140 | // }) 141 | // // 3.jingpin 142 | // db.goodsModel.find( 143 | // {brand_status: "jingpin"}, 144 | // {brand_id:1, brand_name:1, brand_price:1, brand_pic:1, _id:0}, 145 | // {limit: 4}, 146 | // function(err, doc){ 147 | // if (err) { 148 | // console.log('jingpin find error!'); 149 | // } else { 150 | // if (!doc) { 151 | // jingpin = []; 152 | // } else { 153 | // jingpin = doc; 154 | // // res 155 | // res.json({code: 200, msg:'', data: {"temai": temai, "rexiao": rexiao, "jingpin": jingpin}}) 156 | // return 157 | // } 158 | // } 159 | // }) 160 | 161 | // 异步操作,未解决 162 | // 1.temai 163 | // db.goodsModel.find( 164 | // {brand_status: "temai"}, 165 | // {brand_id:1, brand_name:1, brand_price:1, brand_pic:1, _id:0}, 166 | // {limit: 3}, 167 | // function(err, doc){ 168 | // if (err) { 169 | // console.log('temai find error!'); 170 | // } else { 171 | // if (!doc) { 172 | // temai = []; 173 | // } else { 174 | // temai = doc; 175 | // } 176 | // } 177 | // }) 178 | // .then( () => { 179 | // // 2.rexiao 180 | // db.goodsModel.find( 181 | // {brand_status: "rexiao"}, 182 | // {brand_id:1, brand_name:1, brand_desc:1, brand_pic:1, _id:0}, 183 | // {limit: 3}, 184 | // function(err, doc){ 185 | // if (err) { 186 | // console.log('rexiao find error!'); 187 | // } else { 188 | // if (!doc) { 189 | // rexiao = []; 190 | // } else { 191 | // rexiao = doc; 192 | // } 193 | // } 194 | // }) 195 | // .then( () => { 196 | // // 3.jingpin 197 | // db.goodsModel.find( 198 | // {brand_status: "jingpin"}, 199 | // {brand_id:1, brand_name:1, brand_price:1, brand_pic:1, _id:0}, 200 | // {limit: 4}, 201 | // function(err, doc){ 202 | // if (err) { 203 | // console.log('jingpin find error!'); 204 | // } else { 205 | // if (!doc) { 206 | // jingpin = []; 207 | // } else { 208 | // jingpin = doc; 209 | // } 210 | // } 211 | // }) 212 | // .then( () => { 213 | // // res 214 | // res.json({code: 200, msg:'', data: {"temai": temai, "rexiao": rexiao, "jingpin": jingpin}}) 215 | // return 216 | // }) 217 | // }) 218 | // }) 219 | // .catch( (err) => { 220 | // res.json({code: 200, msg:'', data: {"temai": temai, "rexiao": rexiao, "jingpin": jingpin}}) 221 | // return 222 | // }) 223 | 224 | // promise 解决 225 | // temai 226 | const getTemai = new Promise((resolve,reject) => { 227 | db.goodsModel.find( 228 | {brand_status: "temai"}, 229 | {brand_id:1, brand_name:1, brand_price:1, brand_pic:1, brand_status:1, _id:0}, 230 | {limit: 3}, 231 | function(err, doc){ 232 | if (err) { 233 | console.log('temai find error!') 234 | reject('reject temai') 235 | } else { 236 | if (!doc) { 237 | temai = []; 238 | } else { 239 | temai = doc; 240 | } 241 | resolve(temai) 242 | } 243 | }) 244 | }) 245 | // rexiao 246 | const getRexiao = new Promise((resolve,reject) => { 247 | db.goodsModel.find( 248 | {brand_status: "rexiao"}, 249 | {brand_id:1, brand_name:1, brand_desc:1, brand_pic:1, brand_status:1, _id:0}, 250 | {limit: 3}, 251 | function(err, doc){ 252 | if (err) { 253 | console.log('rexiao find error!'); 254 | reject('reject rexiao') 255 | } else { 256 | if (!doc) { 257 | rexiao = []; 258 | } else { 259 | rexiao = doc; 260 | } 261 | resolve(rexiao) 262 | } 263 | }) 264 | }) 265 | // jingpin 266 | const getJingpin = new Promise((resolve,reject) => { 267 | db.goodsModel.find( 268 | {brand_status: "jingpin"}, 269 | {brand_id:1, brand_name:1, brand_price:1, brand_pic:1, brand_status:1, _id:0}, 270 | {limit: 4}, 271 | function(err, doc){ 272 | if (err) { 273 | console.log('jingpin find error!') 274 | reject('reject jingpin') 275 | } else { 276 | if (!doc) { 277 | jingpin = [] 278 | } else { 279 | jingpin = doc 280 | } 281 | resolve(jingpin) 282 | } 283 | }) 284 | }) 285 | 286 | const p_all = Promise.all([getTemai, getRexiao, getJingpin]) 287 | 288 | p_all.then( (suc) => { 289 | let data = { 290 | "temai": suc[0], 291 | "rexiao": suc[1], 292 | "jingpin": suc[2] 293 | } 294 | res.json({code: 200, msg:'', data: data}) 295 | return 296 | }).catch( (err) => { 297 | console.log('err all:' + err) 298 | res.json({code: 600, msg:'查询出错', data: data}) 299 | return 300 | }) 301 | }) 302 | // 精品下拉加载更多api index/jingpin 303 | app.get('/api/goods/index/jingpin', function (req, res) { 304 | let nowLength = parseInt(req.query.nowLength) 305 | db.goodsModel.find( 306 | {brand_status: "jingpin"}, 307 | {brand_id: 1, brand_name: 1, brand_price: 1, brand_pic: 1, _id: 0}, 308 | {limit: 4, skip:nowLength}, 309 | function (err, doc) { 310 | if (err) { 311 | console.log('jingpin find error!'); 312 | console.log(err) 313 | } else { 314 | if (!doc) { 315 | // res 316 | res.json({code: 600, msg: '没有了', data: ''}) 317 | return 318 | } else { 319 | // res 加载效果,故意延时1s 320 | setTimeout( ()=> { 321 | res.json({code: 200, msg: '', data: doc}) 322 | return 323 | }, 1000) 324 | } 325 | } 326 | } 327 | ) 328 | }) 329 | // api cate 330 | app.get('/api/goods/cate', function (req, res) { 331 | db.goodsModel.find({}, function(err, doc){ 332 | if (err) { 333 | console.log('查询出错:' + err); 334 | res.json({code: 700, msg:'查询出错:' + err}) 335 | return 336 | } else { 337 | if (!doc) { 338 | res.json({code: 600, msg:'没有商品', data: doc}) 339 | return 340 | } else { 341 | res.json({code: 200, msg:'', data: doc}) 342 | return 343 | } 344 | 345 | } 346 | }) 347 | }) 348 | // api detail 349 | app.get('/api/goods/detail', function (req, res) { 350 | let brand_id = req.query.brand_id 351 | db.goodsModel.findOne({brand_id: brand_id}, {__v: 0, _id: 0}, function(err, doc){ 352 | if (err) { 353 | console.log('查询出错:' + err); 354 | res.json({code: 700, msg:'查询出错:' + err}) 355 | return 356 | } else { 357 | if (!doc) { 358 | res.json({code: 600, msg:'没有商品', data: doc}) 359 | return 360 | } else { 361 | res.json({code: 200, msg:'', data: doc}) 362 | return 363 | } 364 | } 365 | }) 366 | }) 367 | // api addToCart 368 | app.get('/api/goods/addToCart', function (req, res) { 369 | let brand_id = req.query.brand_id 370 | let name = req.query.name 371 | let newCart = req.query 372 | db.cartsModel.update({brand_id: brand_id, name: name}, {$set:newCart}, {upsert:true}, function(err){ 373 | if (err) { 374 | console.log('加入购物车失败:' + err); 375 | res.json({code: 700, msg:'加入购物车失败:' + err}) 376 | return 377 | } else { 378 | // add 379 | res.json({code: 200, msg:'加入购物车成功'}) 380 | return 381 | } 382 | }) 383 | }) 384 | // api carts 385 | app.get('/api/goods/carts', function (req, res) { 386 | let name = req.query.name 387 | db.cartsModel.find({name: name}, {__v: 0, _id: 0}, function(err, doc){ 388 | if (err) { 389 | console.log('购物车查询出错:' + err); 390 | res.json({code: 700, msg:'购物车查询出错:' + err}) 391 | return 392 | } else { 393 | if (!doc) { 394 | res.json({code: 600, msg:'购物车为空', data: doc}) 395 | return 396 | } else { 397 | res.json({code: 200, msg:'购物车返回成功', data: doc}) 398 | return 399 | } 400 | } 401 | }) 402 | }) 403 | // api delectCart 404 | app.get('/api/goods/delectCart', function (req, res) { 405 | let brand_id = req.query.brand_id 406 | let name = req.query.name 407 | db.cartsModel.remove({brand_id: brand_id, name: name}, function(err){ 408 | if (err) { 409 | console.log('购物车删除:' + err); 410 | res.json({code: 700, msg:'购物车删除:' + err}) 411 | return 412 | } else { 413 | // add 414 | res.json({code: 200, msg:'购物车删除成功'}) 415 | return 416 | } 417 | }) 418 | }) 419 | 420 | app.get('*', function(req, res){ 421 | res.end('404') 422 | }) 423 | } -------------------------------------------------------------------------------- /server/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | const initGoods = require('./initGoods.json') 4 | const initCarts = require('./initCarts.json') 5 | 6 | // 用户信息的数据结构模型 7 | const userSchema = new Schema({ 8 | name: {type: String}, 9 | pwd: {type: String}, 10 | time: {type: Date, default: Date.now} 11 | }) 12 | // 商品的的数据结构模型 13 | const goodsSchema = new Schema({ 14 | brand_id: Number, 15 | brand_cate: String, 16 | brand_cateName: String, 17 | brand_status: String, 18 | brand_name: String, 19 | brand_price: Number, 20 | brand_desc: String, 21 | brand_pic: String 22 | }) 23 | // 购物车的的数据结构模型 24 | const cartsSchema = new Schema({ 25 | name: String, 26 | brand_id: Number, 27 | brand_cate: String, 28 | brand_name: String, 29 | brand_price: Number, 30 | brand_desc: String, 31 | brand_pic: String, 32 | cart_num: Number, 33 | cart_isSelect: Boolean 34 | }); 35 | 36 | mongoose.Promise = global.Promise; 37 | const database = mongoose.connect('mongodb://127.0.0.1:27017/test_nodeVue') 38 | database.connection.on('error', function(error){ 39 | console.log('数据库test_nodeVue连接失败:' + error) 40 | return 41 | }) 42 | database.connection.once('open', function(){ 43 | console.log('数据库test_nodeVue连接成功') 44 | // 初始化数据库 45 | initData(); 46 | }) 47 | 48 | const db = { 49 | userModel: database.model('userModel', userSchema), 50 | goodsModel: database.model('goodsModel', goodsSchema), 51 | cartsModel: database.model('cartsModel', cartsSchema) 52 | } 53 | 54 | const initData = function () { 55 | // 初始化商品goods 56 | db.goodsModel.find({}, function(err, doc){ 57 | if (err) { 58 | console.log('initData出错:' + err); 59 | } else if (!doc.length) { 60 | console.log('db goodsModel open first time'); 61 | // 初始化数据,遍历插入;先打印出来看看 62 | initGoods.map(brand => { 63 | db.goodsModel.create(brand) 64 | }) 65 | // console.log(initGoods) 66 | 67 | } else { 68 | console.log('db open not first time'); 69 | } 70 | }) 71 | // 为用户name15011760730初始化购物车内容 72 | db.cartsModel.find({}, function(err, doc){ 73 | if (err) { 74 | console.log('initData出错:' + err); 75 | } else if (!doc.length) { 76 | console.log('db cartsModel open first time'); 77 | // 初始化数据,遍历插入;先打印出来看看 78 | initCarts.map(brand => { 79 | db.cartsModel.create(brand) 80 | }) 81 | // console.log(initGoods) 82 | 83 | } else { 84 | console.log('db open not first time'); 85 | } 86 | }) 87 | } 88 | 89 | module.exports = db 90 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | 4 | const api = require('./api') 5 | api(app) 6 | 7 | app.listen(8889) -------------------------------------------------------------------------------- /server/initCarts.json: -------------------------------------------------------------------------------- 1 | [ { "name": "15011760730", "brand_id": 10002, "brand_cate": "男装", "brand_name": "商品名字2", "brand_price": 120, "brand_desc": "商品描述商品描述商品描述商品描述", "brand_pic": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg", "cart_num": 1, "cart_isSelect": false }, { "name": "15011760730", "brand_id": 10003, "brand_cate": "男装", "brand_name": "商品名字3", "brand_price": 120, "brand_desc": "商品描述商品描述商品描述商品描述", "brand_pic": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg", "cart_num": 1, "cart_isSelect": false } ] -------------------------------------------------------------------------------- /server/initGoods.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "brand_id": 10001, 4 | "brand_cate": "type_man", 5 | "brand_cateName": "男装", 6 | "brand_status": "temai", 7 | "brand_name": "商品名字1", 8 | "brand_price": 110, 9 | "brand_desc": "商品描述商品描述商品描述商品描述", 10 | "brand_pic": "http://ohe5avf3y.bkt.clouddn.com/pro/vue/vue-shop/vue-proj-goods.jpg" 11 | }, 12 | { 13 | "brand_id": 10002, 14 | "brand_cate": "type_man", 15 | "brand_cateName": "男装", 16 | "brand_status": "temai", 17 | "brand_name": "商品名字1", 18 | "brand_price": 120, 19 | "brand_desc": "商品描述商品描述商品描述商品描述", 20 | "brand_pic": "http://s11.mogucdn.com/mlcdn/c45406/170602_52gblji4hd3ad0i9eeghf9kebg482_640x960.jpg_240x308.v1cAC.70.webp" 21 | }, 22 | { 23 | "brand_id": 10003, 24 | "brand_cate": "type_man", 25 | "brand_cateName": "男装", 26 | "brand_status": "temai", 27 | "brand_name": "商品名字1", 28 | "brand_price": 130, 29 | "brand_desc": "商品描述商品描述商品描述商品描述", 30 | "brand_pic": "http://s2.mogucdn.com/mlcdn/c45406/170603_2lbjee355ek1675be7jb0j5lcl75a_640x960.png_240x308.v1cAC.70.webp" 31 | }, 32 | { 33 | "brand_id": 10004, 34 | "brand_cate": "type_man", 35 | "brand_cateName": "男装", 36 | "brand_status": "rexiao", 37 | "brand_name": "商品名字1", 38 | "brand_price": 140, 39 | "brand_desc": "商品描述商品描述商品描述商品描述", 40 | "brand_pic": "http://s3.mogucdn.com/mlcdn/c45406/170604_37d4jkh07056ce39ig0f978kki6lc_640x960.jpg_240x308.v1cAC.70.webp" 41 | }, 42 | { 43 | "brand_id": 10005, 44 | "brand_cate": "type_man", 45 | "brand_cateName": "男装", 46 | "brand_status": "rexiao", 47 | "brand_name": "商品名字1", 48 | "brand_price": 150, 49 | "brand_desc": "商品描述商品描述商品描述商品描述", 50 | "brand_pic": "http://s3.mogucdn.com/mlcdn/17f85e/170506_5j6jff5k457k0h43h33gid14a7j93_640x960.jpg_240x308.v1cAC.70.webp" 51 | }, 52 | { 53 | "brand_id": 10006, 54 | "brand_cate": "type_man", 55 | "brand_cateName": "男装", 56 | "brand_status": "rexiao", 57 | "brand_name": "商品名字1", 58 | "brand_price": 160, 59 | "brand_desc": "商品描述商品描述商品描述商品描述", 60 | "brand_pic": "http://s2.mogucdn.com/mlcdn/c45406/170507_14l6dgfe1a33ffj4keiahlc1gaeed_640x960.jpg_240x308.v1cAC.70.webp" 61 | }, 62 | { 63 | "brand_id": 10007, 64 | "brand_cate": "type_man", 65 | "brand_cateName": "男装", 66 | "brand_status": "jingpin", 67 | "brand_name": "商品名字1", 68 | "brand_price": 170, 69 | "brand_desc": "商品描述商品描述商品描述商品描述", 70 | "brand_pic": "http://s2.mogucdn.com/mlcdn/c45406/170623_380bf1ela7jb2f65ggadh2d1ll9bg_640x960.jpg_240x308.v1cAC.70.webp" 71 | }, 72 | { 73 | "brand_id": 10008, 74 | "brand_cate": "type_man", 75 | "brand_cateName": "男装", 76 | "brand_status": "jingpin", 77 | "brand_name": "商品名字1", 78 | "brand_price": 180, 79 | "brand_desc": "商品描述商品描述商品描述商品描述", 80 | "brand_pic": "http://s3.mogucdn.com/mlcdn/c45406/170427_8ef6fhgg8kl08iil5809dffc1de8c_640x960.jpg_240x308.v1cAC.70.webp" 81 | }, 82 | { 83 | "brand_id": 10009, 84 | "brand_cate": "type_man", 85 | "brand_cateName": "男装", 86 | "brand_status": "jingpin", 87 | "brand_name": "商品名字1", 88 | "brand_price": 190, 89 | "brand_desc": "商品描述商品描述商品描述商品描述", 90 | "brand_pic": "http://s2.mogucdn.com/mlcdn/c45406/170622_4ehkgj2ag7el2ckb59d3h586bb0ja_640x960.jpg_240x308.v1cAC.70.webp" 91 | }, 92 | { 93 | "brand_id": 10010, 94 | "brand_cate": "type_man", 95 | "brand_cateName": "男装", 96 | "brand_status": "jingpin", 97 | "brand_name": "商品名字1", 98 | "brand_price": 200, 99 | "brand_desc": "商品描述商品描述商品描述商品描述", 100 | "brand_pic": "http://s17.mogucdn.com/mlcdn/c45406/170421_0eg9hh8gh6jj37f07h3j2k5jah9e2_640x960.jpg_240x308.v1cAC.70.webp" 101 | }, 102 | { 103 | "brand_id": 10011, 104 | "brand_cate": "type_man", 105 | "brand_cateName": "男装", 106 | "brand_status": "jingpin", 107 | "brand_name": "商品名字1", 108 | "brand_price": 220, 109 | "brand_desc": "商品描述商品描述商品描述商品描述", 110 | "brand_pic": "http://s2.mogucdn.com/mlcdn/c45406/170526_3b6a34eai87154g6i11a71ab5h3kb_640x960.jpg_240x308.v1cAC.70.webp" 111 | }, 112 | { 113 | "brand_id": 10012, 114 | "brand_cate": "type_man", 115 | "brand_cateName": "男装", 116 | "brand_status": "jingpin", 117 | "brand_name": "商品名字1", 118 | "brand_price": 230, 119 | "brand_desc": "商品描述商品描述商品描述商品描述", 120 | "brand_pic": "http://s3.mogucdn.com/mlcdn/17f85e/170510_6efkgcc5ec9a9g02d87fl028014k9_640x960.jpg_240x308.v1cAC.70.webp" 121 | }, 122 | { 123 | "brand_id": 10013, 124 | "brand_cate": "type_man", 125 | "brand_cateName": "男装", 126 | "brand_status": "jingpin", 127 | "brand_name": "商品名字1", 128 | "brand_price": 240, 129 | "brand_desc": "商品描述商品描述商品描述商品描述", 130 | "brand_pic": "http://s3.mogucdn.com/mlcdn/c45406/170703_4d7d9777201jfi7k5cbfdlg2l2581_640x960.jpg_240x308.v1cAC.70.webp" 131 | }, 132 | { 133 | "brand_id": 10014, 134 | "brand_cate": "type_man", 135 | "brand_cateName": "男装", 136 | "brand_status": "jingpin", 137 | "brand_name": "商品名字1", 138 | "brand_price": 250, 139 | "brand_desc": "商品描述商品描述商品描述商品描述", 140 | "brand_pic": "http://s2.mogucdn.com/mlcdn/c45406/170620_5llbg7ba6gjihcg0jedc16cb59j1i_640x960.jpg_240x308.v1cAC.70.webp" 141 | }, 142 | { 143 | "brand_id": 10015, 144 | "brand_cate": "type_man", 145 | "brand_cateName": "男装", 146 | "brand_status": "jingpin", 147 | "brand_name": "商品名字1", 148 | "brand_price": 260, 149 | "brand_desc": "商品描述商品描述商品描述商品描述", 150 | "brand_pic": "http://s3.mogucdn.com/mlcdn/c45406/170519_52k6ifbb318a1ii3j1he09ck8f323_800x1200.jpg_240x308.v1cAC.70.webp" 151 | }, 152 | { 153 | "brand_id": 10016, 154 | "brand_cate": "type_man", 155 | "brand_cateName": "男装", 156 | "brand_status": "jingpin", 157 | "brand_name": "商品名字1", 158 | "brand_price": 270, 159 | "brand_desc": "商品描述商品描述商品描述商品描述", 160 | "brand_pic": "http://s3.mogucdn.com/mlcdn/c45406/170421_0fb1c9d3j3hf0932i4l61ljb28deh_640x960.jpg_240x308.v1cAC.70.webp" 161 | }, 162 | { 163 | "brand_id": 10017, 164 | "brand_cate": "type_man", 165 | "brand_cateName": "男装", 166 | "brand_status": "jingpin", 167 | "brand_name": "商品名字1", 168 | "brand_price": 280, 169 | "brand_desc": "商品描述商品描述商品描述商品描述", 170 | "brand_pic": "http://s11.mogucdn.com/mlcdn/c45406/170627_867h059le4g5ci0b05k5887dlka0d_640x960.jpg_240x308.v1cAC.70.webp" 171 | }, 172 | { 173 | "brand_id": 10018, 174 | "brand_cate": "type_man", 175 | "brand_cateName": "男装", 176 | "brand_status": "jingpin", 177 | "brand_name": "商品名字1", 178 | "brand_price": 290, 179 | "brand_desc": "商品描述商品描述商品描述商品描述", 180 | "brand_pic": "http://s3.mogucdn.com/mlcdn/c45406/170623_2c0fe84793fbda75cd0je7gi74fdd_640x960.jpg_240x308.v1cAC.70.webp" 181 | }, 182 | { 183 | "brand_id": 10019, 184 | "brand_cate": "type_man", 185 | "brand_cateName": "男装", 186 | "brand_status": "jingpin", 187 | "brand_name": "商品名字1", 188 | "brand_price": 300, 189 | "brand_desc": "商品描述商品描述商品描述商品描述", 190 | "brand_pic": "http://s3.mogucdn.com/mlcdn/c45406/170426_14b064a69k4dgd61kd1fi6k1ig74g_640x960.png_240x308.v1cAC.70.webp" 191 | }, 192 | { 193 | "brand_id": 10020, 194 | "brand_cate": "type_man", 195 | "brand_cateName": "男装", 196 | "brand_status": "jingpin", 197 | "brand_name": "商品名字1", 198 | "brand_price": 201, 199 | "brand_desc": "商品描述商品描述商品描述商品描述", 200 | "brand_pic": "http://s2.mogucdn.com/mlcdn/c45406/170601_1c5fkcjibfc38ld6b935066ahd0ch_640x960.jpg_240x308.v1cAC.70.webp" 201 | } 202 | ] -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "s5_server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "express": "^4.15.3", 8 | "mongoose": "^4.10.6" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "author": "", 15 | "license": "ISC" 16 | } 17 | --------------------------------------------------------------------------------