├── .browserslistrc ├── .gitignore ├── README.md ├── babel.config.js ├── config.properties ├── database.sql ├── nodemon.json ├── package.json ├── pm2.json ├── postcss.config.js ├── server.babel.config.js ├── src ├── client │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ └── HelloWorld.vue │ ├── http.js │ ├── index.html │ ├── main.js │ ├── router.js │ ├── store │ │ └── index.js │ └── views │ │ ├── About.vue │ │ ├── Home.vue │ │ └── todolist │ │ └── index.vue ├── dev.js └── server │ ├── api │ └── TodoApi.js │ ├── app.js │ ├── container │ └── index.js │ ├── daos │ ├── ItemDao.js │ └── base.js │ ├── initialize │ ├── index.js │ ├── properties.js │ └── sequelize.js │ ├── main.js │ ├── middleware │ ├── base.js │ └── transactions.js │ ├── models │ ├── ItemModel.js │ └── index.js │ ├── services │ ├── TodoService.js │ └── base.js │ └── util.js ├── vue.config.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /log 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | 24 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Welcome to Express-Vue App 2 | 3 | server与client一个命令启动。 4 | 5 | ========================= 6 | 7 | 根据database.sql创建mysql数据库后和table后,修改config.properties配置 8 | 9 | 开发: 10 | 11 | ``` 12 | npm run dev 13 | // or yarn dev 14 | ``` 15 | 16 | 部署: 17 | 18 | ``` 19 | npm run build && npm run start 20 | // or yarn build && yarn start 21 | ``` 22 | 23 | ### 服务端技术栈 24 | 25 | * [框架 Express](http://expressjs.com/) 26 | * [热更新 nodemon](https://github.com/remy/nodemon) 27 | * [依赖注入 awilix](https://github.com/jeffijoe/awilix) 28 | * [数据持久化 sequelize](https://github.com/sequelize/sequelize) 29 | * [部署 pm2](https://pm2.io/) 30 | 31 | ### 客户端技术栈 32 | 33 | * [vue-router](https://router.vuejs.org) 34 | * [vuex](https://vuex.vuejs.org) 35 | * [axios](https://github.com/axios/axios) 36 | * [vue-class-component](https://github.com/vuejs/vue-class-component) 37 | * [vue-property-decorator](https://github.com/kaorun343/vue-property-decorator#readme) 38 | * [vuex-class](https://github.com/ktsn/vuex-class) -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "presets": [ 3 | ["@vue/app", { 4 | "loose": true, 5 | "decoratorsLegacy": true 6 | }] 7 | ] 8 | } -------------------------------------------------------------------------------- /config.properties: -------------------------------------------------------------------------------- 1 | [mysql] 2 | host=127.0.0.1 3 | port=3306 4 | user=root 5 | password=root 6 | database=test -------------------------------------------------------------------------------- /database.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `item` ( 2 | `record_id` int(11) unsigned NOT NULL AUTO_INCREMENT, 3 | `name` varchar(500) DEFAULT NULL, 4 | `state` int(11) DEFAULT NULL, 5 | PRIMARY KEY (`record_id`) 6 | ) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8; -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": [ 3 | ".git", 4 | "node_modules/**/node_modules", 5 | "src/client" 6 | ] 7 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-vue-web-slush", 3 | "version": "1.0.0", 4 | "description": "express、vue脚手架", 5 | "main": "index.js", 6 | "scripts": { 7 | "clean": "rimraf dist", 8 | "dev-env": "cross-env NODE_ENV=development", 9 | "pro-env": "cross-env NODE_ENV=production", 10 | "start:client": "vue-cli-service serve", 11 | "build:client": "vue-cli-service build", 12 | "build:server": "babel --config-file ./server.babel.config.js src/server --out-dir dist/server/", 13 | "babel-server": "npm run dev-env && babel-node --config-file ./server.babel.config.js -- ./src/server/main.js", 14 | "build": "npm run clean && npm run build:client && npm run build:server", 15 | "dev": "babel-node --config-file ./server.babel.config.js -- ./src/dev.js", 16 | "start": "pm2 start pm2.json", 17 | "stop": "pm2 delete pm2.json" 18 | }, 19 | "dependencies": { 20 | "axios": "^0.18.0", 21 | "core-js": "^2.6.5", 22 | "vue": "^2.6.10", 23 | "vue-property-decorator": "^8.1.1", 24 | "vue-router": "^3.0.3", 25 | "vuex": "^3.0.1", 26 | "vuex-class": "^0.3.2", 27 | "vuex-module-decorators": "^0.9.8" 28 | }, 29 | "devDependencies": { 30 | "@babel/cli": "^7.4.4", 31 | "@babel/core": "^7.4.4", 32 | "@babel/node": "^7.2.2", 33 | "@babel/plugin-proposal-class-properties": "^7.4.4", 34 | "@babel/plugin-proposal-decorators": "^7.4.4", 35 | "@babel/plugin-transform-runtime": "^7.4.4", 36 | "@babel/preset-env": "^7.4.4", 37 | "@vue/cli-plugin-babel": "^3.7.0", 38 | "@vue/cli-service": "^3.7.0", 39 | "awilix": "^4.2.2", 40 | "awilix-express": "^2.1.1", 41 | "body-parser": "^1.19.0", 42 | "cookie-parser": "^1.4.4", 43 | "cross-env": "^5.2.0", 44 | "express": "^4.16.4", 45 | "mysql2": "^1.6.5", 46 | "node-sass": "^4.9.0", 47 | "nodemon": "^1.19.0", 48 | "pm2": "^3.5.1", 49 | "properties": "^1.2.1", 50 | "rimraf": "^2.6.3", 51 | "sass-loader": "^7.1.0", 52 | "sequelize": "^5.15.1", 53 | "vue-template-compiler": "^2.5.21" 54 | }, 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/yacan8/express-vue-web-slush.git" 58 | }, 59 | "keywords": [ 60 | "vue", 61 | "express" 62 | ], 63 | "author": "can.yang", 64 | "license": "ISC", 65 | "bugs": { 66 | "url": "https://github.com/yacan8/express-vue-web-slush/issues" 67 | }, 68 | "homepage": "https://github.com/yacan8/express-vue-web-slush#readme" 69 | } 70 | -------------------------------------------------------------------------------- /pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [ 3 | { 4 | "name": "vue-express", 5 | "script": "./dist/server/main.js", 6 | "log_date_format": "YYYY-MM-DD HH:mm Z", 7 | "output": "./log/out.log", 8 | "error": "./log/error.log", 9 | "instances": 1, 10 | "watch": false, 11 | "merge_logs": true, 12 | "env": { 13 | "NODE_ENV": "production" 14 | } 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /server.babel.config.js: -------------------------------------------------------------------------------- 1 | var isDebug = process.env.NODE_ENV === 'development'; 2 | 3 | var config = { 4 | presets: [ 5 | ['@babel/env', { 6 | useBuiltIns: false, 7 | targets: { 8 | node: 'current' 9 | } 10 | }] 11 | ], 12 | plugins: [ 13 | ['@babel/plugin-proposal-decorators', {legacy: true}], 14 | ['@babel/plugin-proposal-class-properties', {loose: true}], 15 | '@babel/plugin-transform-runtime' 16 | ] 17 | }; 18 | 19 | // 服务端开发环境babel配置文件 bable-node会自动添加polyfill,所以不需要添加preset/env 20 | // if (isDebug) { 21 | // config.presets = [ 22 | // ['@babel/env', { 23 | // useBuiltIns: false, 24 | // targets: { 25 | // node: 'current' 26 | // } 27 | // }] 28 | // ] 29 | // } 30 | 31 | module.exports = config; -------------------------------------------------------------------------------- /src/client/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 31 | -------------------------------------------------------------------------------- /src/client/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yacan8/express-vue-web-slush/0f315e02c73c01991abff9bf7748c83ba4de4108/src/client/assets/logo.png -------------------------------------------------------------------------------- /src/client/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 34 | 35 | 36 | 52 | -------------------------------------------------------------------------------- /src/client/http.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http接口统一管理 3 | */ 4 | import axios from 'axios'; 5 | // 初始化axios 6 | axios.defaults.timeout = 10000; 7 | // axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; 8 | axios.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8'; 9 | axios.defaults.withCredential = true; 10 | axios.defaults.crossDomain = true; 11 | 12 | /** 13 | * get方法,对应get请求 14 | * @param {string} url [请求的url地址] 15 | * @param {Object} params [请求时携带的参数] 16 | * @param {Object} config [配置] 17 | * @return {Object} Promise 18 | */ 19 | export function get(url, params = {}, config = {}) { 20 | return new Promise((resolve, reject) => { 21 | axios 22 | .get(url, { 23 | params: params 24 | }, config) 25 | .then(res => { 26 | resolve(res.data); 27 | }) 28 | .catch(err => { 29 | reject(err); 30 | }); 31 | }); 32 | } 33 | 34 | /** 35 | * post方法,对应post请求 36 | * @param {string} url [请求的url地址] 37 | * @param {Object} params [请求时携带的参数] 38 | * @param {Object} config [配置] 39 | * @return {Object} Promise 40 | */ 41 | export function post(url, params, config = {}) { 42 | return new Promise((resolve, reject) => { 43 | axios 44 | .post(url, params, config) 45 | .then(res => { 46 | resolve(res.data); 47 | }) 48 | .catch(err => { 49 | reject(err); 50 | }); 51 | }); 52 | } -------------------------------------------------------------------------------- /src/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | client 8 | 9 | 10 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/client/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store/' 5 | 6 | Vue.config.productionTip = false 7 | 8 | new Vue({ 9 | router, 10 | store, 11 | render: h => h(App) 12 | }).$mount('#app') 13 | -------------------------------------------------------------------------------- /src/client/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from './views/Home.vue' 4 | 5 | Vue.use(Router) 6 | 7 | export default new Router({ 8 | routes: [ 9 | { 10 | path: '/', 11 | name: 'home', 12 | component: Home 13 | } 14 | ] 15 | }) 16 | -------------------------------------------------------------------------------- /src/client/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { get, post } from '../http'; 4 | 5 | Vue.use(Vuex) 6 | 7 | const handleError = e => { 8 | console.error(e); 9 | return { 10 | success: false, 11 | message: '客户端异常' 12 | } 13 | } 14 | 15 | export default new Vuex.Store({ 16 | state: { 17 | todolist: [] 18 | }, 19 | mutations: { 20 | setTodolist(state, todolist) { 21 | state.todolist = todolist; 22 | } 23 | }, 24 | actions: { 25 | fetchTodolist({commit}, querys = {}) { 26 | return get('/api/todo/getTodolist', querys).then(res => { 27 | if (res.success) { 28 | commit('setTodolist', res.data); 29 | } 30 | return res; 31 | }).catch(e => handleError(e)); 32 | }, 33 | addTodoItem({commit, rootState}, item) { 34 | return post('/api/todo/addTodoItem', item).then(res => { 35 | if (res.success) { 36 | const { todolist } = rootState; 37 | todolist.push(item); 38 | commit('setTodolist', todolist); 39 | } 40 | return res; 41 | }).catch(e => handleError(e)); 42 | }, 43 | doToggleState({commit, rootState}, item) { 44 | const state = item.state == 1 ? 0 : 1; 45 | item.state = state; 46 | return post('/api/todo/updateItem', item).then(res => { 47 | if (res.success) { 48 | const { todolist } = rootState; 49 | const itemItem = todolist.findIndex(i => i.name === item.name); 50 | todolist[itemItem].state = state; 51 | commit('setTodolist', todolist); 52 | } 53 | return res; 54 | }).catch(e => handleError(e)); 55 | } 56 | } 57 | }) 58 | -------------------------------------------------------------------------------- /src/client/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/client/views/Home.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 35 | -------------------------------------------------------------------------------- /src/client/views/todolist/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 118 | 119 | 145 | -------------------------------------------------------------------------------- /src/dev.js: -------------------------------------------------------------------------------- 1 | import * as childProcess from 'child_process'; 2 | 3 | function run() { 4 | const client = childProcess.spawn('vue-cli-service', ['serve']); 5 | client.stdout.on('data', x => process.stdout.write(x)); 6 | client.stderr.on('data', x => process.stderr.write(x)); 7 | 8 | const server = childProcess.spawn('nodemon', ['--exec', 'npm run babel-server'], { 9 | env: Object.assign({ 10 | NODE_ENV: 'development' 11 | }, process.env), 12 | silent: false 13 | }); 14 | server.stdout.on('data', x => process.stdout.write(x)); 15 | server.stderr.on('data', x => process.stderr.write(x)); 16 | 17 | process.on('exit', () => { 18 | server.kill('SIGTERM'); 19 | client.kill('SIGTERM'); 20 | }); 21 | } 22 | 23 | run(); -------------------------------------------------------------------------------- /src/server/api/TodoApi.js: -------------------------------------------------------------------------------- 1 | import { route, GET, POST } from 'awilix-express'; 2 | 3 | @route('/todo') 4 | export default class TodoAPI { 5 | 6 | constructor({ todoService }) { 7 | this.todoService = todoService; 8 | } 9 | 10 | @route('/getData') 11 | @GET() 12 | async getData(req, res) { 13 | res.successPrint('请求成功', { 14 | info: 'Welcome to Express-Vue App' 15 | }) 16 | } 17 | 18 | @route('/getTodolist') 19 | @GET() 20 | async getTodolist(req, res) { 21 | const { success, data: todolist, message } = await this.todoService.getList(); 22 | if (!success) { 23 | res.failPrint(message); 24 | return; 25 | } 26 | res.successPrint(message, todolist); 27 | } 28 | 29 | @route('/addTodoItem') 30 | @POST() 31 | async addTodoItem(req, res) { 32 | const { name, state } = req.body; 33 | const { success, message } = await this.todoService.addTodoItem({name, state}); 34 | if (!success) { 35 | res.failPrint(message); 36 | return; 37 | } 38 | res.successPrint(message); 39 | } 40 | 41 | 42 | @route('/updateItem') 43 | @POST() 44 | async updateItem(req, res) { 45 | const { name, state } = req.body; 46 | const { success, message } = await this.todoService.updateItem({name, state}); 47 | if (!success) { 48 | res.failPrint(message); 49 | return; 50 | } 51 | res.successPrint(message); 52 | } 53 | } -------------------------------------------------------------------------------- /src/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file app 3 | */ 4 | import express from 'express'; 5 | import { Lifetime, asClass } from 'awilix'; 6 | import { loadControllers, scopePerRequest } from 'awilix-express'; 7 | import container from './container'; 8 | import bodyParser from 'body-parser'; 9 | import path from 'path'; 10 | import fs from 'fs'; 11 | import { baseMiddleware } from './middleware/base'; 12 | import initialize from './initialize'; 13 | import cookieParser from 'cookie-parser'; 14 | 15 | const app = express(); 16 | app.use(express.static(path.resolve(__dirname, '../client'))); 17 | app.use(cookieParser()); 18 | app.use(bodyParser()); 19 | app.use(scopePerRequest(container)); 20 | app.use(baseMiddleware(app)); 21 | app.use('/api', loadControllers('api/*.js', { 22 | cwd: __dirname, 23 | lifetime: Lifetime.SINGLETON 24 | })); 25 | 26 | export default async function run() { 27 | await initialize(app); 28 | 29 | // 依赖注入配置service层和dao层 30 | container.loadModules(['services/*Service.js', 'daos/*Dao.js'], { 31 | formatName: 'camelCase', 32 | register: asClass, 33 | cwd: path.resolve(__dirname) 34 | }); 35 | 36 | app.get('*', (req, res) => { 37 | const html = fs.readFileSync(path.resolve(__dirname, '../client', 'index.html'), 'utf-8'); 38 | res.send(html); 39 | }); 40 | 41 | app.listen(9001, err => { 42 | if (err) { 43 | console.error(err); 44 | return; 45 | } 46 | console.log('Listening at http://localhost:9001'); 47 | }); 48 | } -------------------------------------------------------------------------------- /src/server/container/index.js: -------------------------------------------------------------------------------- 1 | import * as awilix from 'awilix'; 2 | 3 | const container = awilix.createContainer({ 4 | injectionMode: awilix.InjectionMode.PROXY 5 | }); 6 | 7 | export default container; -------------------------------------------------------------------------------- /src/server/daos/ItemDao.js: -------------------------------------------------------------------------------- 1 | import BaseDao from './base'; 2 | 3 | export default class ItemDao extends BaseDao { 4 | 5 | modelName = 'Item'; 6 | 7 | constructor(modules) { 8 | super(modules); 9 | } 10 | 11 | async getList() { 12 | return await this.findAll(); 13 | } 14 | 15 | async addItem(item) { 16 | return await this.insert(item); 17 | } 18 | } -------------------------------------------------------------------------------- /src/server/daos/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file base dao 3 | */ 4 | import db from '../models'; 5 | import { promiseCatch } from '../util'; 6 | 7 | export default class BaseDao { 8 | 9 | constructor(modules) { 10 | const request = modules.request; 11 | this.transaction = request.transaction; 12 | this.sequelize = db.sequelize; 13 | } 14 | 15 | insert(params) { 16 | const Model = this.getModel(); 17 | const config = this.getConfig(); 18 | return promiseCatch(Model.create(params, config)); 19 | } 20 | 21 | batchInsert(params) { 22 | const Model = this.getModel(); 23 | const config = this.getConfig(); 24 | return promiseCatch(Model.bulkCreate(params, config)); 25 | } 26 | 27 | delete(params) { 28 | const Model = this.getModel(); 29 | const config = this.getConfig(); 30 | return promiseCatch(Model.destroy({ 31 | ...config, 32 | ...params 33 | })); 34 | } 35 | 36 | findOne(params) { 37 | const Model = this.getModel(); 38 | return promiseCatch(Model.findOne(params).then(models => { 39 | return models || null; 40 | })); 41 | } 42 | 43 | findAll(params) { 44 | const Model = this.getModel(); 45 | return promiseCatch(Model.findAll(params)); 46 | } 47 | 48 | update(params, query) { 49 | const Model = this.getModel(); 50 | const config = this.getConfig(); 51 | return promiseCatch(Model.update(params, { 52 | ...config, 53 | ...query 54 | })); 55 | } 56 | 57 | getModel() { 58 | return db[this.modelName]; 59 | } 60 | 61 | getConfig() { 62 | const config = {}; 63 | if (this.transaction) { 64 | config.transaction = this.transaction; 65 | } 66 | return config; 67 | } 68 | 69 | commit() { 70 | if (this.transaction) { 71 | return promiseCatch(this.transaction.commit()); 72 | } 73 | return promiseCatch(Promise.resolve()); 74 | } 75 | 76 | rollback() { 77 | if (this.transaction) { 78 | return promiseCatch(this.transaction.rollback()); 79 | } 80 | return promiseCatch(Promise.resolve()); 81 | } 82 | } -------------------------------------------------------------------------------- /src/server/initialize/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file init 3 | */ 4 | import loadProperties from './properties'; 5 | import { initSequelize } from './sequelize'; 6 | import container from '../container'; 7 | import * as awilix from 'awilix'; 8 | import { installModel } from '../models'; 9 | 10 | export default async function initialize() { 11 | const config = await loadProperties(); 12 | const { mysql } = config; 13 | const sequelize = initSequelize(mysql); 14 | installModel(sequelize); 15 | container.register({ 16 | globalConfig: awilix.asValue(config), 17 | sequelize: awilix.asValue(sequelize) 18 | }); 19 | } -------------------------------------------------------------------------------- /src/server/initialize/properties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file properties 3 | */ 4 | import properties from 'properties'; 5 | import path from 'path'; 6 | 7 | const propertiesPath = path.resolve(process.cwd(), 'config.properties'); 8 | 9 | export default function load() { 10 | return new Promise((resolve, reject) => { 11 | properties.parse(propertiesPath, { path: true, sections: true }, (err, obj) => { 12 | if (err) { 13 | reject(err); 14 | return; 15 | } 16 | resolve(obj); 17 | }); 18 | }).catch(e => { 19 | console.error(e); 20 | return {}; 21 | }); 22 | } -------------------------------------------------------------------------------- /src/server/initialize/sequelize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file sequelize 3 | */ 4 | import Sequelize from 'sequelize'; 5 | 6 | let sequelize; 7 | 8 | const defaultPreset = { 9 | host: 'localhost', 10 | dialect: 'mysql', 11 | operatorsAliases: false, 12 | port: 3306, 13 | pool: { 14 | max: 10, 15 | min: 0, 16 | acquire: 30000, 17 | idle: 10000 18 | } 19 | }; 20 | 21 | export function initSequelize(config) { 22 | const { host, database, password, port, user } = config; 23 | sequelize = new Sequelize(database, user, password, Object.assign({}, defaultPreset, { 24 | host, 25 | port 26 | })); 27 | return sequelize; 28 | }; 29 | 30 | export default sequelize; -------------------------------------------------------------------------------- /src/server/main.js: -------------------------------------------------------------------------------- 1 | import run from './app'; 2 | 3 | run(); -------------------------------------------------------------------------------- /src/server/middleware/base.js: -------------------------------------------------------------------------------- 1 | import { asValue } from 'awilix'; 2 | 3 | export function baseMiddleware(app) { 4 | return (req, res, next) => { 5 | res.successPrint = (message, data) => { 6 | res.json({ 7 | success: true, 8 | message, 9 | data 10 | }); 11 | }; 12 | 13 | res.failPrint = (message, data) => { 14 | res.json({ 15 | success: false, 16 | message, 17 | data 18 | }); 19 | }; 20 | 21 | req.app = app; 22 | 23 | // 注入request、response 24 | req.container = req.container.createScope(); 25 | req.container.register({ 26 | request: asValue(req), 27 | response: asValue(res) 28 | }); 29 | next(); 30 | } 31 | } -------------------------------------------------------------------------------- /src/server/middleware/transactions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file transactions 3 | */ 4 | import { asValue } from 'awilix'; 5 | 6 | export default function () { 7 | return function (req, res, next) { 8 | const sequelize = container.resolve('sequelize'); 9 | sequelize.transaction({ 10 | autocommit: false 11 | }).then(t => { 12 | req.container = req.container.createScope(); 13 | req.container.register({ 14 | transaction: asValue(t) 15 | }); 16 | next(); 17 | }); 18 | } 19 | } -------------------------------------------------------------------------------- /src/server/models/ItemModel.js: -------------------------------------------------------------------------------- 1 | export default function (sequelize, DataTypes) { 2 | const Item = sequelize.define('Item', { 3 | recordId: { 4 | type: DataTypes.INTEGER, 5 | field: 'record_id', 6 | primaryKey: true 7 | }, 8 | name: { 9 | type: DataTypes.STRING, 10 | field: 'name' 11 | }, 12 | state: { 13 | type: DataTypes.INTEGER, 14 | field: 'state' 15 | } 16 | }, { 17 | tableName: 'item', 18 | timestamps: false 19 | }); 20 | 21 | return Item; 22 | } -------------------------------------------------------------------------------- /src/server/models/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import Sequelize from 'sequelize'; 4 | 5 | const db = {}; 6 | 7 | export function installModel(sequelize) { 8 | fs.readdirSync(__dirname) 9 | .filter(file => (file.indexOf('.') !== 0 && file.slice(-3) === '.js' && file !== 'index.js')) 10 | .forEach((file) => { 11 | const model = sequelize.import(path.join(__dirname, file)); 12 | db[model.name] = model; 13 | }); 14 | Object.keys(db).forEach((modelName) => { 15 | if (db[modelName].associate) { 16 | db[modelName].associate(db); 17 | } 18 | }); 19 | db.sequelize = sequelize; 20 | db.Sequelize = Sequelize; 21 | } 22 | 23 | export default db; -------------------------------------------------------------------------------- /src/server/services/TodoService.js: -------------------------------------------------------------------------------- 1 | import BaseService from './base'; 2 | 3 | export default class TodoService extends BaseService { 4 | constructor({ itemDao }) { 5 | super(); 6 | this.itemDao = itemDao; 7 | } 8 | 9 | async getList() { 10 | const [err, list] = await this.itemDao.getList(); 11 | if (err) { 12 | return this.fail('获取列表失败', null, err); 13 | } 14 | return this.success('查询成功', list); 15 | } 16 | 17 | async addTodoItem(item) { 18 | const [exsitErr, itemExsit] = await this.itemDao.findOne({ 19 | where: { 20 | name: item.name 21 | } 22 | }); 23 | if (exsitErr) { 24 | return this.fail('添加失败', null, err); 25 | } 26 | if (itemExsit) { 27 | return this.fail('已存在'); 28 | } 29 | const [addErr] = await this.itemDao.addItem(item); 30 | if (addErr) { 31 | return this.fail('添加失败', null, err); 32 | } 33 | return this.success('添加成功') 34 | } 35 | 36 | async updateItem(item) { 37 | const [err, updateItem] = await this.itemDao.update(item, { 38 | where: { 39 | name: item.name, 40 | } 41 | }); 42 | if (err) { 43 | return this.fail('更新失败'); 44 | } 45 | return this.success('更新成功'); 46 | } 47 | } -------------------------------------------------------------------------------- /src/server/services/base.js: -------------------------------------------------------------------------------- 1 | export default class BaseService { 2 | success(message, data = null) { 3 | return { 4 | success: true, 5 | message, 6 | data 7 | } 8 | } 9 | 10 | fail(message, data = null, err) { 11 | if (err) { 12 | console.error(err); 13 | } 14 | return { 15 | success: false, 16 | message, 17 | data 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/server/util.js: -------------------------------------------------------------------------------- 1 | // 处理promise错误 2 | export function promiseCatch(promise) { 3 | return promise.then(data => [null, data]).catch(err => [err]); 4 | } -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file vue-cli config 3 | */ 4 | const path = require('path'); 5 | const clientPath = path.resolve(process.cwd(), './src/client'); 6 | module.exports = { 7 | configureWebpack: { 8 | entry: [ 9 | path.resolve(clientPath, 'main.js') 10 | ], 11 | resolve: { 12 | alias: { 13 | '@': clientPath 14 | } 15 | }, 16 | devServer: { 17 | proxy: { 18 | '/api': { 19 | target: 'http://localhost:9001' 20 | } 21 | } 22 | } 23 | }, 24 | outputDir: './dist/client/' 25 | }; --------------------------------------------------------------------------------