├── .babelrc ├── .editorconfig ├── .gitignore ├── README.md ├── app.js ├── 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 ├── config ├── db.js ├── dev.env.js ├── index.js └── prod.env.js ├── data.js ├── demo ├── addMovie.gif ├── apiTest.png ├── build.png ├── demo.png ├── editMovie.gif ├── gitads.png ├── listMethods01.png ├── listMethods02.png ├── methodBtn.png ├── moviejs.png ├── project.png ├── proxyTabel.png ├── removeMovie.gif ├── router.png └── showDetail.gif ├── index.html ├── models └── movie.js ├── package-lock.json ├── package.json ├── router ├── index.js └── movie.js ├── src ├── App.vue ├── assets │ ├── favicon.ico │ ├── icon.css │ └── logo.png ├── components │ ├── Detail.vue │ └── List.vue ├── main.js └── router │ └── index.js └── static └── .gitkeep /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { "modules": false }], 4 | "stage-2" 5 | ], 6 | "plugins": ["transform-runtime"], 7 | "comments": false, 8 | "env": { 9 | "test": { 10 | "plugins": [ "istanbul" ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前后端分离示例 2 | 3 | 一个前后端分离的案例,前端 vuejs,后端 express, 数据库 mongodb。 4 | 使用 express 的提供api供前端调用,前端ajax请求进行对数据库的CURD操作。 5 | 6 | ## 前言 7 | 8 | 在学习前端开发的过程中了解到前后端分离这个概念 9 | [前后分离架构的探索之路](https://segmentfault.com/a/1190000003795517) 10 | [我们为什么要尝试前后端分离](https://segmentfault.com/a/1190000006240370) 11 | 因此决定小试身手,项目中主要使用到的框架和库. 12 | 13 | > vuejs vue-router muse-ui axios express mongoose mongodb...... 14 | 15 | ## 效果图 16 | 首页 17 | ![demo](./demo/demo.png) 18 | 添加电影 19 | ![addMovie](./demo/addMovie.gif) 20 | 更新电影信息 21 | ![editMovie](./demo/editMovie.gif) 22 | 展示电影详情 23 | ![showDetail](./demo/showDetail.gif) 24 | 删除电影 25 | ![removeMovie](./demo/removeMovie.gif) 26 | 27 | ## 开发环境 28 | 需要本地安装[node](https://nodejs.org/en/),[npm](https://www.npmjs.com/)或[yarn](https://yarnpkg.com/),[mongodb](https://www.mongodb.com/) 29 | 30 | ## 初始化 31 | 首先用vue-cli初始化项目目录 32 | ```bash 33 | vue init webpack my-project 34 | 35 | cd my-rpoject && npm install 36 | 37 | npm run dev 38 | ``` 39 | 看到8080端口出现vuejs的欢迎界面表示成功 40 | 41 | 接着把本地的mongodb服务跑起来,参考这篇[教程](https://segmentfault.com/a/1190000004868504) 42 | 43 | ## 后端开发 44 | - 官方文档 [express](http://www.expressjs.com.cn/) [mongoose](http://mongoosejs.com/docs/guide.html) 45 | 46 | 首先把后端的服务搭好,方便以后前端调用,使用npm安装express,mongoose等必须的依赖即可,暂时不考虑验证等东西. 47 | ```bash 48 | npm install express body-parser mongoose --save 49 | ``` 50 | 然后在项目根目录添加一个app.js,编写好启动express服务器的代码 51 | ``` 52 | const express = require('express') 53 | const app = express() 54 | app.use('/',(req,res) => { 55 | res.send('Yo!') 56 | }) 57 | app.listen(3000,() => { 58 | console.log('app listening on port 3000.') 59 | }) 60 | ``` 61 | 使用nodemon或babel-watch,方便开发 62 | ```bash 63 | npm install nodemon --save-dev 64 | 65 | nodemon app.js 66 | ``` 67 | 68 | 浏览器访问localhost:3000,出现res.send()的内容即表示成功. 69 | 70 | 然后添加需要的数据,新建一个models目录放数据模型,mongoose的每个数据model需要一个schema生成, 71 | 72 | 新建movie.js文件或者其他的数据模型,用来提供基础数据. 73 | 74 | ![movie.js](./demo/moviejs.png) 75 | 76 | 定义了title,poster,rating,introduction,created_at,update_at几个基本信息,最后将model导出即可. 77 | 78 | 接着用mongoose链接mongodb,在app.js里添加 79 | ``` 80 | const mongoose = require('mongoose') 81 | mongoose.connect('mongodb://localhost:27017/yourDbName') 82 | ``` 83 | 84 | 链接数据库成功后,可以用Robomongo可视化工具或者在CMD里输入mongo命令查看数据库. 85 | 86 | 接着将对数据CURD操作的几个路由写出来,新建router文件夹,新建index.js和movie.js分别对应首页路由,和对数据 87 | 88 | 操作的路由,如下. 89 | - 首页路由 [index.js](./router/index.js) 90 | - 对数据操作的路由 [movie.js](./router/movie.js) 91 | 92 | 最后将路由应用到app.js 93 | ``` 94 | ...... 95 | const index = require('./router/index') 96 | const movie = require('./router/movie') 97 | ...... 98 | app.use('/',index) 99 | app.use('/api',movie) 100 | ...... 101 | ``` 102 | 103 | 使用Postman进行测试,测试成功的话,后端服务基本上就完成了. 104 | ![测试](./demo/apiTest.png) 105 | 106 | ## 前端开发 107 | 首先安装必要的依赖,看自己喜欢选择. 108 | [muse-ui](https://museui.github.io/#/index) [axios](https://github.com/mzabriskie/axios) 109 | ```bash 110 | npm install muse-ui axios --save 111 | ``` 112 | 然后把不要的文件删除,在src/components目录新建主要的两个组件List,Detail. 113 | List就是首页的列表,Detail是电影的详细数据,然后把前端路由写出来,在src/router建立前端路由文件, 114 | 只有两个组件之间切换,然后把放到App.vue里面就可以了. 115 | 116 | 前端路由 117 | 118 | ![index.js](./demo/router.png) 119 | 120 | 数据获取,由于我们的express是在3000端口启动的,而前端开发在8080端口,由于跨域所以要配置好vue-cli的proxyTable 121 | 选项,位于config/index.js,改写proxyTable. 122 | 123 | ![proxyTable](./demo/proxyTabel.png) 124 | 125 | 这样当在前端用axios访问 '/api' 的时候,就会被代理到 'http://localhost:3000/api',从而获得需要的数据. 126 | 127 | 能够获取到数据之后就是编写界面了,由于用了muse-ui组件库,所以只要按着文档写一般不会错,要是不满意就自己搭界面. 128 | 129 | 主要就是用ajax访问后端对数据增删改查的路由,将这些操作都写在组件的methods对象里面,写好主要的方法后,将方法 130 | 131 | ...... 132 | 133 | ![listMethods01](./demo/listMethods01.png) 134 | ![listMethods02](./demo/listMethods02.png) 135 | 136 | ...... 137 | 138 | 用vuejs里的写法,绑定到对应的按钮上 139 | ``` 140 | @click="methodName" 141 | ``` 142 | ![methodBtn](./demo/methodBtn.png) 143 | 144 | 这样前端的开发就基本完成了. 145 | 146 | 147 | ## 结语 148 | 149 | 前端开发完成后,就可以用webpack打包了,注意将config/index.js文件里面的productionSourceMap设为false, 150 | 不然打包出来文件很大,最后用express.static中间件将webpack打包好的项目目录'dist'作为express静态文件服务的目录. 151 | 152 | ```bash 153 | npm run build 154 | ``` 155 | ![build](./demo/build.png) 156 | 157 | ``` 158 | app.use(express.static('dist')) 159 | ``` 160 | 161 | 最后案例完成后的目录结构就是这样. 162 | 163 | ![project](./demo/project.png) 164 | 165 | 166 | ## Build Setup 167 | 168 | ``` bash 169 | # install dependencies 170 | npm install 171 | 172 | # serve with hot reload at localhost:8080 173 | npm run dev 174 | 175 | # build for production with minification 176 | npm run build 177 | 178 | # build for production and view the bundle analyzer report 179 | npm run build --report 180 | 181 | # 后端开发 localhost:3000 182 | npm run server 183 | 184 | # webpack打包后,后端运行express静态目录'dist' 185 | npm run start 186 | 187 | ``` 188 | 189 | ## License 190 | 191 | [MIT](https://opensource.org/licenses/MIT) 192 | 193 | written by [xrr2016](https://github.com/xrr2016),欢迎issue,fork,star. 194 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const pkg = require('./package') 2 | const config = require('./config/db') 3 | const express = require('express') 4 | const favicon = require('serve-favicon') 5 | const bodyParser = require('body-parser') 6 | const mongoose = require('mongoose') 7 | const index = require('./router/index') 8 | const movie = require('./router/movie') 9 | 10 | mongoose.connect(config.mongodb) 11 | mongoose.Promise = global.Promise 12 | 13 | const app = express() 14 | const port = process.env.PORT || 3000 15 | 16 | app.use(bodyParser.json()) 17 | app.use(bodyParser.urlencoded({ extended: true })) 18 | app.use(favicon(__dirname + '/src/assets/favicon.ico')) 19 | app.use(express.static('dist')) 20 | app.use('/',index) 21 | app.use('/api',movie) 22 | 23 | app.listen(port, () => { 24 | console.log(`${pkg.name} listening on port ${port}`) 25 | }) 26 | 27 | module.exports = app 28 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | // https://github.com/shelljs/shelljs 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | var ora = require('ora') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var shell = require('shelljs') 10 | var webpack = require('webpack') 11 | var config = require('../config') 12 | var webpackConfig = require('./webpack.prod.conf') 13 | 14 | var spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) 18 | shell.rm('-rf', assetsPath) 19 | shell.mkdir('-p', assetsPath) 20 | shell.config.silent = true 21 | shell.cp('-R', 'static/*', assetsPath) 22 | shell.config.silent = false 23 | 24 | webpack(webpackConfig, function (err, stats) { 25 | spinner.stop() 26 | if (err) throw err 27 | process.stdout.write(stats.toString({ 28 | colors: true, 29 | modules: false, 30 | children: false, 31 | chunks: false, 32 | chunkModules: false 33 | }) + '\n\n') 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | 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 | name: 'npm', 17 | currentVersion: exec('npm --version'), 18 | versionRequirement: packageConfig.engines.npm 19 | } 20 | ] 21 | 22 | module.exports = function () { 23 | var warnings = [] 24 | for (var i = 0; i < versionRequirements.length; i++) { 25 | var mod = versionRequirements[i] 26 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 27 | warnings.push(mod.name + ': ' + 28 | chalk.red(mod.currentVersion) + ' should be ' + 29 | chalk.green(mod.versionRequirement) 30 | ) 31 | } 32 | } 33 | 34 | if (warnings.length) { 35 | console.log('') 36 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 37 | console.log() 38 | for (var i = 0; i < warnings.length; i++) { 39 | var warning = warnings[i] 40 | console.log(' ' + warning) 41 | } 42 | console.log() 43 | process.exit(1) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 = require('./webpack.dev.conf') 14 | 15 | // default port where dev server listens for incoming traffic 16 | var port = process.env.PORT || config.dev.port 17 | // automatically open browser, if not set will be false 18 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 19 | // Define HTTP proxies to your custom API backend 20 | // https://github.com/chimurai/http-proxy-middleware 21 | var proxyTable = config.dev.proxyTable 22 | 23 | var app = express() 24 | var compiler = webpack(webpackConfig) 25 | 26 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 27 | publicPath: webpackConfig.output.publicPath, 28 | quiet: true 29 | }) 30 | 31 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 32 | log: () => {} 33 | }) 34 | // force page reload when html-webpack-plugin template changes 35 | compiler.plugin('compilation', function (compilation) { 36 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 37 | hotMiddleware.publish({ action: 'reload' }) 38 | cb() 39 | }) 40 | }) 41 | 42 | // proxy api requests 43 | Object.keys(proxyTable).forEach(function (context) { 44 | var options = proxyTable[context] 45 | if (typeof options === 'string') { 46 | options = { target: options } 47 | } 48 | app.use(proxyMiddleware(options.filter || context, options)) 49 | }) 50 | 51 | // handle fallback for HTML5 history API 52 | app.use(require('connect-history-api-fallback')()) 53 | 54 | // serve webpack bundle output 55 | app.use(devMiddleware) 56 | 57 | // enable hot-reload and state-preserving 58 | // compilation error display 59 | app.use(hotMiddleware) 60 | 61 | // serve pure static assets 62 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 63 | app.use(staticPath, express.static('./static')) 64 | 65 | var uri = 'http://localhost:' + port 66 | 67 | devMiddleware.waitUntilValid(function () { 68 | console.log('> Listening at ' + uri + '\n') 69 | }) 70 | 71 | module.exports = app.listen(port, function (err) { 72 | if (err) { 73 | console.log(err) 74 | return 75 | } 76 | 77 | // when env is testing, don't need open it 78 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 79 | opn(uri) 80 | } 81 | }) 82 | -------------------------------------------------------------------------------- /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 | // generate loader string to be used with extract text plugin 15 | function generateLoaders (loaders) { 16 | var sourceLoader = loaders.map(function (loader) { 17 | var extraParamChar 18 | if (/\?/.test(loader)) { 19 | loader = loader.replace(/\?/, '-loader?') 20 | extraParamChar = '&' 21 | } else { 22 | loader = loader + '-loader' 23 | extraParamChar = '?' 24 | } 25 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 26 | }).join('!') 27 | 28 | // Extract CSS when that option is specified 29 | // (which is the case during production build) 30 | if (options.extract) { 31 | return ExtractTextPlugin.extract({ 32 | use: sourceLoader, 33 | fallback: 'vue-style-loader' 34 | }) 35 | } else { 36 | return ['vue-style-loader', sourceLoader].join('!') 37 | } 38 | } 39 | 40 | // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html 41 | return { 42 | css: generateLoaders(['css']), 43 | postcss: generateLoaders(['css']), 44 | less: generateLoaders(['css', 'less']), 45 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 46 | scss: generateLoaders(['css', 'sass']), 47 | stylus: generateLoaders(['css', 'stylus']), 48 | styl: generateLoaders(['css', 'stylus']) 49 | } 50 | } 51 | 52 | // Generate loaders for standalone style files (outside of .vue) 53 | exports.styleLoaders = function (options) { 54 | var output = [] 55 | var loaders = exports.cssLoaders(options) 56 | for (var extension in loaders) { 57 | var loader = loaders[extension] 58 | output.push({ 59 | test: new RegExp('\\.' + extension + '$'), 60 | loader: loader 61 | }) 62 | } 63 | return output 64 | } 65 | -------------------------------------------------------------------------------- /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 | postcss: [ 13 | require('autoprefixer')({ 14 | browsers: ['last 2 versions'] 15 | }) 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /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 | modules: [ 24 | resolve('src'), 25 | resolve('node_modules') 26 | ], 27 | alias: { 28 | 'src': resolve('src'), 29 | 'assets': resolve('src/assets'), 30 | 'components': resolve('src/components') 31 | } 32 | }, 33 | module: { 34 | rules: [ 35 | { 36 | test: /\.vue$/, 37 | loader: 'vue-loader', 38 | options: vueLoaderConfig 39 | }, 40 | { 41 | test: /\.js$/, 42 | loader: 'babel-loader', 43 | include: [resolve('src'), resolve('test')] 44 | }, 45 | { 46 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 47 | loader: 'url-loader', 48 | query: { 49 | limit: 10000, 50 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 51 | } 52 | }, 53 | { 54 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 55 | loader: 'url-loader', 56 | query: { 57 | limit: 10000, 58 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 59 | } 60 | } 61 | ] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 HtmlWebpackPlugin = require('html-webpack-plugin') 8 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 9 | var env = config.build.env 10 | 11 | var webpackConfig = merge(baseWebpackConfig, { 12 | module: { 13 | rules: utils.styleLoaders({ 14 | sourceMap: config.build.productionSourceMap, 15 | extract: true 16 | }) 17 | }, 18 | devtool: config.build.productionSourceMap ? '#source-map' : false, 19 | output: { 20 | path: config.build.assetsRoot, 21 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 22 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 23 | }, 24 | plugins: [ 25 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 26 | new webpack.DefinePlugin({ 27 | 'process.env': env 28 | }), 29 | new webpack.optimize.UglifyJsPlugin({ 30 | compress: { 31 | warnings: false 32 | }, 33 | sourceMap: true 34 | }), 35 | // extract css into its own file 36 | new ExtractTextPlugin({ 37 | filename: utils.assetsPath('css/[name].[contenthash].css') 38 | }), 39 | // generate dist index.html with correct asset hash for caching. 40 | // you can customize output by editing /index.html 41 | // see https://github.com/ampedandwired/html-webpack-plugin 42 | new HtmlWebpackPlugin({ 43 | filename: config.build.index, 44 | template: 'index.html', 45 | inject: true, 46 | minify: { 47 | removeComments: true, 48 | collapseWhitespace: true, 49 | removeAttributeQuotes: true 50 | // more options: 51 | // https://github.com/kangax/html-minifier#options-quick-reference 52 | }, 53 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 54 | chunksSortMode: 'dependency' 55 | }), 56 | // split vendor js into its own file 57 | new webpack.optimize.CommonsChunkPlugin({ 58 | name: 'vendor', 59 | minChunks: function (module, count) { 60 | // any required modules inside node_modules are extracted to vendor 61 | return ( 62 | module.resource && 63 | /\.js$/.test(module.resource) && 64 | module.resource.indexOf( 65 | path.join(__dirname, '../node_modules') 66 | ) === 0 67 | ) 68 | } 69 | }), 70 | // extract webpack runtime and module manifest to its own file in order to 71 | // prevent vendor hash from being updated whenever app bundle is updated 72 | new webpack.optimize.CommonsChunkPlugin({ 73 | name: 'manifest', 74 | chunks: ['vendor'] 75 | }) 76 | ] 77 | }) 78 | 79 | if (config.build.productionGzip) { 80 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 81 | 82 | webpackConfig.plugins.push( 83 | new CompressionWebpackPlugin({ 84 | asset: '[path].gz[query]', 85 | algorithm: 'gzip', 86 | test: new RegExp( 87 | '\\.(' + 88 | config.build.productionGzipExtensions.join('|') + 89 | ')$' 90 | ), 91 | threshold: 10240, 92 | minRatio: 0.8 93 | }) 94 | ) 95 | } 96 | 97 | if (config.build.bundleAnalyzerReport) { 98 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 99 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 100 | } 101 | 102 | module.exports = webpackConfig 103 | -------------------------------------------------------------------------------- /config/db.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mongodb : "mongodb://localhost:27017/movielist" 3 | } 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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: false, 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: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: { 31 | '/api': { 32 | target: 'http://localhost:3000', 33 | changeOrigin: true 34 | } 35 | }, 36 | // CSS Sourcemaps off by default because relative paths are "buggy" 37 | // with this option, according to the CSS-Loader README 38 | // (https://github.com/webpack/css-loader#sourcemaps) 39 | // In our experience, they generally work as expected, 40 | // just be aware of this issue when enabling this option. 41 | cssSourceMap: false 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /data.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const config = require('./config/db') 3 | const mongoose = require('mongoose') 4 | mongoose.connect(config.mongodb) 5 | const movieSchema = new mongoose.Schema({ 6 | title: { 7 | type: String, 8 | required: true 9 | }, 10 | original_title: String, 11 | rating: Number, 12 | genres: Array, 13 | casts: Array, 14 | directors: Array, 15 | year: Number, 16 | id: String, 17 | image: String 18 | }) 19 | const Movie = mongoose.model("Movie", movieSchema) 20 | 21 | const jsonBird = "https://bird.ioliu.cn/v1?url=" 22 | const doubanMovie = "http://api.douban.com/v2/movie/top250?count=" 23 | 24 | function fetchData(count) { 25 | const url = `${jsonBird}${doubanMovie}${count}` 26 | axios.get(url) 27 | .then(res => { 28 | const subjects = res.data.subjects 29 | subjects.map(subject => { 30 | let movie = { 31 | title: subject.title, 32 | original_title: subject.original_title, 33 | rating: subject.rating.average, 34 | genres: subject.genres, 35 | casts: subject.casts, 36 | directors: subject.directors, 37 | year: subject.year, 38 | id: subject.id, 39 | image: subject.images.small 40 | } 41 | Movie.create(movie, (err, movie) => { 42 | if (err) { console.log(err) } 43 | console.log(movie.title, '--------') 44 | }) 45 | }) 46 | }) 47 | .catch((err) => { 48 | throw err 49 | }) 50 | } 51 | 52 | fetchData(5) 53 | -------------------------------------------------------------------------------- /demo/addMovie.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/addMovie.gif -------------------------------------------------------------------------------- /demo/apiTest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/apiTest.png -------------------------------------------------------------------------------- /demo/build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/build.png -------------------------------------------------------------------------------- /demo/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/demo.png -------------------------------------------------------------------------------- /demo/editMovie.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/editMovie.gif -------------------------------------------------------------------------------- /demo/gitads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/gitads.png -------------------------------------------------------------------------------- /demo/listMethods01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/listMethods01.png -------------------------------------------------------------------------------- /demo/listMethods02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/listMethods02.png -------------------------------------------------------------------------------- /demo/methodBtn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/methodBtn.png -------------------------------------------------------------------------------- /demo/moviejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/moviejs.png -------------------------------------------------------------------------------- /demo/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/project.png -------------------------------------------------------------------------------- /demo/proxyTabel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/proxyTabel.png -------------------------------------------------------------------------------- /demo/removeMovie.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/removeMovie.gif -------------------------------------------------------------------------------- /demo/router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/router.png -------------------------------------------------------------------------------- /demo/showDetail.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/demo/showDetail.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue Express 前后端分离 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /models/movie.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const movieSchema = mongoose.Schema({ 4 | title : { type:String, required : true }, 5 | poster : String, 6 | rating : String, 7 | introduction : String, 8 | created_at : { type : Date, default : Date.now }, 9 | update_at : { type : Date, default : Date.now } 10 | }) 11 | 12 | const Movie = module.exports = mongoose.model('Movie',movieSchema) 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-express-mongodb", 3 | "version": "1.0.0", 4 | "description": "A Vue.js Express Mongodb project", 5 | "author": "xrr2016 ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "build": "node build/build.js", 10 | "server": "nodemon app.js", 11 | "start": "node app.js" 12 | }, 13 | "dependencies": { 14 | "axios": ">=0.18.1", 15 | "body-parser": "^1.16.1", 16 | "express": "^4.14.1", 17 | "jquery": "^3.1.1", 18 | "keycode": "^2.1.8", 19 | "mongoose": ">=5.7.5", 20 | "muse-ui": "^2.0.0-rc.5", 21 | "serve-favicon": "^2.3.2", 22 | "toastr": "^2.1.2", 23 | "vodal": "^1.0.3", 24 | "vue": "^2.1.10", 25 | "vue-router": "^2.2.0" 26 | }, 27 | "devDependencies": { 28 | "autoprefixer": "^6.7.2", 29 | "babel-core": "^6.22.1", 30 | "babel-loader": "^6.2.10", 31 | "babel-plugin-transform-runtime": "^6.22.0", 32 | "babel-preset-es2015": "^6.22.0", 33 | "babel-preset-stage-2": "^6.22.0", 34 | "babel-register": "^6.22.0", 35 | "chalk": "^1.1.3", 36 | "connect-history-api-fallback": "^1.3.0", 37 | "css-loader": "^0.26.1", 38 | "eventsource-polyfill": "^0.9.6", 39 | "express": "^4.14.1", 40 | "extract-text-webpack-plugin": "^2.0.0-rc.2", 41 | "file-loader": "^0.10.0", 42 | "friendly-errors-webpack-plugin": "^1.1.3", 43 | "function-bind": "^1.1.0", 44 | "html-webpack-plugin": "^2.28.0", 45 | "http-proxy-middleware": "^0.17.3", 46 | "nodemon": "^1.11.0", 47 | "opn": "^4.0.2", 48 | "ora": "^1.1.0", 49 | "semver": "^5.3.0", 50 | "shelljs": "^0.7.6", 51 | "url-loader": "^0.5.7", 52 | "vue-loader": "^10.3.0", 53 | "vue-style-loader": "^2.0.0", 54 | "vue-template-compiler": "^2.1.10", 55 | "webpack": "^2.2.1", 56 | "webpack-bundle-analyzer": ">=3.3.2", 57 | "webpack-dev-middleware": "^1.10.0", 58 | "webpack-hot-middleware": "^2.16.1", 59 | "webpack-merge": "^2.6.1" 60 | }, 61 | "engines": { 62 | "node": ">= 4.0.0", 63 | "npm": ">= 3.0.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /router/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | 4 | router.get('/', (req,res) => { 5 | res.send('Hello Express!') 6 | }) 7 | 8 | module.exports = router 9 | -------------------------------------------------------------------------------- /router/movie.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const Movie = require('../models/movie') 4 | 5 | // 查询所有电影 6 | router.get('/movie', (req, res) => { 7 | Movie.find({}) 8 | .sort({ update_at : -1}) 9 | .then(movies => { 10 | res.json(movies) 11 | }) 12 | .catch(err => { 13 | res.json(err) 14 | }) 15 | }) 16 | // 通过ObjectId查询单个电影 17 | router.get('/movie/:id', (req, res) => { 18 | Movie.findById(req.params.id) 19 | .then(movie => { 20 | res.json(movie) 21 | }) 22 | .catch(err => { 23 | res.json(err) 24 | }) 25 | }) 26 | // 添加一部电影 27 | router.post('/movie', (req, res) => { 28 | //使用Movie model上的create方法储存数据 29 | Movie.create(req.body, (err, movie) => { 30 | if (err) { 31 | res.json(err) 32 | } else { 33 | res.json(movie) 34 | } 35 | }) 36 | //使用实例的save方法存储数据 37 | // let movie = new Movie({ 38 | // title : req.body.title, 39 | // year : req.body.year, 40 | // poster : req.body.poster, 41 | // introduction : req.body.introduction 42 | // }) 43 | // movie.save( (err,movie) => { 44 | // if (err) { 45 | // res.json(err) 46 | // } else { 47 | // res.json(movie) 48 | // } 49 | // }) 50 | }) 51 | //更新一部电影 52 | router.put('/movie/:id',(req,res) => { 53 | Movie.findOneAndUpdate({ _id : req.params.id} 54 | ,{ $set : { title: req.body.title, 55 | rating : req.body.rating, 56 | poster : req.body.poster, 57 | introduction : req.body.introduction } 58 | },{ 59 | new : true 60 | }) 61 | .then(movie => res.json(movie)) 62 | .catch(err => res.json(err)) 63 | }) 64 | //删除一部电影 65 | router.delete('/movie/:id',(req,res) => { 66 | Movie.findOneAndRemove({ 67 | _id : req.params.id 68 | }) 69 | .then(movie => res.send(`${movie.title}删除成功`)) 70 | .catch(err => res.json(err)) 71 | }) 72 | 73 | module.exports = router 74 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 26 | 27 | 47 | -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/icon.css: -------------------------------------------------------------------------------- 1 | /*ICON*/ 2 | /* fallback */ 3 | @font-face { 4 | font-family: 'Material Icons'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: local('Material Icons'), local('MaterialIcons-Regular'), url(http://fonts.gstatic.com/s/materialicons/v20/2fcrYFNaTjcS6g4U3t-Y5ZjZjT5FdEJ140U2DJYC3mY.woff2) format('woff2'); 8 | } 9 | .material-icons { 10 | font-family: 'Material Icons'; 11 | font-weight: normal; 12 | font-style: normal; 13 | font-size: 24px; 14 | line-height: 1; 15 | letter-spacing: normal; 16 | text-transform: none; 17 | display: inline-block; 18 | white-space: nowrap; 19 | word-wrap: normal; 20 | direction: ltr; 21 | -webkit-font-feature-settings: 'liga'; 22 | -webkit-font-smoothing: antialiased; 23 | } 24 | /*FONT*/ 25 | /* cyrillic-ext */ 26 | @font-face { 27 | font-family: 'Roboto'; 28 | font-style: normal; 29 | font-weight: 300; 30 | src: local('Roboto Light'), local('Roboto-Light'), url(http://fonts.gstatic.com/s/roboto/v15/0eC6fl06luXEYWpBSJvXCBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 31 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 32 | } 33 | /* cyrillic */ 34 | @font-face { 35 | font-family: 'Roboto'; 36 | font-style: normal; 37 | font-weight: 300; 38 | src: local('Roboto Light'), local('Roboto-Light'), url(http://fonts.gstatic.com/s/roboto/v15/Fl4y0QdOxyyTHEGMXX8kcRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 39 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 40 | } 41 | /* greek-ext */ 42 | @font-face { 43 | font-family: 'Roboto'; 44 | font-style: normal; 45 | font-weight: 300; 46 | src: local('Roboto Light'), local('Roboto-Light'), url(http://fonts.gstatic.com/s/roboto/v15/-L14Jk06m6pUHB-5mXQQnRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 47 | unicode-range: U+1F00-1FFF; 48 | } 49 | /* greek */ 50 | @font-face { 51 | font-family: 'Roboto'; 52 | font-style: normal; 53 | font-weight: 300; 54 | src: local('Roboto Light'), local('Roboto-Light'), url(http://fonts.gstatic.com/s/roboto/v15/I3S1wsgSg9YCurV6PUkTORJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 55 | unicode-range: U+0370-03FF; 56 | } 57 | /* vietnamese */ 58 | @font-face { 59 | font-family: 'Roboto'; 60 | font-style: normal; 61 | font-weight: 300; 62 | src: local('Roboto Light'), local('Roboto-Light'), url(http://fonts.gstatic.com/s/roboto/v15/NYDWBdD4gIq26G5XYbHsFBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 63 | unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; 64 | } 65 | /* latin-ext */ 66 | @font-face { 67 | font-family: 'Roboto'; 68 | font-style: normal; 69 | font-weight: 300; 70 | src: local('Roboto Light'), local('Roboto-Light'), url(http://fonts.gstatic.com/s/roboto/v15/Pru33qjShpZSmG3z6VYwnRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 71 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 72 | } 73 | /* latin */ 74 | @font-face { 75 | font-family: 'Roboto'; 76 | font-style: normal; 77 | font-weight: 300; 78 | src: local('Roboto Light'), local('Roboto-Light'), url(http://fonts.gstatic.com/s/roboto/v15/Hgo13k-tfSpn0qi1SFdUfVtXRa8TVwTICgirnJhmVJw.woff2) format('woff2'); 79 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; 80 | } 81 | /* cyrillic-ext */ 82 | @font-face { 83 | font-family: 'Roboto'; 84 | font-style: normal; 85 | font-weight: 400; 86 | src: local('Roboto'), local('Roboto-Regular'), url(http://fonts.gstatic.com/s/roboto/v15/ek4gzZ-GeXAPcSbHtCeQI_esZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); 87 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 88 | } 89 | /* cyrillic */ 90 | @font-face { 91 | font-family: 'Roboto'; 92 | font-style: normal; 93 | font-weight: 400; 94 | src: local('Roboto'), local('Roboto-Regular'), url(http://fonts.gstatic.com/s/roboto/v15/mErvLBYg_cXG3rLvUsKT_fesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); 95 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 96 | } 97 | /* greek-ext */ 98 | @font-face { 99 | font-family: 'Roboto'; 100 | font-style: normal; 101 | font-weight: 400; 102 | src: local('Roboto'), local('Roboto-Regular'), url(http://fonts.gstatic.com/s/roboto/v15/-2n2p-_Y08sg57CNWQfKNvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); 103 | unicode-range: U+1F00-1FFF; 104 | } 105 | /* greek */ 106 | @font-face { 107 | font-family: 'Roboto'; 108 | font-style: normal; 109 | font-weight: 400; 110 | src: local('Roboto'), local('Roboto-Regular'), url(http://fonts.gstatic.com/s/roboto/v15/u0TOpm082MNkS5K0Q4rhqvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); 111 | unicode-range: U+0370-03FF; 112 | } 113 | /* vietnamese */ 114 | @font-face { 115 | font-family: 'Roboto'; 116 | font-style: normal; 117 | font-weight: 400; 118 | src: local('Roboto'), local('Roboto-Regular'), url(http://fonts.gstatic.com/s/roboto/v15/NdF9MtnOpLzo-noMoG0miPesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); 119 | unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; 120 | } 121 | /* latin-ext */ 122 | @font-face { 123 | font-family: 'Roboto'; 124 | font-style: normal; 125 | font-weight: 400; 126 | src: local('Roboto'), local('Roboto-Regular'), url(http://fonts.gstatic.com/s/roboto/v15/Fcx7Wwv8OzT71A3E1XOAjvesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); 127 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 128 | } 129 | /* latin */ 130 | @font-face { 131 | font-family: 'Roboto'; 132 | font-style: normal; 133 | font-weight: 400; 134 | src: local('Roboto'), local('Roboto-Regular'), url(http://fonts.gstatic.com/s/roboto/v15/CWB0XYA8bzo0kSThX0UTuA.woff2) format('woff2'); 135 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; 136 | } 137 | /* cyrillic-ext */ 138 | @font-face { 139 | font-family: 'Roboto'; 140 | font-style: normal; 141 | font-weight: 500; 142 | src: local('Roboto Medium'), local('Roboto-Medium'), url(http://fonts.gstatic.com/s/roboto/v15/ZLqKeelYbATG60EpZBSDyxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 143 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 144 | } 145 | /* cyrillic */ 146 | @font-face { 147 | font-family: 'Roboto'; 148 | font-style: normal; 149 | font-weight: 500; 150 | src: local('Roboto Medium'), local('Roboto-Medium'), url(http://fonts.gstatic.com/s/roboto/v15/oHi30kwQWvpCWqAhzHcCSBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 151 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 152 | } 153 | /* greek-ext */ 154 | @font-face { 155 | font-family: 'Roboto'; 156 | font-style: normal; 157 | font-weight: 500; 158 | src: local('Roboto Medium'), local('Roboto-Medium'), url(http://fonts.gstatic.com/s/roboto/v15/rGvHdJnr2l75qb0YND9NyBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 159 | unicode-range: U+1F00-1FFF; 160 | } 161 | /* greek */ 162 | @font-face { 163 | font-family: 'Roboto'; 164 | font-style: normal; 165 | font-weight: 500; 166 | src: local('Roboto Medium'), local('Roboto-Medium'), url(http://fonts.gstatic.com/s/roboto/v15/mx9Uck6uB63VIKFYnEMXrRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 167 | unicode-range: U+0370-03FF; 168 | } 169 | /* vietnamese */ 170 | @font-face { 171 | font-family: 'Roboto'; 172 | font-style: normal; 173 | font-weight: 500; 174 | src: local('Roboto Medium'), local('Roboto-Medium'), url(http://fonts.gstatic.com/s/roboto/v15/mbmhprMH69Zi6eEPBYVFhRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 175 | unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; 176 | } 177 | /* latin-ext */ 178 | @font-face { 179 | font-family: 'Roboto'; 180 | font-style: normal; 181 | font-weight: 500; 182 | src: local('Roboto Medium'), local('Roboto-Medium'), url(http://fonts.gstatic.com/s/roboto/v15/oOeFwZNlrTefzLYmlVV1UBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 183 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 184 | } 185 | /* latin */ 186 | @font-face { 187 | font-family: 'Roboto'; 188 | font-style: normal; 189 | font-weight: 500; 190 | src: local('Roboto Medium'), local('Roboto-Medium'), url(http://fonts.gstatic.com/s/roboto/v15/RxZJdnzeo3R5zSexge8UUVtXRa8TVwTICgirnJhmVJw.woff2) format('woff2'); 191 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; 192 | } 193 | /* cyrillic-ext */ 194 | @font-face { 195 | font-family: 'Roboto'; 196 | font-style: normal; 197 | font-weight: 700; 198 | src: local('Roboto Bold'), local('Roboto-Bold'), url(http://fonts.gstatic.com/s/roboto/v15/77FXFjRbGzN4aCrSFhlh3hJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 199 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 200 | } 201 | /* cyrillic */ 202 | @font-face { 203 | font-family: 'Roboto'; 204 | font-style: normal; 205 | font-weight: 700; 206 | src: local('Roboto Bold'), local('Roboto-Bold'), url(http://fonts.gstatic.com/s/roboto/v15/isZ-wbCXNKAbnjo6_TwHThJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 207 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 208 | } 209 | /* greek-ext */ 210 | @font-face { 211 | font-family: 'Roboto'; 212 | font-style: normal; 213 | font-weight: 700; 214 | src: local('Roboto Bold'), local('Roboto-Bold'), url(http://fonts.gstatic.com/s/roboto/v15/UX6i4JxQDm3fVTc1CPuwqhJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 215 | unicode-range: U+1F00-1FFF; 216 | } 217 | /* greek */ 218 | @font-face { 219 | font-family: 'Roboto'; 220 | font-style: normal; 221 | font-weight: 700; 222 | src: local('Roboto Bold'), local('Roboto-Bold'), url(http://fonts.gstatic.com/s/roboto/v15/jSN2CGVDbcVyCnfJfjSdfBJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 223 | unicode-range: U+0370-03FF; 224 | } 225 | /* vietnamese */ 226 | @font-face { 227 | font-family: 'Roboto'; 228 | font-style: normal; 229 | font-weight: 700; 230 | src: local('Roboto Bold'), local('Roboto-Bold'), url(http://fonts.gstatic.com/s/roboto/v15/PwZc-YbIL414wB9rB1IAPRJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 231 | unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; 232 | } 233 | /* latin-ext */ 234 | @font-face { 235 | font-family: 'Roboto'; 236 | font-style: normal; 237 | font-weight: 700; 238 | src: local('Roboto Bold'), local('Roboto-Bold'), url(http://fonts.gstatic.com/s/roboto/v15/97uahxiqZRoncBaCEI3aWxJtnKITppOI_IvcXXDNrsc.woff2) format('woff2'); 239 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 240 | } 241 | /* latin */ 242 | @font-face { 243 | font-family: 'Roboto'; 244 | font-style: normal; 245 | font-weight: 700; 246 | src: local('Roboto Bold'), local('Roboto-Bold'), url(http://fonts.gstatic.com/s/roboto/v15/d-6IYplOFocCacKzxwXSOFtXRa8TVwTICgirnJhmVJw.woff2) format('woff2'); 247 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; 248 | } 249 | /* cyrillic-ext */ 250 | @font-face { 251 | font-family: 'Roboto'; 252 | font-style: italic; 253 | font-weight: 400; 254 | src: local('Roboto Italic'), local('Roboto-Italic'), url(http://fonts.gstatic.com/s/roboto/v15/WxrXJa0C3KdtC7lMafG4dRTbgVql8nDJpwnrE27mub0.woff2) format('woff2'); 255 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 256 | } 257 | /* cyrillic */ 258 | @font-face { 259 | font-family: 'Roboto'; 260 | font-style: italic; 261 | font-weight: 400; 262 | src: local('Roboto Italic'), local('Roboto-Italic'), url(http://fonts.gstatic.com/s/roboto/v15/OpXUqTo0UgQQhGj_SFdLWBTbgVql8nDJpwnrE27mub0.woff2) format('woff2'); 263 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 264 | } 265 | /* greek-ext */ 266 | @font-face { 267 | font-family: 'Roboto'; 268 | font-style: italic; 269 | font-weight: 400; 270 | src: local('Roboto Italic'), local('Roboto-Italic'), url(http://fonts.gstatic.com/s/roboto/v15/1hZf02POANh32k2VkgEoUBTbgVql8nDJpwnrE27mub0.woff2) format('woff2'); 271 | unicode-range: U+1F00-1FFF; 272 | } 273 | /* greek */ 274 | @font-face { 275 | font-family: 'Roboto'; 276 | font-style: italic; 277 | font-weight: 400; 278 | src: local('Roboto Italic'), local('Roboto-Italic'), url(http://fonts.gstatic.com/s/roboto/v15/cDKhRaXnQTOVbaoxwdOr9xTbgVql8nDJpwnrE27mub0.woff2) format('woff2'); 279 | unicode-range: U+0370-03FF; 280 | } 281 | /* vietnamese */ 282 | @font-face { 283 | font-family: 'Roboto'; 284 | font-style: italic; 285 | font-weight: 400; 286 | src: local('Roboto Italic'), local('Roboto-Italic'), url(http://fonts.gstatic.com/s/roboto/v15/K23cxWVTrIFD6DJsEVi07RTbgVql8nDJpwnrE27mub0.woff2) format('woff2'); 287 | unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; 288 | } 289 | /* latin-ext */ 290 | @font-face { 291 | font-family: 'Roboto'; 292 | font-style: italic; 293 | font-weight: 400; 294 | src: local('Roboto Italic'), local('Roboto-Italic'), url(http://fonts.gstatic.com/s/roboto/v15/vSzulfKSK0LLjjfeaxcREhTbgVql8nDJpwnrE27mub0.woff2) format('woff2'); 295 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 296 | } 297 | /* latin */ 298 | @font-face { 299 | font-family: 'Roboto'; 300 | font-style: italic; 301 | font-weight: 400; 302 | src: local('Roboto Italic'), local('Roboto-Italic'), url(http://fonts.gstatic.com/s/roboto/v15/vPcynSL0qHq_6dX7lKVByfesZW2xOQ-xsNqO47m55DA.woff2) format('woff2'); 303 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; 304 | } 305 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/Detail.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 71 | 72 | 139 | -------------------------------------------------------------------------------- /src/components/List.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 182 | 183 | 203 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | import toastr from 'toastr' 4 | import MuseUI from 'muse-ui' 5 | import Vodal from 'vodal' 6 | import axios from 'axios' 7 | import router from './router' 8 | import 'toastr/build/toastr.min.css' 9 | import 'muse-ui/dist/muse-ui.css' 10 | import './assets/icon.css' 11 | import 'vodal/fade.css' 12 | 13 | Vue.use(MuseUI) 14 | Vue.prototype.$http = axios 15 | Vue.prototype.toastr = toastr 16 | Vue.component(Vodal.name,Vodal) 17 | 18 | toastr.options = { 19 | "closeButton": true, 20 | "debug": false, 21 | "newestOnTop": false, 22 | "progressBar": false, 23 | "positionClass": "toast-top-right", 24 | "preventDuplicates": false, 25 | "onclick": null, 26 | "showDuration": "300", 27 | "hideDuration": "1000", 28 | "timeOut": "3000", 29 | "extendedTimeOut": "1000", 30 | "showEasing": "swing", 31 | "hideEasing": "linear", 32 | "showMethod": "fadeIn", 33 | "hideMethod": "fadeOut" 34 | } 35 | // Vue.filter('imgUrlPrefix',(value) => { 36 | // const url = value.substr(7) 37 | // const prefix = "https://images.weserv.nl/?url=" 38 | // return prefix + url 39 | // }) 40 | Vue.filter('castsToString',(casts) => { 41 | return casts.map(item => { 42 | return item.name 43 | }) 44 | }) 45 | 46 | /* eslint-disable no-new */ 47 | new Vue({ 48 | created(){ 49 | toastr.success('启动成功!') 50 | }, 51 | router, 52 | render: h => h(App) 53 | }).$mount('#app') 54 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import List from 'components/List' 4 | import Detail from 'components/Detail' 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | routes: [ 10 | { 11 | path: '/', 12 | name: 'List', 13 | component: List 14 | },{ 15 | path : '/movie/:title', 16 | name : 'detail', 17 | component : Detail 18 | }, 19 | { 20 | path: '*', 21 | redirect : '/' 22 | } 23 | ] 24 | }) 25 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xrr2016/vue-express-mongodb/3e0c9df0216c589564853b3c54bad18ac14796d1/static/.gitkeep --------------------------------------------------------------------------------