├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── assets
└── images
│ ├── logo.png
│ └── logo.svg
├── meta.js
├── package.json
├── script
└── generate.js
└── template
├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── client
├── README.md
├── build
│ ├── build.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
├── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ ├── css
│ │ │ ├── animate.styl
│ │ │ ├── flex.styl
│ │ │ └── variable.styl
│ │ ├── fonts
│ │ │ ├── demo.css
│ │ │ ├── demo_fontclass.html
│ │ │ ├── iconfont.css
│ │ │ ├── iconfont.eot
│ │ │ ├── iconfont.js
│ │ │ ├── iconfont.svg
│ │ │ ├── iconfont.ttf
│ │ │ └── iconfont.woff
│ │ └── images
│ │ │ └── login-bg.jpg
│ ├── components
│ │ ├── ContentLoading.vue
│ │ ├── ContentModule.vue
│ │ ├── DataTable.vue
│ │ ├── Header.vue
│ │ ├── NProgress.vue
│ │ ├── NavMenu.vue
│ │ └── Pagination.vue
│ ├── constants.js
│ ├── element-ui.js
│ ├── http
│ │ └── index.js
│ ├── locales
│ │ ├── en.js
│ │ ├── header
│ │ │ ├── en.js
│ │ │ ├── index.js
│ │ │ └── zh-CN.js
│ │ ├── index.js
│ │ ├── login
│ │ │ ├── en.js
│ │ │ ├── index.js
│ │ │ └── zh-CN.js
│ │ ├── menu
│ │ │ ├── en.js
│ │ │ ├── index.js
│ │ │ └── zh-CN.js
│ │ ├── pagination
│ │ │ ├── en.js
│ │ │ ├── index.js
│ │ │ └── zh-CN.js
│ │ ├── things
│ │ │ ├── en.js
│ │ │ ├── index.js
│ │ │ └── zh-CN.js
│ │ ├── users
│ │ │ ├── en.js
│ │ │ ├── index.js
│ │ │ └── zh-CN.js
│ │ └── zh-CN.js
│ ├── main.js
│ ├── resources.js
│ ├── router
│ │ ├── index.js
│ │ └── module.js
│ ├── shared
│ │ ├── search.js
│ │ └── validators.js
│ ├── socket
│ │ └── index.js
│ ├── storage
│ │ └── index.js
│ ├── store
│ │ ├── index.js
│ │ └── modules
│ │ │ ├── global-config.js
│ │ │ ├── route.js
│ │ │ ├── user.api.js
│ │ │ └── user.js
│ ├── stored.js
│ └── view
│ │ ├── CommonView.vue
│ │ ├── Dashboard.vue
│ │ ├── ThingList.vue
│ │ ├── UserList.vue
│ │ └── auth
│ │ └── Login.vue
└── static
│ └── favicon.ico
├── config.js
├── mock
├── README.md
├── ajax
│ ├── things.js
│ └── users.js
├── index.js
└── socket
│ └── dashboard.js
├── nginx.example.conf
├── package.json
├── server
├── README.md
├── api
│ ├── paging.js
│ ├── thing
│ │ ├── index.js
│ │ ├── thing.controller.js
│ │ ├── thing.model.js
│ │ ├── thing.socket.js
│ │ └── thing.spec.js
│ └── user
│ │ ├── index.js
│ │ ├── user.controller.js
│ │ ├── user.model.js
│ │ └── user.model.spec.js
├── app.js
├── auth
│ ├── auth.service.js
│ ├── index.js
│ └── local
│ │ ├── index.js
│ │ └── passport.js
├── components
│ └── errors
│ │ └── index.js
├── config
│ ├── express.js
│ ├── seed.js
│ └── socketio.js
├── routes.js
└── views
│ └── 404.html
└── tasks
├── mock.js
├── remove.js
└── replaceI18n.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6"
4 | before_install:
5 | - git config --global push.default matching
6 | - git config --global user.name "erguotou"
7 | - git config --global user.email "erguotou525@gmail.com"
8 | install:
9 | - npm install -g vue-cli
10 | - npm install
11 | script:
12 | - rm -rf ../vf-backend
13 | - rm -rf ../vf-mock
14 | - node script/generate.js
15 | - echo 'shell pwd'
16 | - pwd
17 | - cd ../vf-mock
18 | - sed -i '4d' .gitignore
19 | - npm install
20 | - npm run remove:mock
21 | - npm run build
22 | - git init
23 | - git add -A
24 | - git commit -m "Auto commit"
25 | - git push -u https://$GH_TOKEN@github.com/erguotou520/vue-fullstack.git HEAD:vf-mock --force
26 | - cd ../vf-backend
27 | - sed -i '4d' .gitignore
28 | - npm install
29 | - npm run build
30 | - git init
31 | - git add -A
32 | - git commit -m "Auto commit"
33 | - git push -u https://$GH_TOKEN@github.com/erguotou520/vue-fullstack.git HEAD:vf-backend --force
34 | cache:
35 | directories:
36 | - node_modules
37 | - $(npm config get prefix)/vue-cli
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 erguotou520
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # Vue fullstack template
3 | 
4 | 
5 | 
6 |
7 | This project's goal is to help people create a **reactive, realtime and user friendly** backend system.:smirk:
8 |
9 | ## Vue version
10 | This template only support vue2, because it use `ElementUI` as the default ui library and `ElementUI` just support vue2.
11 |
12 | ## Requirement
13 | - `Mongodb` This project requires `mongodb` as the database. You can follow [it's tutorial](https://docs.mongodb.com/manual/administration/install-community/) to install it if you haven't installed it already. In consideration of the recent [mongodb attack event](https://www.bleepingcomputer.com/news/security/mongodb-apocalypse-is-here-as-ransom-attacks-hit-10-000-servers/), it's highly recommend to enable auth and disable public login for your mongodb.
14 | - Basic `vue.js` skills
15 |
16 | ## About mock and i18n
17 | If you chose to use mock server or not use i18n at project initialization, you need to take a look at this description, else you can skip this step.
18 | As `vue-cli` does't support to run a task after initialization, you need to run `npm run remove:i18n`(*This script doesn't change the page view which contains `change locale` in `client/src/components/Header.vue` and `client/src/view/auth/Login.vue`, you can remove that code manually.*) and `npm run remove:mock` manually.
19 |
20 | ## Usage
21 | This is a project template for vue-cli. It is recommended to use npm 3+ for a more efficient dependency tree.
22 | ```shell
23 | # cli version must be greater than 2.8.0
24 | $ npm install -g vue-cli
25 | $ vue init erguotou520/vue-fullstack my-project ## (important hint: don't use a dot in your project name, otherwise it will cause issues with mongodb)
26 | $ cd my-project
27 | $ npm install
28 | # If you chose to use mock server, you need to run remove:mock to change the file structure, otherwise just skip this step
29 | $ npm run remove:mock
30 | # If you chose not to use vue-i18n, you need to run remove:i18n to replace the files with default locale text, otherwise just skip this step
31 | $ npm run remove:i18n
32 | # If you chose to use real backend server, run this script to start an express server
33 | $ npm run server
34 | # If you chose to use mock server, run this script to start a mock server
35 | $ npm run mock
36 | # Open another terminal and cd into my-project
37 | # This runs a frontend dev server
38 | $ npm run client
39 | ```
40 |
41 | ## What's Included
42 | - `vue`
43 | - `vue-router`
44 | - `vuex`
45 | - `vue-i18n`
46 | - `vue-resource`
47 | - `element-ui`
48 | - `express`
49 | - `mongoose`
50 | - `socket.io`
51 | - `mock server`
52 |
53 | ## Demo app
54 | ~~* Here is a [demo](https://vue-fullstack-demo.herokuapp.com) and the demo repo is [here](https://github.com/erguotou520/vue-fullstack-demo)~~
55 | ~~* This is [another one](http://meals.erguotou.me)~~
56 |
57 | Now the demo apps are generated by ci automatically and pushed to `vf-backend`|`vf-mock` branches.
58 | - [Backend server demo](https://vf-backend.herokuapp.com)
59 | - [Mock server demo](https://vf-mock.herokuapp.com/)
60 |
61 | *Do not change the password please.*
62 | ```
63 | username: admin
64 | password: admin
65 | ```
66 |
67 | ## App structure
68 | ```
69 | ├─client # frontend source folder
70 | │ ├─build # frontend dev scripts
71 | │ ├─src # frontend src
72 | │ │ ├─assets
73 | │ │ │ ├─css
74 | │ │ │ ├─fonts
75 | │ │ │ └─images
76 | │ │ ├─components # vue components
77 | │ │ ├─http # vue-resource configuration
78 | │ │ ├─locale # vue-i18n configuration
79 | │ │ ├─router # vue-router configuration
80 | │ │ ├─socket # socket.io configuration
81 | │ │ ├─storage # web storage api
82 | │ │ ├─store # vuex store
83 | │ │ │ └─modules
84 | │ │ └─view # app pages
85 | │ │ └─auth
86 | │ └─static # static folder
87 | ├─mock # mock server
88 | │ ├─ajax # ajax mock configs
89 | │ ├─socket # socket.io mock configs
90 | └─server # backend server folder
91 | ├─api # backend api list
92 | │ ├─thing
93 | │ └─user
94 | ├─auth # user auth logical
95 | │ └─local
96 | ├─components # server components
97 | │ └─errors
98 | ├─config # server configs, contains express socket.io, etc.
99 | └─views # server served pages
100 | ```
101 |
102 | ## Configuration
103 | Most of the configuration is concentrated in the `config.js` file and most of them have explicit comments. You need to take a look at it first.
104 |
105 | Here are some important/frequently-used configurations:
106 | - `frontend.port` port that frontend server listens at
107 | - `backend.port` port that backend server listen at
108 | - `backend.secrets.session` secret for session, important when you deploy your app, make sure it's complex enough
109 | - `backend.mongo.uri` change this if your mongodb uri is not matched
110 | - `backend.serverFrontend` whether to server the frontend code. If set to `true` the express server serves the frontend code. Otherwise you may need a http server like nginx to serve frontend code and there is a nginx configuration at `nginx.example.conf` (default true)
111 |
112 | ## Environment variables
113 | When you deploy your app to you cloud server it's easy to configure youre app with `environment variables`. Following are supported:
114 | - `APP_port` or `PORT`: set to `backend.port`
115 | - `APP_HOST` or `APP_IP` or `HOST` or `IP`: set to `backend.ip`
116 | - `MONGODB_URI` or `MONGOHQ_URI`: set to `backend.mongo.uri`
117 | - `SECRET`: set to `backend.secrets.session`
118 |
119 | ## Notice
120 | The generated app is just a template to build your app system fast. Maybe it can't meet your needs, so you need to do some changes at this issue.
121 |
122 | ## License
123 | Under [MIT license](./LICENSE)
124 |
125 | ## Reference resources
126 | - [generator-angular-fullstack](https://github.com/angular-fullstack/generator-angular-fullstack) most express code are copied from this repo :kissing_heart:
127 | - [ElementUI](http://element.eleme.io/) design page fast and easy :+1:
128 |
129 | ## At the end
130 | I am very glad to receive your suggestions and pull request.
131 |
--------------------------------------------------------------------------------
/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erguotou520/vue-fullstack/3bcece5a3effc8ea96125e14c264771373a1d6f8/assets/images/logo.png
--------------------------------------------------------------------------------
/assets/images/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/meta.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "prompts": {
3 | "name": {
4 | "type": "string",
5 | "required": true,
6 | "message": "Project name",
7 | "default": "vue-fullstack"
8 | },
9 | "description": {
10 | "type": "string",
11 | "required": false,
12 | "message": "Project description",
13 | "default": "A vue-fullstack project"
14 | },
15 | "author": {
16 | "type": "string",
17 | "message": "Author"
18 | },
19 | "mock": {
20 | "type": "list",
21 | "message": "Use real backend server or mock server",
22 | "choices": [{
23 | "name": "Backend server",
24 | "value": "backend",
25 | "short": "Backend"
26 | }, {
27 | "name": "Mock server",
28 | "value": "mock",
29 | "short": "Mock"
30 | }]
31 | },
32 | "backendPort": {
33 | "type": "string",
34 | "when": "mock==='backend'",
35 | "message": "Port that backend server listen at development environment",
36 | "default": 9000
37 | },
38 | "frontendPort": {
39 | "type": "string",
40 | "message": "Port that frontend server listen at development environment",
41 | "default": 9001
42 | },
43 | "mongoUri": {
44 | "type": "string",
45 | "when": "mock==='backend'",
46 | "message": "MongoDB uri, '-dev' will be appended in development, default is local uri"
47 | },
48 | "mockPort": {
49 | "type": "string",
50 | "when": "mock==='mock'",
51 | "message": "Port that mock server listen at",
52 | "default": 7878
53 | },
54 | "i18n": {
55 | "type": "confirm",
56 | "message": "Need vue-i18n?"
57 | }
58 | },
59 | "filters": {
60 | "tasks/replaceI18n.js": "!i18n",
61 | "tasks/mock.js": "mock==='mock'",
62 | "tasks/remove.js": "!i18n||mock==='mock'",
63 | "mock/**/*": "mock==='mock'"
64 | },
65 | "skipInterpolation": "{client/**/*.vue,tasks/**/*.*}",
66 | "completeMessage": "To get started:\n\n cd {{destDirName}}\n npm install\n{{#if_eq mock 'mock'}} npm run remove:mock\n{{/if_eq}}{{#unless i18n}} npm run remove:i18n\n{{/unless}}{{#if_eq mock 'backend'}} npm run server\n{{/if_eq}}{{#if_eq mock 'mock'}} npm run mock\n{{/if_eq}} npm run client"
67 | };
68 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-fullstack",
3 | "version": "0.2.1",
4 | "license": "MIT",
5 | "keywords": [
6 | "vue",
7 | "vuejs",
8 | "vuex",
9 | "vue-router",
10 | "vue-i18n",
11 | "vue-resource",
12 | "fullstack",
13 | "mongodb",
14 | "mongoose",
15 | "socket.io",
16 | "element",
17 | "element-ui",
18 | "mock server"
19 | ],
20 | "description": "This project's target is to helper people to create a Reactivity, Realtime, User friendly backend system",
21 | "author": "erguotou525@gmail.com",
22 | "devDependencies": {
23 | "suppose": "^0.6.1"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/script/generate.js:
--------------------------------------------------------------------------------
1 | var suppose = require('suppose')
2 | var path = require('path')
3 | var fs = require('fs')
4 |
5 | var backendProject = 'vf-backend'
6 | var mockProject = 'vf-mock'
7 |
8 | // set the package.json engines
9 | var pkgPath = path.resolve(__dirname, '../template/package.json')
10 | fs.readFile(pkgPath, 'utf-8', function (err, data) {
11 | if (err) throw err
12 | data = data.replace('"node": ">= 4.0.0",', '"node": "6.4.0",')
13 | data = data.replace('"npm": ">= 3.0.0"', '"npm": "3.10.6"')
14 | fs.writeFile(pkgPath, data, function (err) {
15 | if (err) {
16 | console.error('Error when set engines.')
17 | }
18 | })
19 | })
20 |
21 | // cd to parent folder
22 | process.chdir(path.join(__dirname, '../../'))
23 | console.log('Current pwd: ', process.cwd())
24 | // remove folder
25 | // fs.unlinkSync(path.join(__dirname, backendProject))
26 | // fs.unlinkSync(path.join(__dirname, mockProject))
27 | // console.log('Remove backend and mock folder: ', path.join(__dirname, backendProject), path.join(__dirname, mockProject))
28 |
29 | // shell expect
30 | // backend expect
31 | suppose('vue', ['init', './vue-fullstack', backendProject])
32 | .when(/Project name/).respond('\n')
33 | .when(/Project description/).respond('\n')
34 | .when(/Author/).respond('\n')
35 | .when(/Use real backend server or mock server/).respond('\n')
36 | .when(/Port that backend server listen at development environment/).respond("\n")
37 | .when(/Port that frontend server listen at development environment/).respond('\n')
38 | .when(/MongoDB uri/).respond("\n")
39 | .when(/Need vue-i18n/).respond('Y\n')
40 | .on('error', function(err){
41 | console.log(err.message)
42 | })
43 | .end(function(code){
44 | console.log('Finish expecting backend.')
45 | fs.writeFileSync(path.join(__dirname, '../../', backendProject, 'Procfile'), 'web: node server/app.js')
46 | // add code: can't change admin's password
47 | var text = fs.readFileSync(path.join(__dirname, '../../', backendProject, 'server/api/user/user.controller.js'), { encoding: 'utf-8' })
48 | fs.writeFileSync(path.join(__dirname, '../../', backendProject, 'server/api/user/user.controller.js'), text.replace(/exports\.changePassword = function \(req, res, next\) \{/, 'exports.changePassword = function (req, res, next) {\nif(req.user.role==="admin"){return res.sendStatus(200)}\n'))
49 | // mock expect
50 | suppose('vue', ['init', './vue-fullstack', mockProject])
51 | .when(/Project name/).respond('\n')
52 | .when(/Project description/).respond('\n')
53 | .when(/Author/).respond('\n')
54 | .when(/Use real backend server or mock server/).respond('\033\[A\n')
55 | .when(/Port that frontend server listen at development environment/).respond('\n')
56 | .when(/Port that mock server listen at/).respond('\n')
57 | .when(/Need vue-i18n/).respond('Y\n')
58 | .on('error', function(err){
59 | console.log(err.message)
60 | })
61 | .end(function(code){
62 | console.log('Finish expecting mock.')
63 | fs.writeFileSync(path.join(__dirname, '../../', mockProject, 'Procfile'), 'web: node mock/index.js')
64 | })
65 | })
66 |
--------------------------------------------------------------------------------
/template/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", { "modules": false }],
4 | "stage-2"
5 | ],
6 | "plugins": ["transform-runtime", ["component", [
7 | {
8 | "libraryName": "element-ui",
9 | "styleLibraryName": "theme-default"
10 | }
11 | ]]],
12 | "comments": false
13 | }
14 |
--------------------------------------------------------------------------------
/template/.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 |
--------------------------------------------------------------------------------
/template/.eslintignore:
--------------------------------------------------------------------------------
1 | {{#if_eq mock 'backend'}}client/{{/if_eq}}src/assets/fonts
2 |
--------------------------------------------------------------------------------
/template/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // https://github.com/vuejs/eslint-config-vue
4 | extends: 'vue',
5 | // required to lint *.vue files
6 | plugins: [
7 | 'html'
8 | ],
9 | // add your custom rules here
10 | 'rules': {
11 | "no-sequences": [0],
12 | "no-debugger": process.env.NODE_ENV === 'production' ? 2 : 0
13 | },
14 | globals: {
15 | "it": true,
16 | "describe": true
17 | },
18 | parser: 'babel-eslint',
19 | parserOptions: {
20 | sourceType: 'module',
21 | allowImportExportEverywhere: false
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/template/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log
4 | {{#if_eq mock 'backend'}}client/{{/if_eq}}dist
5 |
--------------------------------------------------------------------------------
/template/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 {{author}}
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/template/README.md:
--------------------------------------------------------------------------------
1 | # {{name}}
2 | This is a NodeJs fullstack project using `express`, `mongodb`, `passport`, `vue`, `vue-router`, `vuex`, etc.
3 |
4 | ## Feature
5 | - Separate for backend and frontend when development
6 | - Configurable
7 | - Restfull api
8 |
9 | ## Before dev
10 | 1. Install `mongodb` follow [official manual](https://docs.mongodb.com/manual/installation/). It's recommend to use [MongoChef](3t.io/mongochef/) as the db client.
11 | 2. NodeJs installed.
12 |
13 | ## Dev step
14 | 1. Open terminal and run `npm install`, if you don't choose i18n when initialization, you need to run `npm run remove:i18n` here manually
15 | 2. Run `npm run server`, this will initial the db and `User` document if not exists
16 | 3. Open other terminal and run `npm run client`, you can combine the two command with `npm run dev`
17 | 4. Open browser and nav to `localhost:9001` (the default port is 9001, if you change this, change the port)
18 |
19 | ## Build
20 | Run `npm run build`
21 |
22 | ## App structure
23 | ```
24 | ├─client # frontend source folder
25 | │ ├─build # frontend dev scripts
26 | │ ├─src # frontend src
27 | │ │ ├─assets
28 | │ │ │ ├─css
29 | │ │ │ ├─fonts
30 | │ │ │ └─images
31 | │ │ ├─components # vue components
32 | │ │ ├─http # vue-resource configuration
33 | │ │ ├─locale # vue-i18n configuration
34 | │ │ ├─router # vue-router configuration
35 | │ │ ├─socket # socket.io configuration
36 | │ │ ├─storage # web storage api
37 | │ │ ├─store # vuex store
38 | │ │ │ └─modules
39 | │ │ └─view # app pages
40 | │ │ └─auth
41 | │ └─static # static folder
42 | └─server # backend server folder
43 | ├─api # backend api list
44 | │ ├─thing
45 | │ └─user
46 | ├─auth # user auth logical
47 | │ └─local
48 | ├─components # server components
49 | │ └─errors
50 | ├─config # server configs, contains express socket.io, etc.
51 | └─views # server servered pages
52 | ```
53 |
54 | ## Configuration
55 | Most of the configuration is concentrated in the `config.js` file, and most of them have explicit comments, you need to take a look at it first.
56 |
57 | Here is some important/frequently-used configuration:
58 | - `frontend.port` port that frontend server listens at
59 | - `backend.port` port that backend server listen at
60 | - `backend.secrets.session` secret for session, important when you deploy your app, make sure it's complex enough
61 | - `backend.mongo.uri` change this if your mongodb uri is not matched
62 | - `backend.serverFrontend` whether to server the frontend code. If set to `true` the express server servers the frontend code, otherwise you may need a http server like nginx to server frontend code and there is a nginx configuration at `nginx.example.conf` (default true)
63 |
64 | ## Environment variable
65 | When you deploy your app to you cloud server, it's easy to config you app with `environment variable`, here is the supported:
66 | - `APP_port` or `PORT`: set to `backend.port`
67 | - `APP_HOST` or `APP_IP` or `HOST` or `IP`: set to `backend.ip`
68 | - `MONGODB_URI` or `MONGOHQ_URI`: set to `backend.mongo.uri`
69 | - `SECRET`: set to `backend.secrets.session`
70 |
71 | ## Notice
72 | The generated app is just a template to build your app system fast, maybe it can't meet your needs, so you need to do some change at this issue.
73 |
74 | ## License
75 | Under [MIT license](./LICENSE)
76 |
--------------------------------------------------------------------------------
/template/client/README.md:
--------------------------------------------------------------------------------
1 | # Frontend folder
2 |
--------------------------------------------------------------------------------
/template/client/build/build.js:
--------------------------------------------------------------------------------
1 | // https://github.com/shelljs/shelljs
2 | require('shelljs/global')
3 | var path = require('path')
4 | process.env.NODE_ENV = 'production'
5 | var config = require({{#if_eq mock "mock"}}'../config'{{/if_eq}}{{#if_eq mock "backend"}}'../../config'{{/if_eq}}).frontend
6 | var ora = require('ora')
7 | var webpack = require('webpack')
8 | var webpackConfig = require('./webpack.prod.conf')
9 |
10 | console.log(
11 | ' Tip:\n' +
12 | ' Built files are meant to be served over an HTTP server.\n' +
13 | ' Opening index.html over file:// won\'t work.\n'
14 | )
15 |
16 | var spinner = ora('building for production...')
17 | spinner.start()
18 |
19 | var assetsPath = path.join(config.assetsRoot, config.assetsSubDirectory)
20 | rm('-rf', assetsPath) // eslint-disable-line
21 | mkdir('-p', assetsPath) // eslint-disable-line
22 | cp('-R', {{#if_eq mock "mock"}}'static/*'{{/if_eq}}{{#if_eq mock "backend"}}'client/static/*'{{/if_eq}}, assetsPath) // eslint-disable-line
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')
34 | })
35 |
--------------------------------------------------------------------------------
/template/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 |
--------------------------------------------------------------------------------
/template/client/build/dev-server.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'development'
2 | var config = require({{#if_eq mock "mock"}}'../config'{{/if_eq}}{{#if_eq mock "backend"}}'../../config'{{/if_eq}}).frontend
3 | var path = require('path')
4 | var express = require('express')
5 | var webpack = require('webpack')
6 | var proxyMiddleware = require('http-proxy-middleware')
7 | var webpackConfig = require('./webpack.dev.conf')
8 |
9 | // default port where dev server listens for incoming traffic
10 | var port = config.port || 8080
11 | // Define HTTP proxies to your custom API backend
12 | // https://github.com/chimurai/http-proxy-middleware
13 | var proxyTable = config.proxyTable
14 |
15 | var app = express()
16 | var compiler = webpack(webpackConfig)
17 |
18 | var devMiddleware = require('webpack-dev-middleware')(compiler, {
19 | publicPath: webpackConfig.output.publicPath,
20 | quiet: true
21 | })
22 |
23 | var hotMiddleware = require('webpack-hot-middleware')(compiler, {
24 | log: () => {}
25 | })
26 | // force page reload when html-webpack-plugin template changes
27 | compiler.plugin('compilation', function (compilation) {
28 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
29 | hotMiddleware.publish({ action: 'reload' })
30 | cb()
31 | })
32 | })
33 |
34 | // proxy api requests
35 | Object.keys(proxyTable).forEach(function (context) {
36 | var options = proxyTable[context]
37 | if (typeof options === 'string') {
38 | options = { target: options }
39 | }
40 | app.use(proxyMiddleware(options.filter || context, options))
41 | })
42 |
43 | // handle fallback for HTML5 history API
44 | app.use(require('connect-history-api-fallback')())
45 |
46 | // serve webpack bundle output
47 | app.use(devMiddleware)
48 |
49 | // enable hot-reload and state-preserving
50 | // compilation error display
51 | app.use(hotMiddleware)
52 |
53 | // serve pure static assets
54 | var staticPath = path.posix.join(config.assetsPublicPath, config.assetsSubDirectory)
55 | app.use(staticPath, express.static(path.join(__dirname, '../static')))
56 |
57 | module.exports = app.listen(port, function (err) {
58 | if (err) {
59 | console.log(err)
60 | return
61 | }
62 | var uri = 'http://localhost:' + port
63 | console.log('Listening at ' + uri + '\n')
64 | })
65 |
--------------------------------------------------------------------------------
/template/client/build/utils.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require({{#if_eq mock "mock"}}'../config'{{/if_eq}}{{#if_eq mock "backend"}}'../../config'{{/if_eq}}).frontend
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
4 |
5 | exports.assetsPath = function (_path) {
6 | var assetsSubDirectory = config.assetsSubDirectory
7 | return path.posix.join(assetsSubDirectory, _path)
8 | }
9 |
10 | exports.cssLoaders = function (options) {
11 | options = options || {}
12 |
13 | var cssLoader = {
14 | loader: 'css-loader',
15 | options: {
16 | minimize: process.env.NODE_ENV === 'production',
17 | sourceMap: options.sourceMap
18 | }
19 | }
20 |
21 | // generate loader string to be used with extract text plugin
22 | function generateLoaders (loader, loaderOptions) {
23 | var loaders = [cssLoader]
24 | if (loader) {
25 | loaders.push({
26 | loader: loader + '-loader',
27 | options: Object.assign({}, loaderOptions, {
28 | sourceMap: options.sourceMap
29 | })
30 | })
31 | }
32 |
33 | // Extract CSS when that option is specified
34 | // (which is the case during production build)
35 | if (options.extract) {
36 | return ExtractTextPlugin.extract({
37 | use: loaders,
38 | fallback: 'vue-style-loader'
39 | })
40 | } else {
41 | return ['vue-style-loader'].concat(loaders)
42 | }
43 | }
44 |
45 | // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
46 | return {
47 | css: generateLoaders(),
48 | postcss: generateLoaders(),
49 | less: generateLoaders('less'),
50 | sass: generateLoaders('sass', { indentedSyntax: true }),
51 | scss: generateLoaders('sass'),
52 | stylus: generateLoaders('stylus'),
53 | styl: generateLoaders('stylus')
54 | }
55 | }
56 |
57 | // Generate loaders for standalone style files (outside of .vue)
58 | exports.styleLoaders = function (options) {
59 | var output = []
60 | var loaders = exports.cssLoaders(options)
61 | for (var extension in loaders) {
62 | var loader = loaders[extension]
63 | output.push({
64 | test: new RegExp('\\.' + extension + '$'),
65 | use: loader
66 | })
67 | }
68 | return output
69 | }
70 |
--------------------------------------------------------------------------------
/template/client/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var config = require({{#if_eq mock "mock"}}'../config'{{/if_eq}}{{#if_eq mock "backend"}}'../../config'{{/if_eq}}).frontend
3 | var isProduction = process.env.NODE_ENV === 'production'
4 |
5 | module.exports = {
6 | loaders: utils.cssLoaders({
7 | sourceMap: config.cssSourceMap,
8 | extract: isProduction
9 | }),
10 | postcss: [
11 | require('autoprefixer')({
12 | browsers: ['last 2 versions']
13 | })
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/template/client/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require({{#if_eq mock "mock"}}'../config'{{/if_eq}}{{#if_eq mock "backend"}}'../../config'{{/if_eq}}).frontend
3 | var vueLoaderConfig = require('./vue-loader.conf')
4 | var utils = require('./utils')
5 |
6 | function resolve (dir) {
7 | return path.join(__dirname, '..', dir)
8 | }
9 |
10 | module.exports = {
11 | entry: {
12 | app: resolve('src/main.js')
13 | },
14 | output: {
15 | path: config.assetsRoot,
16 | publicPath: config.assetsPublicPath,
17 | filename: '[name].js'
18 | },
19 | resolve: {
20 | extensions: ['.js', '.vue', '.json'],
21 | modules: [resolve('src'), resolve({{#if_eq mock "mock"}}'node_modules'{{/if_eq}}{{#if_eq mock "backend"}}'../node_modules'{{/if_eq}})],
22 | alias: {
23 | 'vue$': 'vue/dist/vue.common.js',
24 | 'src': resolve('src'),
25 | 'assets': resolve('src/assets'),
26 | 'components': resolve('src/components'),
27 | 'locales': resolve('src/locales'),
28 | 'shared': resolve('src/shared'),
29 | 'resources': resolve('src/resources')
30 | }
31 | },
32 | module: {
33 | rules: [
34 | {
35 | test: /\.(js|vue)$/,
36 | loader: 'eslint-loader',
37 | enforce: 'pre',
38 | include: [resolve('src'), resolve('test')],
39 | options: {
40 | formatter: require('eslint-friendly-formatter')
41 | }
42 | },
43 | {
44 | test: /\.vue$/,
45 | loader: 'vue-loader',
46 | options: vueLoaderConfig
47 | },
48 | {
49 | test: /\.js$/,
50 | loader: 'babel-loader',
51 | include: [resolve('src')]
52 | },
53 | {
54 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
55 | loader: 'url-loader',
56 | query: {
57 | limit: 10000,
58 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
59 | }
60 | },
61 | {
62 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
63 | loader: 'url-loader',
64 | query: {
65 | limit: 10000,
66 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
67 | }
68 | }
69 | ]
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/template/client/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | var config = require({{#if_eq mock "mock"}}'../config'{{/if_eq}}{{#if_eq mock "backend"}}'../../config'{{/if_eq}}).frontend
2 | var webpack = require('webpack')
3 | var merge = require('webpack-merge')
4 | var utils = require('./utils')
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] = [{{#if_eq mock "mock"}}'./build/dev-client'{{/if_eq}}{{#if_eq mock "backend"}}'./client/build/dev-client'{{/if_eq}}].concat(baseWebpackConfig.entry[name])
12 | })
13 |
14 | module.exports = merge(baseWebpackConfig, {
15 | module: {
16 | rules: utils.styleLoaders({ sourceMap: config.cssSourceMap })
17 | },
18 | // cheap-module-source-map is faster for development
19 | devtool: '#cheap-module-eval-source-map',
20 | plugins: [
21 | new webpack.DefinePlugin({
22 | 'process.env': '"development"'
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: '{{#if_eq mock "mock"}}index.html{{/if_eq}}{{#if_eq mock "backend"}}client/index.html{{/if_eq}}',
31 | inject: true
32 | }),
33 | new FriendlyErrorsPlugin()
34 | ]
35 | })
36 |
--------------------------------------------------------------------------------
/template/client/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require({{#if_eq mock "mock"}}'../config'{{/if_eq}}{{#if_eq mock "backend"}}'../../config'{{/if_eq}}).frontend
3 | var utils = require('./utils')
4 | var webpack = require('webpack')
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 OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
10 |
11 | var webpackConfig = merge(baseWebpackConfig, {
12 | module: {
13 | rules: utils.styleLoaders({ sourceMap: config.cssSourceMap, extract: true })
14 | },
15 | devtool: config.cssSourceMap ? '#source-map' : false,
16 | output: {
17 | path: config.assetsRoot,
18 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
19 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
20 | },
21 | plugins: [
22 | // http://vuejs.github.io/vue-loader/workflow/production.html
23 | new webpack.DefinePlugin({
24 | 'process.env': '"production"'
25 | }),
26 | new webpack.optimize.UglifyJsPlugin({
27 | sourceMap: true,
28 | compress: {
29 | warnings: false
30 | }
31 | }),
32 | // extract css into its own file
33 | new ExtractTextPlugin({
34 | filename: utils.assetsPath('css/[name].[contenthash].css')
35 | }),
36 | // Compress extracted CSS. We are using this plugin so that possible
37 | // duplicated CSS from different components can be deduped.
38 | new OptimizeCSSPlugin(),
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.index,
44 | template: '{{#if_eq mock "mock"}}index.html{{/if_eq}}{{#if_eq mock "backend"}}client/index.html{{/if_eq}}',
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 | chunks: ['manifest', 'vendor', 'element', 'app'],
54 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
55 | chunksSortMode: function (a, b) {
56 | var orders = ['manifest', 'vendor', 'element', 'app']
57 | var order1 = orders.indexOf(a.names[0])
58 | var order2 = orders.indexOf(b.names[0])
59 | if (order1 > order2) {
60 | return 1
61 | } else if (order1 < order2) {
62 | return -1
63 | } else {
64 | return 0
65 | }
66 | }
67 | }),
68 | // split vendor js into its own file
69 | new webpack.optimize.CommonsChunkPlugin({
70 | name: 'vendor',
71 | minChunks: function (module, count) {
72 | // any required modules inside node_modules are extracted to vendor
73 | return (
74 | module.resource &&
75 | /\.js$/.test(module.resource) &&
76 | module.resource.indexOf(
77 | path.join(__dirname, {{#if_eq mock "mock"}}'../node_modules'{{/if_eq}}{{#if_eq mock "backend"}}'../../node_modules'{{/if_eq}})
78 | ) === 0
79 | )
80 | }
81 | }),
82 | new webpack.optimize.CommonsChunkPlugin({
83 | name: 'element',
84 | minChunks: function (module, count) {
85 | // element-ui will extracted to element
86 | return (
87 | module.resource.indexOf(
88 | path.join(__dirname, {{#if_eq mock "mock"}}'../node_modules/element-ui'{{/if_eq}}{{#if_eq mock "backend"}}'../../node_modules/element-ui'{{/if_eq}})
89 | ) === 0
90 | )
91 | }
92 | }),
93 | // extract webpack runtime and module manifest to its own file in order to
94 | // prevent vendor hash from being updated whenever app bundle is updated
95 | new webpack.optimize.CommonsChunkPlugin({
96 | name: 'manifest',
97 | chunks: ['vendor', 'element']
98 | })
99 | ]
100 | })
101 |
102 | if (config.productionGzip) {
103 | var CompressionWebpackPlugin = require('compression-webpack-plugin')
104 |
105 | webpackConfig.plugins.push(
106 | new CompressionWebpackPlugin({
107 | asset: '[path].gz[query]',
108 | algorithm: 'gzip',
109 | test: new RegExp(
110 | '\\.(' +
111 | config.productionGzipExtensions.join('|') +
112 | ')$'
113 | ),
114 | threshold: 10240,
115 | minRatio: 0.8
116 | })
117 | )
118 | }
119 |
120 | if (config.bundleAnalyzerReport) {
121 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
122 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
123 | }
124 |
125 | module.exports = webpackConfig
126 |
--------------------------------------------------------------------------------
/template/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{name}}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/template/client/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
33 |
88 |
--------------------------------------------------------------------------------
/template/client/src/assets/css/animate.styl:
--------------------------------------------------------------------------------
1 | @keyframes shake
2 | from, to
3 | transform translate3d(0, 0, 0)
4 | 10%, 30%, 50%, 70%, 90%
5 | transform translate3d(-10px, 0, 0)
6 | 20%, 40%, 60%, 80%
7 | transform translate3d(10px, 0, 0)
8 |
9 | @keyframes rotate
10 | 0%
11 | transform rotate(0deg) scale(1)
12 | 50%
13 | transform rotate(180deg) scale(0.6)
14 | 100%
15 | transform rotate(360deg) scale(1)
16 |
--------------------------------------------------------------------------------
/template/client/src/assets/css/flex.styl:
--------------------------------------------------------------------------------
1 | /**
2 | * FLex display helper
3 | */
4 | .flex
5 | display flex
6 | .flex-inline
7 | display inline-flex
8 | .flex-1
9 | flex 1
10 | .flex-row
11 | flex-direction row
12 | .flex-column
13 | flex-direction column
14 | .flex-main-center
15 | justify-content center
16 | .flex-main-start
17 | justify-content flex-start
18 | .flex-main-end
19 | justify-content flex-end
20 | .flex-cross-center
21 | align-items center
22 | .flex-between
23 | justify-content space-between
24 |
--------------------------------------------------------------------------------
/template/client/src/assets/css/variable.styl:
--------------------------------------------------------------------------------
1 | // primary color
2 | $color-primary = #20a0ff
3 | $color-primary-light = #58B7FF
4 | $color-primary-dark = #1D8CE0
5 |
6 | $color-success = #13CE66
7 | $color-warning = #F7BA2A
8 | $color-danger = #FF4949
9 |
10 | $color-black = #1F2D3D
11 | $color-black-light = #324057
12 | $color-black-exact-light = #475669
13 |
14 | $color-silver = #8492A6
15 | $color-silver-light = #99A9BF
16 | $color-silver-exact-light = #C0CCDA
17 |
18 | $color-gray = #D3DCE6
19 | $color-gray-light = #E5E9F2
20 | $color-gray-exact-light = #EFF2F7
21 |
22 | $color-white = #fff
23 | $color-white-dark = #F9FAFC
24 |
25 | $header-height = 3.5rem
26 | $menu-width = 12.5rem
27 |
--------------------------------------------------------------------------------
/template/client/src/assets/fonts/demo.css:
--------------------------------------------------------------------------------
1 | *{margin: 0;padding: 0;list-style: none;}
2 | /*
3 | KISSY CSS Reset
4 | 理念:1. reset 的目的不是清除浏览器的默认样式,这仅是部分工作。清除和重置是紧密不可分的。
5 | 2. reset 的目的不是让默认样式在所有浏览器下一致,而是减少默认样式有可能带来的问题。
6 | 3. reset 期望提供一套普适通用的基础样式。但没有银弹,推荐根据具体需求,裁剪和修改后再使用。
7 | 特色:1. 适应中文;2. 基于最新主流浏览器。
8 | 维护:玉伯, 正淳
9 | */
10 |
11 | /** 清除内外边距 **/
12 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */
13 | dl, dt, dd, ul, ol, li, /* list elements 列表元素 */
14 | pre, /* text formatting elements 文本格式元素 */
15 | form, fieldset, legend, button, input, textarea, /* form elements 表单元素 */
16 | th, td /* table elements 表格元素 */ {
17 | margin: 0;
18 | padding: 0;
19 | }
20 |
21 | /** 设置默认字体 **/
22 | body,
23 | button, input, select, textarea /* for ie */ {
24 | font: 12px/1.5 tahoma, arial, \5b8b\4f53, sans-serif;
25 | }
26 | h1, h2, h3, h4, h5, h6 { font-size: 100%; }
27 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */
28 | code, kbd, pre, samp { font-family: courier new, courier, monospace; } /* 统一等宽字体 */
29 | small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */
30 |
31 | /** 重置列表元素 **/
32 | ul, ol { list-style: none; }
33 |
34 | /** 重置文本格式元素 **/
35 | a { text-decoration: none; }
36 | a:hover { text-decoration: underline; }
37 |
38 |
39 | /** 重置表单元素 **/
40 | legend { color: #000; } /* for ie6 */
41 | fieldset, img { border: 0; } /* img 搭车:让链接里的 img 无边框 */
42 | button, input, select, textarea { font-size: 100%; } /* 使得表单元素在 ie 下能继承字体大小 */
43 | /* 注:optgroup 无法扶正 */
44 |
45 | /** 重置表格元素 **/
46 | table { border-collapse: collapse; border-spacing: 0; }
47 |
48 | /* 清除浮动 */
49 | .ks-clear:after, .clear:after {
50 | content: '\20';
51 | display: block;
52 | height: 0;
53 | clear: both;
54 | }
55 | .ks-clear, .clear {
56 | *zoom: 1;
57 | }
58 |
59 | .main {
60 | padding: 30px 100px;
61 | width: 960px;
62 | margin: 0 auto;
63 | }
64 | .main h1{font-size:36px; color:#333; text-align:left;margin-bottom:30px; border-bottom: 1px solid #eee;}
65 |
66 | .helps{margin-top:40px;}
67 | .helps pre{
68 | padding:20px;
69 | margin:10px 0;
70 | border:solid 1px #e7e1cd;
71 | background-color: #fffdef;
72 | overflow: auto;
73 | }
74 |
75 | .icon_lists{
76 | width: 100% !important;
77 |
78 | }
79 |
80 | .icon_lists li{
81 | float:left;
82 | width: 100px;
83 | height:180px;
84 | text-align: center;
85 | list-style: none !important;
86 | }
87 | .icon_lists .icon{
88 | font-size: 42px;
89 | line-height: 100px;
90 | margin: 10px 0;
91 | color:#333;
92 | -webkit-transition: font-size 0.25s ease-out 0s;
93 | -moz-transition: font-size 0.25s ease-out 0s;
94 | transition: font-size 0.25s ease-out 0s;
95 |
96 | }
97 | .icon_lists .icon:hover{
98 | font-size: 100px;
99 | }
100 |
101 |
102 |
103 | .markdown {
104 | color: #666;
105 | font-size: 14px;
106 | line-height: 1.8;
107 | }
108 |
109 | .highlight {
110 | line-height: 1.5;
111 | }
112 |
113 | .markdown img {
114 | vertical-align: middle;
115 | max-width: 100%;
116 | }
117 |
118 | .markdown h1 {
119 | color: #404040;
120 | font-weight: 500;
121 | line-height: 40px;
122 | margin-bottom: 24px;
123 | }
124 |
125 | .markdown h2,
126 | .markdown h3,
127 | .markdown h4,
128 | .markdown h5,
129 | .markdown h6 {
130 | color: #404040;
131 | margin: 1.6em 0 0.6em 0;
132 | font-weight: 500;
133 | clear: both;
134 | }
135 |
136 | .markdown h1 {
137 | font-size: 28px;
138 | }
139 |
140 | .markdown h2 {
141 | font-size: 22px;
142 | }
143 |
144 | .markdown h3 {
145 | font-size: 16px;
146 | }
147 |
148 | .markdown h4 {
149 | font-size: 14px;
150 | }
151 |
152 | .markdown h5 {
153 | font-size: 12px;
154 | }
155 |
156 | .markdown h6 {
157 | font-size: 12px;
158 | }
159 |
160 | .markdown hr {
161 | height: 1px;
162 | border: 0;
163 | background: #e9e9e9;
164 | margin: 16px 0;
165 | clear: both;
166 | }
167 |
168 | .markdown p,
169 | .markdown pre {
170 | margin: 1em 0;
171 | }
172 |
173 | .markdown > p,
174 | .markdown > blockquote,
175 | .markdown > .highlight,
176 | .markdown > ol,
177 | .markdown > ul {
178 | width: 80%;
179 | }
180 |
181 | .markdown ul > li {
182 | list-style: circle;
183 | }
184 |
185 | .markdown > ul li,
186 | .markdown blockquote ul > li {
187 | margin-left: 20px;
188 | padding-left: 4px;
189 | }
190 |
191 | .markdown > ul li p,
192 | .markdown > ol li p {
193 | margin: 0.6em 0;
194 | }
195 |
196 | .markdown ol > li {
197 | list-style: decimal;
198 | }
199 |
200 | .markdown > ol li,
201 | .markdown blockquote ol > li {
202 | margin-left: 20px;
203 | padding-left: 4px;
204 | }
205 |
206 | .markdown code {
207 | margin: 0 3px;
208 | padding: 0 5px;
209 | background: #eee;
210 | border-radius: 3px;
211 | }
212 |
213 | .markdown pre {
214 | border-radius: 6px;
215 | background: #f7f7f7;
216 | padding: 20px;
217 | }
218 |
219 | .markdown pre code {
220 | border: none;
221 | background: #f7f7f7;
222 | margin: 0;
223 | }
224 |
225 | .markdown strong,
226 | .markdown b {
227 | font-weight: 600;
228 | }
229 |
230 | .markdown > table {
231 | border-collapse: collapse;
232 | border-spacing: 0px;
233 | empty-cells: show;
234 | border: 1px solid #e9e9e9;
235 | width: 95%;
236 | margin-bottom: 24px;
237 | }
238 |
239 | .markdown > table th {
240 | white-space: nowrap;
241 | color: #333;
242 | font-weight: 600;
243 |
244 | }
245 |
246 | .markdown > table th,
247 | .markdown > table td {
248 | border: 1px solid #e9e9e9;
249 | padding: 8px 16px;
250 | text-align: left;
251 | }
252 |
253 | .markdown > table th {
254 | background: #F7F7F7;
255 | }
256 |
257 | .markdown blockquote {
258 | font-size: 90%;
259 | color: #999;
260 | border-left: 4px solid #e9e9e9;
261 | padding-left: 0.8em;
262 | margin: 1em 0;
263 | font-style: italic;
264 | }
265 |
266 | .markdown blockquote p {
267 | margin: 0;
268 | }
269 |
270 | .markdown .anchor {
271 | opacity: 0;
272 | transition: opacity 0.3s ease;
273 | margin-left: 8px;
274 | }
275 |
276 | .markdown .waiting {
277 | color: #ccc;
278 | }
279 |
280 | .markdown h1:hover .anchor,
281 | .markdown h2:hover .anchor,
282 | .markdown h3:hover .anchor,
283 | .markdown h4:hover .anchor,
284 | .markdown h5:hover .anchor,
285 | .markdown h6:hover .anchor {
286 | opacity: 1;
287 | display: inline-block;
288 | }
289 |
290 | .markdown > br,
291 | .markdown > p > br {
292 | clear: both;
293 | }
294 |
295 |
296 | .hljs {
297 | display: block;
298 | background: white;
299 | padding: 0.5em;
300 | color: #333333;
301 | overflow-x: auto;
302 | }
303 |
304 | .hljs-comment,
305 | .hljs-meta {
306 | color: #969896;
307 | }
308 |
309 | .hljs-string,
310 | .hljs-variable,
311 | .hljs-template-variable,
312 | .hljs-strong,
313 | .hljs-emphasis,
314 | .hljs-quote {
315 | color: #df5000;
316 | }
317 |
318 | .hljs-keyword,
319 | .hljs-selector-tag,
320 | .hljs-type {
321 | color: #a71d5d;
322 | }
323 |
324 | .hljs-literal,
325 | .hljs-symbol,
326 | .hljs-bullet,
327 | .hljs-attribute {
328 | color: #0086b3;
329 | }
330 |
331 | .hljs-section,
332 | .hljs-name {
333 | color: #63a35c;
334 | }
335 |
336 | .hljs-tag {
337 | color: #333333;
338 | }
339 |
340 | .hljs-title,
341 | .hljs-attr,
342 | .hljs-selector-id,
343 | .hljs-selector-class,
344 | .hljs-selector-attr,
345 | .hljs-selector-pseudo {
346 | color: #795da3;
347 | }
348 |
349 | .hljs-addition {
350 | color: #55a532;
351 | background-color: #eaffea;
352 | }
353 |
354 | .hljs-deletion {
355 | color: #bd2c00;
356 | background-color: #ffecec;
357 | }
358 |
359 | .hljs-link {
360 | text-decoration: underline;
361 | }
362 |
363 | pre{
364 | background: #fff;
365 | }
366 |
367 |
368 |
369 |
370 |
371 |
--------------------------------------------------------------------------------
/template/client/src/assets/fonts/demo_fontclass.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | IconFont
7 |
8 |
9 |
10 |
11 |
12 |
IconFont 图标
13 |
14 |
15 | -
16 |
17 |
刷新
18 | .icon-refresh
19 |
20 |
21 | -
22 |
23 |
trailer-page
24 | .icon-last-page
25 |
26 |
27 | -
28 |
29 |
home-page
30 | .icon-home-page
31 |
32 |
33 |
34 |
35 |
font-class引用
36 |
37 |
38 |
font-class是unicode使用方式的一种变种,主要是解决unicode书写不直观,语意不明确的问题。
39 |
与unicode使用方式相比,具有如下特点:
40 |
41 | - 兼容性良好,支持ie8+,及所有现代浏览器。
42 | - 相比于unicode语意明确,书写更直观。可以很容易分辨这个icon是什么。
43 | - 因为使用class来定义图标,所以当要替换图标时,只需要修改class里面的unicode引用。
44 | - 不过因为本质上还是使用的字体,所以多色图标还是不支持的。
45 |
46 |
使用步骤如下:
47 |
第一步:引入项目下面生成的fontclass代码:
48 |
49 |
50 |
51 |
第二步:挑选相应图标并获取类名,应用于页面:
52 |
<i class="iconfont icon-xxx"></i>
53 |
54 | 实际情况中"iconfont"(font-family)需要修改为你项目下的font-family。可以通过编辑项目查看,默认是"iconfont"。
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/template/client/src/assets/fonts/iconfont.css:
--------------------------------------------------------------------------------
1 |
2 | @font-face {font-family: "iconfont";
3 | src: url('iconfont.eot?t=1478412965459'); /* IE9*/
4 | src: url('iconfont.eot?t=1478412965459#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url('iconfont.woff?t=1478412965459') format('woff'), /* chrome, firefox */
6 | url('iconfont.ttf?t=1478412965459') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7 | url('iconfont.svg?t=1478412965459#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family:"iconfont" !important;
12 | font-size:16px;
13 | font-style:normal;
14 | -webkit-font-smoothing: antialiased;
15 | -webkit-text-stroke-width: 0.2px;
16 | -moz-osx-font-smoothing: grayscale;
17 | }
18 |
19 | .icon-refresh:before { content: "\e623"; }
20 |
21 | .icon-last-page:before { content: "\e81a"; }
22 |
23 | .icon-home-page:before { content: "\e626"; }
24 |
25 |
--------------------------------------------------------------------------------
/template/client/src/assets/fonts/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erguotou520/vue-fullstack/3bcece5a3effc8ea96125e14c264771373a1d6f8/template/client/src/assets/fonts/iconfont.eot
--------------------------------------------------------------------------------
/template/client/src/assets/fonts/iconfont.js:
--------------------------------------------------------------------------------
1 | ;(function(window) {
2 |
3 | var svgSprite = ''
28 | var script = function() {
29 | var scripts = document.getElementsByTagName('script')
30 | return scripts[scripts.length - 1]
31 | }()
32 | var shouldInjectCss = script.getAttribute("data-injectcss")
33 |
34 | /**
35 | * document ready
36 | */
37 | var ready = function(fn){
38 | if(document.addEventListener){
39 | document.addEventListener("DOMContentLoaded",function(){
40 | document.removeEventListener("DOMContentLoaded",arguments.callee,false)
41 | fn()
42 | },false)
43 | }else if(document.attachEvent){
44 | IEContentLoaded (window, fn)
45 | }
46 |
47 | function IEContentLoaded (w, fn) {
48 | var d = w.document, done = false,
49 | // only fire once
50 | init = function () {
51 | if (!done) {
52 | done = true
53 | fn()
54 | }
55 | }
56 | // polling for no errors
57 | ;(function () {
58 | try {
59 | // throws errors until after ondocumentready
60 | d.documentElement.doScroll('left')
61 | } catch (e) {
62 | setTimeout(arguments.callee, 50)
63 | return
64 | }
65 | // no errors, fire
66 |
67 | init()
68 | })()
69 | // trying to always fire before onload
70 | d.onreadystatechange = function() {
71 | if (d.readyState == 'complete') {
72 | d.onreadystatechange = null
73 | init()
74 | }
75 | }
76 | }
77 | }
78 |
79 | /**
80 | * Insert el before target
81 | *
82 | * @param {Element} el
83 | * @param {Element} target
84 | */
85 |
86 | var before = function (el, target) {
87 | target.parentNode.insertBefore(el, target)
88 | }
89 |
90 | /**
91 | * Prepend el to target
92 | *
93 | * @param {Element} el
94 | * @param {Element} target
95 | */
96 |
97 | var prepend = function (el, target) {
98 | if (target.firstChild) {
99 | before(el, target.firstChild)
100 | } else {
101 | target.appendChild(el)
102 | }
103 | }
104 |
105 | function appendSvg(){
106 | var div,svg
107 |
108 | div = document.createElement('div')
109 | div.innerHTML = svgSprite
110 | svg = div.getElementsByTagName('svg')[0]
111 | if (svg) {
112 | svg.setAttribute('aria-hidden', 'true')
113 | svg.style.position = 'absolute'
114 | svg.style.width = 0
115 | svg.style.height = 0
116 | svg.style.overflow = 'hidden'
117 | prepend(svg,document.body)
118 | }
119 | }
120 |
121 | if(shouldInjectCss && !window.__iconfont__svg__cssinject__){
122 | window.__iconfont__svg__cssinject__ = true
123 | try{
124 | document.write("");
125 | }catch(e){
126 | console && console.log(e)
127 | }
128 | }
129 |
130 | ready(appendSvg)
131 |
132 |
133 | })(window)
134 |
--------------------------------------------------------------------------------
/template/client/src/assets/fonts/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
50 |
--------------------------------------------------------------------------------
/template/client/src/assets/fonts/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erguotou520/vue-fullstack/3bcece5a3effc8ea96125e14c264771373a1d6f8/template/client/src/assets/fonts/iconfont.ttf
--------------------------------------------------------------------------------
/template/client/src/assets/fonts/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erguotou520/vue-fullstack/3bcece5a3effc8ea96125e14c264771373a1d6f8/template/client/src/assets/fonts/iconfont.woff
--------------------------------------------------------------------------------
/template/client/src/assets/images/login-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erguotou520/vue-fullstack/3bcece5a3effc8ea96125e14c264771373a1d6f8/template/client/src/assets/images/login-bg.jpg
--------------------------------------------------------------------------------
/template/client/src/components/ContentLoading.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
21 |
55 |
--------------------------------------------------------------------------------
/template/client/src/components/ContentModule.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
52 |
--------------------------------------------------------------------------------
/template/client/src/components/DataTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
85 |
129 |
--------------------------------------------------------------------------------
/template/client/src/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
67 |
68 |
69 |
174 |
202 |
--------------------------------------------------------------------------------
/template/client/src/components/NProgress.vue:
--------------------------------------------------------------------------------
1 |
2 |
31 |
36 |
--------------------------------------------------------------------------------
/template/client/src/components/NavMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
29 |
55 |
--------------------------------------------------------------------------------
/template/client/src/components/Pagination.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
55 |
88 |
--------------------------------------------------------------------------------
/template/client/src/constants.js:
--------------------------------------------------------------------------------
1 | // user info
2 | export const STORE_KEY_USERNAME = 'user.username'
3 | export const STORE_KEY_ACCESS_TOKEN = 'user.access_token'
4 | export const STORE_KEY_REFRESH_TOKEN = 'user.refresh_token'
5 |
6 | // user config
7 | {{#if i18n}}export const STORE_KEY_CONFIG_LANG = 'config.lang'
8 | {{/if}}export const STORE_KEY_CONFIG_PAGE_LIMIT = 'config.page.limit'
9 |
--------------------------------------------------------------------------------
/template/client/src/element-ui.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import { Dialog, Autocomplete, Dropdown, DropdownMenu, DropdownItem, Input, InputNumber,
3 | Radio, RadioGroup, RadioButton, Checkbox, CheckboxGroup, Switch, Select, Option,
4 | OptionGroup, Button, ButtonGroup, Table, TableColumn, DatePicker, TimeSelect, TimePicker,
5 | Popover, Tooltip, MessageBox, Breadcrumb, BreadcrumbItem, Form, FormItem, Tabs, TabPane,
6 | Tag, Tree, Notification, Slider, Loading, Row, Col, Upload, Progress, Spinner,
7 | Message, Badge, Card, Steps, Step } from 'element-ui'
8 |
9 | Vue.component(Dialog.name, Dialog)
10 | Vue.component(Autocomplete.name, Autocomplete)
11 | Vue.component(Dropdown.name, Dropdown)
12 | Vue.component(DropdownMenu.name, DropdownMenu)
13 | Vue.component(DropdownItem.name, DropdownItem)
14 | Vue.component(Input.name, Input)
15 | Vue.component(InputNumber.name, InputNumber)
16 | Vue.component(Radio.name, Radio)
17 | Vue.component(RadioGroup.name, RadioGroup)
18 | Vue.component(RadioButton.name, RadioButton)
19 | Vue.component(Checkbox.name, Checkbox)
20 | Vue.component(CheckboxGroup.name, CheckboxGroup)
21 | Vue.component(Switch.name, Switch)
22 | Vue.component(Select.name, Select)
23 | Vue.component(Option.name, Option)
24 | Vue.component(OptionGroup.name, OptionGroup)
25 | Vue.component(Button.name, Button)
26 | Vue.component(ButtonGroup.name, ButtonGroup)
27 | Vue.component(Table.name, Table)
28 | Vue.component(TableColumn.name, TableColumn)
29 | Vue.component(DatePicker.name, DatePicker)
30 | Vue.component(TimeSelect.name, TimeSelect)
31 | Vue.component(TimePicker.name, TimePicker)
32 | Vue.component(Popover.name, Popover)
33 | Vue.component(Tooltip.name, Tooltip)
34 | Vue.component(Breadcrumb.name, Breadcrumb)
35 | Vue.component(BreadcrumbItem.name, BreadcrumbItem)
36 | Vue.component(Form.name, Form)
37 | Vue.component(FormItem.name, FormItem)
38 | Vue.component(Tabs.name, Tabs)
39 | Vue.component(TabPane.name, TabPane)
40 | Vue.component(Tag.name, Tag)
41 | Vue.component(Tree.name, Tree)
42 | Vue.component(Slider.name, Slider)
43 | Vue.component(Row.name, Row)
44 | Vue.component(Col.name, Col)
45 | Vue.component(Upload.name, Upload)
46 | Vue.component(Progress.name, Progress)
47 | Vue.component(Spinner.name, Spinner)
48 | Vue.component(Badge.name, Badge)
49 | Vue.component(Card.name, Card)
50 | Vue.component(Steps.name, Steps)
51 | Vue.component(Step.name, Step)
52 |
53 | Vue.use(Loading.directive)
54 | try {
55 | var a = MessageBox
56 | var b = Message
57 | var c = Notification
58 | Vue.prototype.$loading = Loading.service
59 | Vue.prototype.$msgbox = a
60 | Vue.prototype.$alert = a.alert
61 | Vue.prototype.$confirm = a.confirm
62 | Vue.prototype.$prompt = a.prompt
63 | Vue.prototype.$notify = c
64 | Vue.prototype.$message = b
65 | } catch (e) {
66 | console.log(e)
67 | } finally {
68 | //
69 | }
70 |
--------------------------------------------------------------------------------
/template/client/src/http/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import store from '../store'
3 | import router from '../router'
4 | import VueResource from 'vue-resource'
5 | import { Message } from 'element-ui'
6 | Vue.use(VueResource)
7 | // Vue.http.options.emulateJSON = true
8 | Vue.http.options.root = '/api'
9 |
10 | const requestMap = {}
11 |
12 | // response interceptor
13 | Vue.http.interceptors.push((request, next) => {
14 | let key
15 | // abort the same post request
16 | if (/POST|PUT|DELETE/.test(request.method)) {
17 | key = `${request.method}${request.url}${JSON.stringify(request.body)}`
18 | // abort the existed request
19 | if (key && requestMap[key]) {
20 | key = null
21 | setTimeout(() => {
22 | request.abort()
23 | })
24 | } else {
25 | requestMap[key] = request
26 | }
27 | }
28 |
29 | if (store.getters.loggedIn) {
30 | // if logged in, add the token to the header
31 | request.headers.set('Authorization', `Bearer ${store.getters.accessToken}`)
32 | }
33 | next((response) => {
34 | // delete current request in the map
35 | if (key) {
36 | delete requestMap[key]
37 | }
38 | // don't handler for login page
39 | if (store.state.route.path === '/login') {
40 | return
41 | }
42 | if (response.status === 401) {
43 | Message.error(Vue.t('http.error.E401'))
44 | store.dispatch('logout').then(() => {
45 | const location = window.location
46 | const path = store.state.route.fullpath || (location.pathname + location.search)
47 | router.push({ path: '/login', query: { redirect: path }})
48 | })
49 | return
50 | }
51 | if (response.status === 403) {
52 | Message.error(Vue.t('http.error.E403'))
53 | return
54 | }
55 | if (response.status === 404) {
56 | Message.error(Vue.t('http.error.E404'))
57 | return
58 | }
59 | if (response.status === 500) {
60 | Message.error(Vue.t('http.error.E500'))
61 | return
62 | }
63 | // other errors
64 | if (!response.ok) {
65 | Message.error({
66 | message: response.data || Vue.t('http.error.others')
67 | })
68 | }
69 | })
70 | })
71 |
--------------------------------------------------------------------------------
/template/client/src/locales/en.js:
--------------------------------------------------------------------------------
1 | export default {
2 | title: 'XXX Backend',
3 | constant: {
4 | name: 'Name',
5 | desc: 'Description'
6 | },
7 | confirm: {
8 | title: 'Warning',
9 | ok: 'save',
10 | cancel: 'cancel',
11 | prevStep: 'Previous',
12 | nextStep: 'Next',
13 | remove: 'This will remove the selected {content} forever, continue?',
14 | confirmSelected: 'You have selected the following items. Please confirm your choices as this action can\'t be recoveried'
15 | },
16 | label: {
17 | name: 'Name',
18 | enable: 'Enable'
19 | },
20 | status: {
21 | enabled: 'Enabled',
22 | disabled: 'Disabled'
23 | },
24 | operation: {
25 | add: 'Add',
26 | create: 'Create',
27 | edit: 'Edit',
28 | update: 'Update',
29 | remove: 'Remove',
30 | multiRemove: 'Multi remove',
31 | operation: 'Operation',
32 | search: 'Search',
33 | enable: 'Click to enable',
34 | disable: 'Click to disable'
35 | },
36 | message: {
37 | save: {
38 | ok: 'Saved!',
39 | err: 'Error occured when saving!'
40 | },
41 | error: 'Error',
42 | created: 'Create successed',
43 | createFailed: 'Create failed',
44 | updated: 'Update successed',
45 | updateFailed: 'Update failed',
46 | removed: 'Delete successed',
47 | removeFailed: 'Delete failed'
48 | },
49 | http: {
50 | error: {
51 | E401: 'Not authorized',
52 | E403: 'Permission not allowed',
53 | E404: 'Url not found',
54 | E500: 'Server error',
55 | others: 'Some error occured, please try again'
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/template/client/src/locales/header/en.js:
--------------------------------------------------------------------------------
1 | export default {
2 | header: {
3 | settings: 'User settings',
4 | password: 'Password',
5 | logout: 'Logout',
6 | langSetting: 'Lang',
7 | pageLimit: 'Data count limit per page',
8 | _password: {
9 | description: 'Change your password. It\'s strongly recommend that you should pick a complex password.',
10 | old: 'Old password',
11 | _new: 'New password',
12 | newConfirm: 'Confirm new password',
13 | rules: {
14 | old: 'Please input old password',
15 | _new: 'Please input new password',
16 | newConfirm: 'Please input new password again',
17 | notMatch: 'The two new password not matched'
18 | },
19 | afterChange: 'Password has changed, system will logout automaticaly, please re-login with the new password.'
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/template/client/src/locales/header/index.js:
--------------------------------------------------------------------------------
1 | import en from './en'
2 | import zhCN from './zh-CN'
3 |
4 | export default {
5 | 'zh-CN': zhCN,
6 | en
7 | }
8 |
--------------------------------------------------------------------------------
/template/client/src/locales/header/zh-CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | header: {
3 | settings: '用户设置',
4 | password: '修改密码',
5 | logout: '退出',
6 | langSetting: '语言',
7 | pageLimit: '每页条目数',
8 | _password: {
9 | description: '修改你的密码。强烈建议您选择一个复杂密码。',
10 | old: '旧密码',
11 | _new: '新密码',
12 | newConfirm: '确认新密码',
13 | rules: {
14 | old: '请输入旧密码',
15 | _new: '请输入新密码',
16 | newConfirm: '请再次确认新密码',
17 | notMatch: '两次输入的新密码不一致'
18 | },
19 | afterChange: '密码已修改,将自动退出,请使用新密码重新登录。'
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/template/client/src/locales/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueI18n from 'vue-i18n'
3 | import { merge } from 'lodash'
4 | import { lang } from '../stored'
5 |
6 | import zhCN from './zh-CN'
7 | import en from './en'
8 | import eleZhCN from 'element-ui/lib/locale/lang/zh-CN'
9 | import eleEn from 'element-ui/lib/locale/lang/en'
10 |
11 | const locales = {
12 | 'zh-CN': merge(zhCN, eleZhCN),
13 | en: merge(en, eleEn)
14 | }
15 |
16 | Vue.use(VueI18n)
17 |
18 | Vue.config.lang = lang
19 | Vue.config.fallbackLang = 'zh-CN'
20 |
21 | // set locales
22 | Object.keys(locales).forEach(lang => {
23 | Vue.locale(lang, locales[lang])
24 | })
25 |
--------------------------------------------------------------------------------
/template/client/src/locales/login/en.js:
--------------------------------------------------------------------------------
1 | export default {
2 | login: {
3 | username: 'Please input the username',
4 | password: 'Please input the password',
5 | button: 'Log in',
6 | authFail: 'Username or password is not correct'
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/template/client/src/locales/login/index.js:
--------------------------------------------------------------------------------
1 | import en from './en'
2 | import zhCN from './zh-CN'
3 |
4 | export default {
5 | 'zh-CN': zhCN,
6 | en
7 | }
8 |
--------------------------------------------------------------------------------
/template/client/src/locales/login/zh-CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | login: {
3 | username: '请输入用户名',
4 | password: '请输入密码',
5 | button: '登录',
6 | authFail: '用户名或密码错误'
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/template/client/src/locales/menu/en.js:
--------------------------------------------------------------------------------
1 | export default {
2 | menu: {
3 | users: 'Users',
4 | things: 'Things'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/template/client/src/locales/menu/index.js:
--------------------------------------------------------------------------------
1 | import en from './en'
2 | import zhCN from './zh-CN'
3 |
4 | export default {
5 | 'zh-CN': zhCN,
6 | en
7 | }
8 |
--------------------------------------------------------------------------------
/template/client/src/locales/menu/zh-CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | menu: {
3 | users: '用户列表',
4 | things: '事件列表'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/template/client/src/locales/pagination/en.js:
--------------------------------------------------------------------------------
1 | export default {
2 | pagination: {
3 | current: 'current',
4 | currentAppend: 'page',
5 | pages: 'total',
6 | pagesAppend: 'page'
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/template/client/src/locales/pagination/index.js:
--------------------------------------------------------------------------------
1 | import en from './en'
2 | import zhCN from './zh-CN'
3 |
4 | export default {
5 | 'zh-CN': zhCN,
6 | en
7 | }
8 |
--------------------------------------------------------------------------------
/template/client/src/locales/pagination/zh-CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | pagination: {
3 | current: '当前第',
4 | currentAppend: '页',
5 | pages: '共',
6 | pagesAppend: '页'
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/template/client/src/locales/things/en.js:
--------------------------------------------------------------------------------
1 | export default {
2 | thing: {
3 | breadcrumb: {
4 | home: 'Home',
5 | current: 'Things'
6 | },
7 | model: {
8 | name: 'name',
9 | description: 'description'
10 | },
11 | rules: {
12 | name: 'Please input the name'
13 | },
14 | edit: {
15 | create: 'Add thing',
16 | update: 'Update thing'
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/template/client/src/locales/things/index.js:
--------------------------------------------------------------------------------
1 | import en from './en'
2 | import zhCN from './zh-CN'
3 |
4 | export default {
5 | 'zh-CN': zhCN,
6 | en
7 | }
8 |
--------------------------------------------------------------------------------
/template/client/src/locales/things/zh-CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | thing: {
3 | breadcrumb: {
4 | home: '首页',
5 | current: '事件管理'
6 | },
7 | model: {
8 | name: '名字',
9 | description: '描述'
10 | },
11 | rules: {
12 | name: '请输入名称'
13 | },
14 | edit: {
15 | create: '新增事件',
16 | update: '编辑事件'
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/template/client/src/locales/users/en.js:
--------------------------------------------------------------------------------
1 | export default {
2 | user: {
3 | breadcrumb: {
4 | home: 'Home',
5 | current: 'Users'
6 | },
7 | model: {
8 | username: 'Username',
9 | role: 'Role',
10 | password: 'Password'
11 | },
12 | create: {
13 | title: 'Create a user'
14 | },
15 | rules: {
16 | username: 'Please input the username',
17 | password: 'Please input the password'
18 | },
19 | action: {
20 | userExisted: 'User existed'
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/template/client/src/locales/users/index.js:
--------------------------------------------------------------------------------
1 | import en from './en'
2 | import zhCN from './zh-CN'
3 |
4 | export default {
5 | 'zh-CN': zhCN,
6 | en
7 | }
8 |
--------------------------------------------------------------------------------
/template/client/src/locales/users/zh-CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | user: {
3 | breadcrumb: {
4 | home: '首页',
5 | current: '用户管理'
6 | },
7 | model: {
8 | username: '用户名',
9 | role: '角色',
10 | password: '密码'
11 | },
12 | create: {
13 | title: '创建用户'
14 | },
15 | rules: {
16 | username: '请输入用户名',
17 | password: '请输入密码'
18 | },
19 | action: {
20 | userExisted: '用户已存在'
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/template/client/src/locales/zh-CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | title: 'XXX管理系统',
3 | constant: {
4 | name: '名称',
5 | desc: '描述'
6 | },
7 | confirm: {
8 | title: '提示',
9 | ok: '确 定',
10 | cancel: '取 消',
11 | prevStep: '上一步',
12 | nextStep: '下一步',
13 | remove: '此操作将永久删除所选{content},是否继续?',
14 | confirmSelected: '您已经选择了下列选项。请确认您的选择。这个动作不能撤消!'
15 | },
16 | label: {
17 | name: '名称',
18 | enable: '是否已启用'
19 | },
20 | status: {
21 | enabled: '已启用',
22 | disabled: '已禁用'
23 | },
24 | form: {
25 | enable: '是否启用',
26 | enabled: '启用'
27 | },
28 | operation: {
29 | add: '添加',
30 | create: '创建',
31 | edit: '编辑',
32 | update: '更新',
33 | remove: '删除',
34 | multiRemove: '批量删除',
35 | operation: '操作',
36 | search: '搜索',
37 | enable: '点击启用',
38 | disable: '点击禁用'
39 | },
40 | message: {
41 | save: {
42 | ok: '已保存!',
43 | err: '保存失败!'
44 | },
45 | error: '错误',
46 | created: '新增成功',
47 | createFailed: '新增失败',
48 | updated: '已保存更改',
49 | updateFailed: '更新失败',
50 | removed: '删除成功',
51 | removeFailed: '删除失败'
52 | },
53 | http: {
54 | error: {
55 | E401: '身份认证失败',
56 | E403: '权限不足',
57 | E404: '请求路径错误',
58 | E500: '后台错误',
59 | others: '操作失败,请重试'
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/template/client/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | // read localStorage stored data
3 | import './stored'
4 | {{#if i18n}}
5 | // locale
6 | import './locales'
7 | {{/if}}
8 |
9 | // router and store
10 | import store from './store'
11 | import router, { hook as routerHook } from './router'
12 | import { sync } from 'vuex-router-sync'
13 | sync(store, router)
14 |
15 | // ui library
16 | import './element-ui'
17 |
18 | // ajax
19 | import './http'
20 |
21 | const userPromise = store.dispatch('initUserInfo')
22 | routerHook(userPromise)
23 |
24 | // main component
25 | import App from './App'
26 |
27 | import './socket'
28 |
29 | userPromise.then(() => {
30 | const app = new Vue({
31 | router,
32 | store,
33 | ...App // Object spread copying everything from App.vue
34 | })
35 | // actually mount to DOM
36 | app.$mount('app')
37 | })
38 |
--------------------------------------------------------------------------------
/template/client/src/resources.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | // things resource
3 | export const thing = Vue.resource('things{/_id}')
4 | // users resource
5 | export const user = Vue.resource('users{/_id}', {}, {
6 | changePassword: { method: 'put', url: 'users{/id}/password' }
7 | })
8 |
--------------------------------------------------------------------------------
/template/client/src/router/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * App router config
3 | */
4 | import Vue from 'vue'
5 | import VueRouter from 'vue-router'
6 | import store from '../store'
7 | import otherModuleRoutes from './module'
8 | Vue.use(VueRouter)
9 |
10 | const routes = [{
11 | path: '/login',
12 | component: (resolve) => {
13 | import('../view/auth/Login.vue').then(resolve)
14 | },
15 | meta: {
16 | skipAuth: true
17 | }
18 | }, {
19 | path: '/',
20 | component: (resolve) => {
21 | import('../view/CommonView.vue').then(resolve)
22 | },
23 | children: [...otherModuleRoutes, {
24 | path: '/', redirect: '/dashboard'
25 | }]
26 | }, {
27 | path: '*',
28 | component: {
29 | render (h) {
30 | return h('div', { staticClass: 'flex flex-main-center', attrs: { style: 'width:100%;font-size:32px' }}, 'Page not found')
31 | }
32 | }
33 | }]
34 |
35 | const router = new VueRouter({
36 | mode: 'history',
37 | linkActiveClass: 'active',
38 | scrollBehavior: () => ({ y: 0 }),
39 | routes
40 | })
41 |
42 | export function hook (userPromise) {
43 | // router
44 | router.beforeEach((to, from, next) => {
45 | // 确保用户身份信息已获取
46 | userPromise.then(() => {
47 | store.dispatch('changeRouteLoading', true).then(() => {
48 | // has logged in, reject
49 | if (to.path === '/login' && store.getters.loggedIn) {
50 | return next(false)
51 | }
52 | if (!to.meta.skipAuth) {
53 | // this route requires auth, check if logged in
54 | // if not, redirect to login page.
55 | if (!store.getters.loggedIn) {
56 | next({
57 | path: '/login',
58 | query: { redirect: to.fullPath }
59 | })
60 | } else {
61 | next()
62 | }
63 | } else {
64 | next()
65 | }
66 | })
67 | })
68 | })
69 |
70 | router.afterEach(() => {
71 | store.dispatch('changeRouteLoading', false)
72 | })
73 | }
74 |
75 | export default router
76 |
--------------------------------------------------------------------------------
/template/client/src/router/module.js:
--------------------------------------------------------------------------------
1 | export default [{
2 | path: '/dashboard',
3 | component: (resolve) => {
4 | import('../view/Dashboard.vue').then(resolve)
5 | }
6 | }, {
7 | path: '/users',
8 | component: (resolve) => {
9 | import('../view/UserList.vue').then(resolve)
10 | }
11 | }, {
12 | path: '/things',
13 | component: (resolve) => {
14 | import('../view/ThingList.vue').then(resolve)
15 | }
16 | }]
17 |
--------------------------------------------------------------------------------
/template/client/src/shared/search.js:
--------------------------------------------------------------------------------
1 | // 从数组中筛选含搜索字段的新数组
2 | export default function (array, searchKey) {
3 | return array.filter(obj => {
4 | return Object.keys(obj).some(key => {
5 | return String(obj[key]).toLowerCase().indexOf(searchKey.toLowerCase()) > -1
6 | })
7 | })
8 | }
9 |
--------------------------------------------------------------------------------
/template/client/src/shared/validators.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | export const EMAIL_REGEX = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9_-]+?\.[a-zA-Z]{2,5}$/
3 |
4 | function commonValidator (regex, defaultMessage, rule, value, callback) {
5 | if (!value || regex.test(value)) {
6 | callback()
7 | } else {
8 | callback(new Error(rule.message || defaultMessage))
9 | }
10 | }
11 |
12 | /**
13 | * 邮箱验证
14 | */
15 | export function email (rule, value, callback) {
16 | commonValidator(EMAIL_REGEX, Vue.t('validation.email'), rule, value, callback)
17 | }
18 |
--------------------------------------------------------------------------------
/template/client/src/socket/index.js:
--------------------------------------------------------------------------------
1 | import IO from 'socket.io-client'
2 | import Vue from 'vue'
3 |
4 | const socket = IO.connect()
5 | Vue.prototype.$socket = socket
6 |
7 | export default socket
8 | export function authSocket (token, cb) {
9 | socket
10 | .on('authenticated', () => {
11 | cb()
12 | })
13 | .emit('authenticate', { token: token })
14 | }
15 |
--------------------------------------------------------------------------------
/template/client/src/storage/index.js:
--------------------------------------------------------------------------------
1 | const storage = window.localStorage
2 |
3 | export function save (key, value) {
4 | storage.setItem(key, value)
5 | }
6 |
7 | export function saveMulti (datas) {
8 | datas.forEach(data => save(data.key, data.value))
9 | }
10 |
11 | export function read (key) {
12 | return storage.getItem(key)
13 | }
14 |
15 | export function readMulti (keys) {
16 | return keys.map(key => read(key))
17 | }
18 |
19 | export function clear (key, clearAll = false) {
20 | if (clearAll) {
21 | storage.clear()
22 | } else {
23 | storage.removeItem(key)
24 | }
25 | }
26 |
27 | export function clearMulti (keys) {
28 | keys.forEach(key => clear(key))
29 | }
30 |
--------------------------------------------------------------------------------
/template/client/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import routeLoading from './modules/route'
4 | import config from './modules/global-config'
5 | import user from './modules/user'
6 | Vue.use(Vuex)
7 |
8 | const store = new Vuex.Store({
9 | strict: process.env.NODE_ENV !== 'production',
10 | modules: {
11 | user,
12 | config,
13 | routeLoading
14 | }
15 | })
16 |
17 | export default store
18 |
--------------------------------------------------------------------------------
/template/client/src/store/modules/global-config.js:
--------------------------------------------------------------------------------
1 | {{#if i18n}}import Vue from 'vue'{{/if}}
2 | import { {{#if i18n}}lang, {{/if}}pageLimit } from '../../stored'
3 | import { save } from '../../storage'
4 | import { {{#if i18n}}STORE_KEY_CONFIG_LANG, {{/if}}STORE_KEY_CONFIG_PAGE_LIMIT } from '../../constants'
5 |
6 | const state = {
7 | {{#if i18n}}lang: lang,
8 | // value see http://stackoverflow.com/questions/5580876/navigator-language-list-of-all-languages
9 | langs: [{
10 | label: '中文',
11 | value: 'zh-CN'
12 | }, {
13 | label: 'English',
14 | value: 'en'
15 | }],
16 | {{/if}}pageLimit: pageLimit
17 | }
18 |
19 | const mutations = {
20 | UPDATE (state, config) {
21 | {{#if i18n}}state.lang = config.lang || state.lang
22 | {{/if}}state.pageLimit = config.pageLimit || state.pageLimit
23 | }{{#if i18n}},
24 | UPDATE_LANG (state, lang) {
25 | state.lang = lang || state.lang
26 | }{{/if}}
27 | }
28 |
29 | const actions = {
30 | {{#if i18n}}changeLang ({ commit }, lang) {
31 | Vue.config.lang = lang
32 | commit('UPDATE_LANG', lang)
33 | save(STORE_KEY_CONFIG_LANG, lang)
34 | },
35 | {{/if}}updateGlobalConfig ({ commit, state, dispatch }, config) {
36 | {{#if i18n}}if (config.lang !== state.lang) {
37 | Vue.config.lang = config.lang
38 | save(STORE_KEY_CONFIG_LANG, config.lang)
39 | }
40 | {{/if}}commit('UPDATE', config)
41 | {{#if i18n}}save(STORE_KEY_CONFIG_LANG, state.lang)
42 | {{/if}}save(STORE_KEY_CONFIG_PAGE_LIMIT, state.pageLimit)
43 | }
44 | }
45 |
46 | const getters = {
47 | globalConfig (state) {
48 | return state
49 | }
50 | }
51 |
52 | export default {
53 | state,
54 | mutations,
55 | actions,
56 | getters
57 | }
58 |
--------------------------------------------------------------------------------
/template/client/src/store/modules/route.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | loading: false
3 | }
4 |
5 | const mutations = {
6 | CHANGE (state, status) {
7 | state.loading = !!status
8 | }
9 | }
10 |
11 | const actions = {
12 | changeRouteLoading ({ commit }, status) {
13 | commit('CHANGE', status)
14 | }
15 | }
16 |
17 | const getters = {
18 | routeLoadingStatus (state) {
19 | return state.loading
20 | }
21 | }
22 |
23 | export default {
24 | state,
25 | mutations,
26 | actions,
27 | getters
28 | }
29 |
--------------------------------------------------------------------------------
/template/client/src/store/modules/user.api.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import { authSocket } from '../../socket'
3 |
4 | export function login (username, password) {
5 | return Vue.http.post('auth/local', {
6 | username,
7 | password
8 | }).then(res => res.json())
9 | }
10 |
11 | export function getUserInfo (token) {
12 | return new Promise((resolve) => {
13 | Vue.http.get('users/me', {
14 | headers: {
15 | 'Authorization': `Bearer ${token}`
16 | }
17 | }).then(data => data.json()).then(data => {
18 | authSocket(token, () => {
19 | console.log('Token authenticated.')
20 | })
21 | resolve(data)
22 | }).catch(() => {
23 | resolve({})
24 | })
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/template/client/src/store/modules/user.js:
--------------------------------------------------------------------------------
1 | import { merge } from 'lodash'
2 | import { saveMulti, clearMulti } from '../../storage'
3 | import { login, getUserInfo } from './user.api'
4 | // eslint-disable-next-line camelcase
5 | import { username, access_token, refresh_token } from '../../stored'
6 | import { STORE_KEY_USERNAME, STORE_KEY_ACCESS_TOKEN, STORE_KEY_REFRESH_TOKEN } from '../../constants'
7 |
8 | const state = {
9 | _id: '',
10 | role: 'guest',
11 | username: username,
12 | access_token, // eslint-disable-line
13 | refresh_token // eslint-disable-line
14 | }
15 |
16 | const mutations = {
17 | // set user info
18 | SET_USER_INFO (state, userInfo) {
19 | merge(state, userInfo)
20 | },
21 | // after logout
22 | LOGOUT (state) {
23 | state._id = ''
24 | state.username = ''
25 | state.role = 'guest'
26 | state.access_token = '' // eslint-disable-line
27 | state.refresh_token = '' // eslint-disable-line
28 | }
29 | }
30 |
31 | const actions = {
32 | // init user info
33 | initUserInfo ({ commit, dispatch, state }) {
34 | return new Promise((resolve, reject) => {
35 | // token
36 | if (username) {
37 | getUserInfo(state.access_token).then(data => { // eslint-disable-line
38 | if (data._id) {
39 | commit('SET_USER_INFO', data)
40 | }
41 | resolve(data)
42 | }).catch(err => { reject(err) })
43 | } else {
44 | resolve()
45 | }
46 | })
47 | },
48 | // login action
49 | login ({ commit, dispatch }, payload) {
50 | return new Promise((resolve, reject) => {
51 | login(payload.username, payload.password).then(data => {
52 | getUserInfo(data.token).then(user => {
53 | const userInfo = merge({}, user, {
54 | username: payload.username,
55 | access_token: data.token, // eslint-disable-line
56 | refresh_token: '' // eslint-disable-line
57 | })
58 | commit('SET_USER_INFO', userInfo)
59 | saveMulti([{
60 | key: STORE_KEY_USERNAME,
61 | value: userInfo.username
62 | }, {
63 | key: STORE_KEY_ACCESS_TOKEN,
64 | value: userInfo.access_token // eslint-disable-line
65 | }, {
66 | key: STORE_KEY_REFRESH_TOKEN,
67 | value: userInfo.refresh_token // eslint-disable-line
68 | }])
69 | resolve()
70 | }).catch(() => {})
71 | }).catch(err => { reject(err) })
72 | })
73 | },
74 | // refresh token action
75 | refreToken ({ commit }, payload) {
76 | commit('REFERE_TOKEN', payload)
77 | saveMulti([{
78 | key: STORE_KEY_ACCESS_TOKEN,
79 | value: payload.access_token // eslint-disable-line
80 | }, {
81 | key: STORE_KEY_REFRESH_TOKEN,
82 | value: payload.refresh_token // eslint-disable-line
83 | }])
84 | },
85 | // logout action
86 | logout ({ commit }, payload) {
87 | commit('LOGOUT')
88 | clearMulti([STORE_KEY_USERNAME, STORE_KEY_ACCESS_TOKEN, STORE_KEY_REFRESH_TOKEN])
89 | }
90 | }
91 |
92 | const getters = {
93 | userId (state) {
94 | return state._id
95 | },
96 | userRole (state) {
97 | return state.role
98 | },
99 | accessToken (state) {
100 | return state.access_token // eslint-disable-line
101 | },
102 | username (state) {
103 | return state.username
104 | },
105 | loggedIn (state) {
106 | return !!(state.username && state.access_token) // eslint-disable-line
107 | }
108 | }
109 |
110 | export default {
111 | state,
112 | mutations,
113 | actions,
114 | getters
115 | }
116 |
--------------------------------------------------------------------------------
/template/client/src/stored.js:
--------------------------------------------------------------------------------
1 | import { read } from './storage'
2 | import { STORE_KEY_USERNAME, STORE_KEY_ACCESS_TOKEN, STORE_KEY_REFRESH_TOKEN,
3 | STORE_KEY_CONFIG_LANG, STORE_KEY_CONFIG_PAGE_LIMIT } from './constants'
4 |
5 | export const username = read(STORE_KEY_USERNAME) || ''
6 | // eslint-disable-next-line camelcase
7 | export const access_token = read(STORE_KEY_ACCESS_TOKEN) || '' // eslint-disable-line
8 | // eslint-disable-next-line camelcase
9 | export const refresh_token = read(STORE_KEY_REFRESH_TOKEN) || '' // eslint-disable-line
10 | // lang order: localStorage -> browser language -> default
11 | export const lang = read(STORE_KEY_CONFIG_LANG) || navigator.language || 'zh-CN'
12 | export const pageLimit = +read(STORE_KEY_CONFIG_PAGE_LIMIT) || 20
13 |
--------------------------------------------------------------------------------
/template/client/src/view/CommonView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
20 |
--------------------------------------------------------------------------------
/template/client/src/view/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dashboard
4 | Send hello
5 |
6 |
7 |
21 |
--------------------------------------------------------------------------------
/template/client/src/view/ThingList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{$t('thing.breadcrumb.home')}}
5 | {{$t('thing.breadcrumb.current')}}
6 |
7 |
8 | {{$t('operation.create')}}
9 |
10 |
11 |
12 |
13 | {{thing.name}}
14 |
15 |
16 |
17 |
18 | {{thing.info}}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
120 |
135 |
--------------------------------------------------------------------------------
/template/client/src/view/UserList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{$t('user.breadcrumb.home')}}
5 | {{$t('user.breadcrumb.current')}}
6 |
7 |
8 |
9 | {{$t('operation.create')}}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{$t('operation.remove')}}
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
124 |
--------------------------------------------------------------------------------
/template/client/src/view/auth/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{$t('title')}}
5 |
7 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {{$t('login.button')}}
22 |
23 |
24 |
30 |
31 |
32 |
89 |
142 |
--------------------------------------------------------------------------------
/template/client/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erguotou520/vue-fullstack/3bcece5a3effc8ea96125e14c264771373a1d6f8/template/client/static/favicon.ico
--------------------------------------------------------------------------------
/template/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Project config file includes dev/prod and frontend/backend
3 | */
4 | var path = require('path')
5 | var _ = require('lodash')
6 | {{#if_eq mock 'backend'}}
7 | var backendBase = {
8 | // Root path of server
9 | root: path.normalize(__dirname),
10 |
11 | // Server port
12 | port: process.env.PORT || {{backendPort}},
13 |
14 | // Secret for session, you will want to change this and make it an environment variable
15 | secrets: {
16 | session: process.env.SECRET || '{{name}}-secret'
17 | },
18 |
19 | // List of user roles
20 | userRoles: ['admin', 'user'],
21 |
22 | // MongoDB connection options
23 | mongo: {
24 | options: {
25 | db: {
26 | safe: true
27 | }
28 | }
29 | }
30 | }{{/if_eq}}
31 |
32 | var development = {
33 | frontend: {
34 | port: {{frontendPort}},
35 | assetsRoot: path.resolve(__dirname, './{{#if_eq mock "backend"}}client/{{/if_eq}}src'),
36 | assetsSubDirectory: 'static',
37 | assetsPublicPath: '/',
38 | proxyTable: {
39 | '/api': { target: 'http://localhost:' + {{#if_eq mock 'backend'}}backendBase.port{{/if_eq}}{{#if_eq mock 'mock'}}{{mockPort}}{{/if_eq}}, changeOrigin: true{{#if_eq mock "mock"}}, pathRewrite: { '^/api/': '' }{{/if_eq}} },
40 | '/socket.io': { target: 'http://localhost:' + {{#if_eq mock 'backend'}}backendBase.port{{/if_eq}}{{#if_eq mock 'mock'}}{{mockPort}}{{/if_eq}}, changeOrigin: true, ws: true }
41 | },
42 | // CSS Sourcemaps off by default because relative paths are "buggy"
43 | // with this option, according to the CSS-Loader README
44 | // (https://github.com/webpack/css-loader#sourcemaps)
45 | // In our experience, they generally work as expected,
46 | // just be aware of this issue when enabling this option.
47 | cssSourceMap: false
48 | }{{#if_eq mock 'backend'}},
49 | backend: _.merge({}, backendBase, {
50 | mongo: {
51 | uri: {{#if mongoUri}}'{{mongoUri}}-dev'{{else}}'mongodb://localhost/{{name}}-dev'{{/if}}
52 | }
53 | }){{/if_eq}}{{#if_eq mock 'mock'}},
54 | mock: {
55 | port: {{mockPort}}
56 | }{{/if_eq}}
57 | }
58 | var production = {
59 | frontend: {
60 | index: path.resolve(__dirname, './{{#if_eq mock "backend"}}client/{{/if_eq}}dist/index.html'),
61 | assetsRoot: path.resolve(__dirname, './{{#if_eq mock "backend"}}client/{{/if_eq}}dist'),
62 | assetsSubDirectory: 'static',
63 | assetsPublicPath: '/',
64 | cssSourceMap: true,
65 | // Gzip off by default as many popular static hosts such as
66 | // Surge or Netlify already gzip all static assets for you.
67 | // Before setting to `true`, make sure to:
68 | // npm install --save-dev compression-webpack-plugin
69 | productionGzip: false,
70 | productionGzipExtensions: ['js', 'css']
71 | }{{#if_eq mock 'backend'}},
72 | backend: _.merge({}, backendBase, {
73 | // whether backend servers the frontend, you can use nginx to server frontend and proxy to backend services
74 | // if set to true, you need no web services like nginx
75 | serverFrontend: true,
76 | // Server IP
77 | ip: process.env.APP_HOST || process.env.APP_IP || process.env.HOST || process.env.IP,
78 | // Server port
79 | port: process.env.APP_PORT || process.env.PORT,
80 | // MongoDB connection options
81 | mongo: {
82 | uri: process.env.MONGODB_URI || process.env.MONGOHQ_URI ||
83 | {{#if mongoUri}}'{{mongoUri}}'{{else}}'mongodb://localhost/{{name}}'{{/if}}
84 | },
85 |
86 | // frontend folder
87 | frontend: path.resolve(__dirname, './client/dist')
88 | }){{/if_eq}}{{#if_eq mock 'mock'}},
89 | mock: {
90 | port: process.env.APP_PORT || process.env.PORT
91 | }{{/if_eq}}
92 | }
93 |
94 | var config = process.env.NODE_ENV === 'production' ? production : development
95 |
96 | module.exports = _.assign({}, config)
97 |
--------------------------------------------------------------------------------
/template/mock/README.md:
--------------------------------------------------------------------------------
1 | # Mock server
2 | *This folder just show the way to integrate the mock server, you can change `fms` and `Mock.js` to any other lib that you are familiar with.*
3 |
4 | * It will include all the `.js` file, so you don't need to include them manually.
5 | * `ajax` folder for web http mock and `socket` folder for socket.io request mock.
6 | * You can follow the `ajax/things.js` and `ajax/users.js` to write your own mock server.
7 |
8 | ## Doc reference
9 | [F.M.S](http://www.fmsjs.org/)
10 | [Mock.js](https://github.com/nuysoft/Mock/wiki)
11 |
--------------------------------------------------------------------------------
/template/mock/ajax/things.js:
--------------------------------------------------------------------------------
1 | var fms = require('fms')
2 | var Mock = require('mockjs')
3 |
4 | fms.get({
5 | title: 'Get thing list',
6 | url: '/things',
7 | request: {
8 | page: {
9 | current: 2,
10 | limit: 15
11 | }
12 | },
13 | res: {
14 | ok: function () {
15 | return Mock.mock({
16 | page: {
17 | total: '@natural(5,20)'
18 | },
19 | 'results|5-8': [{
20 | _id: '@uuid',
21 | name: '@name',
22 | info: '@paragraph',
23 | active: '@boolean'
24 | }]
25 | })
26 | },
27 | err: {
28 | message: 'error when quering'
29 | }
30 | }
31 | })
32 |
33 | fms.get({
34 | title: 'Get a specified thing',
35 | url: '/things/:id',
36 | resStatus: {
37 | err: 404
38 | },
39 | res: {
40 | ok: function () {
41 | return Mock.mock({
42 | _id: '@uuid',
43 | name: '@name',
44 | info: '@paragraph',
45 | active: '@boolean'
46 | })
47 | }
48 | }
49 | })
50 |
51 | fms.post({
52 | title: 'Create a thing',
53 | url: '/things',
54 | request: {
55 | name: 'xxx',
56 | info: 'xxx'
57 | },
58 | res: {
59 | ok: function () {
60 | return Mock.mock({
61 | token: '@string(32)'
62 | })
63 | }
64 | }
65 | })
66 |
67 | fms.put({
68 | title: 'Update a thing info',
69 | url: '/things/:id',
70 | request: {
71 | name: 'xxx',
72 | info: 'xxx',
73 | active: 'true|false'
74 | },
75 | resStatus: {
76 | err: 403
77 | }
78 | })
79 |
80 | fms.ajax({
81 | title: 'Delete a thing',
82 | type: 'delete',
83 | url: '/things/:id',
84 | resStatus: {
85 | ok: 204,
86 | err: 403
87 | },
88 | res: {
89 | ok: {}
90 | }
91 | })
92 |
--------------------------------------------------------------------------------
/template/mock/ajax/users.js:
--------------------------------------------------------------------------------
1 | var fms = require('fms')
2 | var Mock = require('mockjs')
3 |
4 | fms.post({
5 | title: 'Login',
6 | url: '/auth/local',
7 | request: {
8 | username: 'xxx',
9 | password: 'xxx'
10 | },
11 | resStatus: {
12 | err: 401
13 | },
14 | res: {
15 | ok: function () {
16 | return Mock.mock({
17 | token: '@string(32)'
18 | })
19 | },
20 | err: {
21 | message: 'Username or password is not correct.'
22 | }
23 | }
24 | })
25 |
26 | fms.get({
27 | title: 'Get user list',
28 | url: '/users',
29 | request: {
30 | page: {
31 | current: 2,
32 | limit: 15
33 | }
34 | },
35 | res: {
36 | ok: function () {
37 | return Mock.mock({
38 | page: {
39 | total: '@natural(5,20)'
40 | },
41 | 'results|5-8': [{
42 | _id: '@uuid',
43 | name: '@name',
44 | username: '@name',
45 | role: '@pick(["user","admin"])',
46 | provider: 'local'
47 | }]
48 | })
49 | },
50 | err: {
51 | message: 'error when quering'
52 | }
53 | }
54 | })
55 |
56 | fms.get({
57 | title: 'Get a specified user',
58 | url: '/users/:id',
59 | resStatus: {
60 | err: 404
61 | },
62 | res: {
63 | ok: function () {
64 | return Mock.mock({
65 | _id: '@uuid',
66 | name: '@name',
67 | username: '@name',
68 | role: '@pick(["user","admin"])',
69 | provider: 'local'
70 | })
71 | }
72 | }
73 | })
74 |
75 | fms.get({
76 | title: 'Get user info',
77 | url: '/users/me',
78 | resStatus: {
79 | err: 401
80 | },
81 | res: {
82 | ok: function () {
83 | return Mock.mock({
84 | id: '@uuid'
85 | })
86 | }
87 | }
88 | })
89 |
90 | fms.post({
91 | title: 'Create a user',
92 | url: '/users',
93 | request: {
94 | name: 'xxx',
95 | username: 'xxx',
96 | password: 'xxx'
97 | },
98 | res: {
99 | ok: function () {
100 | return Mock.mock({
101 | token: '@string(32)'
102 | })
103 | }
104 | }
105 | })
106 |
107 | fms.put({
108 | title: 'Update user\'s password',
109 | url: '/users/:id/password',
110 | resStatus: {
111 | err: 403
112 | }
113 | })
114 |
115 | fms.ajax({
116 | title: 'Delete a user',
117 | type: 'delete',
118 | url: '/users/:id',
119 | resStatus: {
120 | ok: 204,
121 | err: 403
122 | },
123 | res: {
124 | ok: {}
125 | }
126 | })
127 |
--------------------------------------------------------------------------------
/template/mock/index.js:
--------------------------------------------------------------------------------
1 | var fms = require('fms')
2 | var glob = require('glob')
3 | var path = require('path')
4 |
5 | // api prefix: /api
6 | fms.run({
7 | port: require('../config').mock.port,
8 | root: process.cwd(),
9 | static: './dist',
10 | urlRewrite: [
11 | '/', '/index.html',
12 | /^\/api/, ''
13 | ]
14 | })
15 |
16 | // import all .js module
17 | glob.sync('ajax/**/*.js', {
18 | cwd: path.resolve(__dirname, './')
19 | }).forEach(function (file) {
20 | require(path.resolve(__dirname, file))
21 | })
22 |
23 | var socketio = require('socket.io')(fms.server)
24 | socketio.sockets.on('connection', function (socket) {
25 | socket.emit('authenticated')
26 | // Call onDisconnect.
27 | socket.on('disconnect', function () {
28 | console.info('DISCONNECTED')
29 | })
30 |
31 | // import all .js module
32 | glob.sync('socket/**/*.js', {
33 | cwd: path.resolve(__dirname, './')
34 | }).forEach(function (file) {
35 | require(path.resolve(__dirname, file))(socket)
36 | })
37 | console.info('CONNECTED')
38 | })
39 |
--------------------------------------------------------------------------------
/template/mock/socket/dashboard.js:
--------------------------------------------------------------------------------
1 | module.exports = function (socket) {
2 | socket.on('info', function (data) {
3 | console.info('[%s] %s', socket.address, JSON.stringify(data, null, 2))
4 | })
5 | socket.on('client:hello', function (data) {
6 | console.info('Received from client: %s', data)
7 | socket.emit('server:hello', 'server hello')
8 | })
9 | }
10 |
--------------------------------------------------------------------------------
/template/nginx.example.conf:
--------------------------------------------------------------------------------
1 | # enable gzip in nginx.conf
2 | # gzip on;
3 |
4 | # gzip_min_length 1k;
5 |
6 | # gzip_comp_level 4;
7 |
8 | # gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
9 |
10 | # gzip_vary on;
11 |
12 | # gzip_disable "MSIE [1-6]\.";
13 |
14 | server {
15 | listen 80;
16 | server_name fullstack.io;
17 |
18 | charset utf-8;
19 | root /your/path/to/{{#if_eq mock 'backend'}}client/{{/if_eq}}dist;
20 |
21 | access_log logs/{{name}}.access.log main;
22 | error_log logs/{{name}}.error.log debug;
23 |
24 | location /api {
25 | proxy_set_header X-Real-IP $remote_addr;
26 | proxy_set_header Host $http_host;
27 | # change this to your server host
28 | proxy_pass http://127.0.0.1:{{#if_eq mock 'backend'}}{{backendPort}}{{/if_eq}}{{#if_eq mock 'mock'}}port{{/if_eq}};
29 | }
30 |
31 | location ~* ^.+\.(ico|gif|jpg|jpeg|png)$ {
32 | access_log off;
33 | expires 30d;
34 | }
35 |
36 | location ~* ^.+\.(css|js|txt|xml|swf|wav)$ {
37 | access_log off;
38 | expires 24h;
39 | }
40 |
41 | location ~* ^.+\.(html|htm)$ {
42 | expires 1h;
43 | }
44 |
45 | location ~* ^.+\.(eot|ttf|otf|woff|svg)$ {
46 | access_log off;
47 | expires max;
48 | }
49 |
50 | location / {
51 | try_files $uri /index.html;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "{{name}}",
3 | "version": "0.0.1",
4 | "keywords": [
5 | "vue",
6 | "vuejs",
7 | "vuex",
8 | "vue-router",
9 | "vue-i18n",
10 | "vue-resource",
11 | "fullstack",{{#if_eq mock 'backend'}}
12 | "mongodb",
13 | "mongoose",{{/if_eq}}
14 | "socket.io",
15 | "element",
16 | "element-ui"
17 | ],
18 | "description": "{{description}}",
19 | "author": "{{author}}",
20 | "private": false,
21 | "scripts": {
22 | "client": "node {{#if_eq mock 'backend'}}client/{{/if_eq}}build/dev-server.js",{{#if_eq mock 'backend'}}
23 | "server": "./node_modules/.bin/nodemon --watch server server/app.js",{{/if_eq}}{{#if_eq mock 'mock'}}
24 | "mock": "./node_modules/.bin/nodemon --watch mock mock/index.js",{{/if_eq}}
25 | "dev": "npm run client|npm run {{#if_eq mock 'backend'}}server{{/if_eq}}{{#if_eq mock 'mock'}}mock{{/if_eq}}",
26 | "build": "node {{#if_eq mock 'backend'}}client/{{/if_eq}}build/build.js",
27 | {{#unless i18n}}"remove:i18n": "node tasks/replaceI18n.js",
28 | {{/unless}}{{#if_eq mock 'mock'}}"remove:mock": "node tasks/mock.js",
29 | {{/if_eq}}"lint": "eslint --ext .js,.vue {{#if_eq mock 'backend'}}client/{{/if_eq}}src"
30 | },
31 | "dependencies": {
32 | {{#if_eq mock "backend"}}
33 | "body-parser": "^1.15.2",
34 | "composable-middleware": "^0.3.0",
35 | "compression": "^1.6.2",
36 | "cookie-parser": "^1.4.3",
37 | "crypto": "0.0.3",
38 | "ejs": "^2.5.2",
39 | {{/if_eq}}
40 | "element-ui": "^1.1.3",
41 | {{#if_eq mock "backend"}}
42 | "express": "^4.14.0",
43 | "express-jwt": "^5.1.0",
44 | {{/if_eq}}{{#if_eq mock "mock"}}
45 | "fms": "0.2.0",
46 | {{/if_eq}}{{#if_eq mock "backend"}}
47 | "jsonwebtoken": "^7.1.9",
48 | {{/if_eq}}
49 | "lodash": "^4.17.2",
50 | {{#if_eq mock "backend"}}
51 | "method-override": "^2.3.7",
52 | {{/if_eq}}{{#if_eq mock "mock"}}
53 | "mockjs": "^1.0.1-beta3",
54 | {{/if_eq}}
55 | "nodemon": "^1.10.2",
56 | {{#if_eq mock "backend"}}
57 | "mongoose": "^4.7.0",
58 | {{/if_eq}}
59 | "nprogress": "^0.2.0",
60 | {{#if_eq mock "backend"}}
61 | "passport": "^0.3.2",
62 | "passport-local": "^1.0.0",
63 | {{/if_eq}}
64 | "socket.io": "^1.7.1",
65 | "socket.io-client": "^1.7.1",
66 | {{#if_eq mock "backend"}}
67 | "socketio-jwt": "^4.5.0",
68 | {{/if_eq}}
69 | "vue": "^2.1.3",
70 | "vue-i18n": "^5.0.3",
71 | "vue-resource": "^1.0.3",
72 | "vue-router": "^2.0.3",
73 | "vuex": "^2.0.0",
74 | "vuex-router-sync": "^4.1.2"
75 | },
76 | "devDependencies": {
77 | "autoprefixer": "^6.7.2",
78 | "babel-core": "^6.22.1",
79 | "babel-eslint": "^7.1.1",
80 | "babel-loader": "^6.2.10",
81 | "babel-plugin-component": "^0.9.1",
82 | "babel-plugin-transform-runtime": "^6.22.0",
83 | "babel-preset-es2015": "^6.22.0",
84 | "babel-preset-stage-2": "^6.22.0",
85 | "babel-register": "^6.22.0",
86 | "connect-history-api-fallback": "^1.3.0",
87 | "cross-spawn": "^5.1.0",
88 | "css-loader": "^0.27.3",
89 | "eslint": "^3.14.1",
90 | "eslint-config-vue": "^2.0.1",
91 | "eslint-friendly-formatter": "^2.0.7",
92 | "eslint-loader": "^1.6.1",
93 | "eslint-plugin-html": "^2.0.1",
94 | "eslint-plugin-vue": "^2.0.1",
95 | "eslint-plugin-promise": "^3.4.0",
96 | "eventsource-polyfill": "^0.9.6",
97 | "express": "^4.14.1",
98 | "extract-text-webpack-plugin": "^2.0.0-rc.3",
99 | "file-loader": "^0.10.0",
100 | "friendly-errors-webpack-plugin": "^1.1.3",
101 | "glob": "^7.1.1",
102 | "html-webpack-plugin": "^2.28.0",
103 | "http-proxy-middleware": "^0.17.3",
104 | "inject-loader": "^2.0.1",
105 | "ora": "^1.1.0",
106 | "optimize-css-assets-webpack-plugin": "^1.3.0",
107 | "os-locale": "^2.0.0",
108 | "shelljs": "^0.7.7",
109 | "stylus": "^0.54.5",
110 | "stylus-loader": "^3.0.1",
111 | "url-loader": "^0.5.7",
112 | "vue-loader": "^11.0.0",
113 | "vue-style-loader": "^2.0.0",
114 | "vue-template-compiler": "^2.1.10",
115 | "webpack": "^2.2.1",
116 | "webpack-bundle-analyzer": "^2.2.1",
117 | "webpack-dev-middleware": "^1.10.0",
118 | "webpack-hot-middleware": "^2.16.1",
119 | "webpack-merge": "^4.1.0"
120 | },
121 | "engines": {
122 | "node": "6.4.0",
123 | "npm": "3.10.6"
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/template/server/README.md:
--------------------------------------------------------------------------------
1 | # Backend server
2 |
--------------------------------------------------------------------------------
/template/server/api/paging.js:
--------------------------------------------------------------------------------
1 | var async = require('async')
2 | module.exports = {
3 | listQuery: function (schema, search, selection, sort, page, callback) {
4 | for (var key in search) {
5 | if (search[key] === null || search[key] === undefined || search[key] === '') {
6 | delete search[key]
7 | }
8 | }
9 | async.parallel({
10 | total: function (done) {
11 | schema.count(search).exec(function (err, total) {
12 | done(err, total)
13 | })
14 | },
15 | records: function (done) {
16 | schema.find(search, selection).sort(sort).skip((+page.current - 1) * (+page.limit))
17 | .limit(+page.limit).exec(function (err, doc) {
18 | done(err, doc)
19 | })
20 | }
21 | }, function functionName (err, data) {
22 | callback(err, {
23 | page: {
24 | total: data.total
25 | },
26 | results: data.records
27 | })
28 | })
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/template/server/api/thing/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var express = require('express')
4 | var controller = require('./thing.controller')
5 |
6 | var router = express.Router()
7 |
8 | router.get('/', controller.index)
9 | router.get('/:id', controller.show)
10 | router.post('/', controller.create)
11 | router.put('/:id', controller.update)
12 | router.patch('/:id', controller.update)
13 | router.delete('/:id', controller.destroy)
14 |
15 | module.exports = router
16 |
--------------------------------------------------------------------------------
/template/server/api/thing/thing.controller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Using Rails-like standard naming convention for endpoints.
3 | * GET /things -> index
4 | * POST /things -> create
5 | * GET /things/:id -> show
6 | * PUT /things/:id -> update
7 | * DELETE /things/:id -> destroy
8 | */
9 |
10 | 'use strict'
11 |
12 | var _ = require('lodash')
13 | var Thing = require('./thing.model')
14 |
15 | // Get list of things
16 | exports.index = function (req, res) {
17 | Thing.find(function (err, things) {
18 | if (err) {
19 | return handleError(res, err)
20 | }
21 | return res.status(200).json({ results: things })
22 | })
23 | }
24 |
25 | // Get a single thing
26 | exports.show = function (req, res) {
27 | Thing.findById(req.params.id, function (err, thing) {
28 | if (err) { return handleError(res, err) }
29 | if (!thing) { return res.send(404) }
30 | return res.json(thing)
31 | })
32 | }
33 |
34 | // Creates a new thing in the DB.
35 | exports.create = function (req, res) {
36 | Thing.create(req.body, function (err, thing) {
37 | if (err) { return handleError(res, err) }
38 | return res.status(200).json(thing)
39 | })
40 | }
41 |
42 | // Updates an existing thing in the DB.
43 | exports.update = function (req, res) {
44 | if (req.body._id) { delete req.body._id }
45 | Thing.findById(req.params.id, function (err, thing) {
46 | if (err) { return handleError(res, err) }
47 | if (!thing) { return res.status(404).send() }
48 | var updated = _.merge(thing, req.body)
49 | updated.save(function (err) {
50 | if (err) { return handleError(res, err) }
51 | return res.status(200).json(thing)
52 | })
53 | })
54 | }
55 |
56 | // Deletes a thing from the DB.
57 | exports.destroy = function (req, res) {
58 | Thing.findById(req.params.id, function (err, thing) {
59 | if (err) { return handleError(res, err) }
60 | if (!thing) { return res.status(404).send() }
61 | thing.remove(function (err) {
62 | if (err) { return handleError(res, err) }
63 | return res.status(204).send()
64 | })
65 | })
66 | }
67 |
68 | function handleError (res, err) {
69 | return res.status(500).send(err)
70 | }
71 |
--------------------------------------------------------------------------------
/template/server/api/thing/thing.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var mongoose = require('mongoose')
4 | var Schema = mongoose.Schema
5 |
6 | var ThingSchema = new Schema({
7 | name: String,
8 | info: String,
9 | active: Boolean
10 | })
11 |
12 | module.exports = mongoose.model('Thing', ThingSchema)
13 |
--------------------------------------------------------------------------------
/template/server/api/thing/thing.socket.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Broadcast updates to client when the model changes
3 | */
4 |
5 | 'use strict'
6 |
7 | var thing = require('./thing.model')
8 |
9 | exports.register = function (socket) {
10 | thing.schema.post('save', function (doc) {
11 | onSave(socket, doc)
12 | })
13 | thing.schema.post('remove', function (doc) {
14 | onRemove(socket, doc)
15 | })
16 | }
17 |
18 | function onSave (socket, doc, cb) {
19 | socket.emit('thing:save', doc)
20 | }
21 |
22 | function onRemove (socket, doc, cb) {
23 | socket.emit('thing:remove', doc)
24 | }
25 |
--------------------------------------------------------------------------------
/template/server/api/thing/thing.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | // var should = require('should')
4 | var app = require('../../app')
5 | var request = require('supertest')
6 |
7 | describe('GET /api/things', function () {
8 | it('should respond with JSON array', function (done) {
9 | request(app)
10 | .get('/api/things')
11 | .expect(200)
12 | .expect('Content-Type', /json/)
13 | .end(function (err, res) {
14 | if (err) return done(err)
15 | res.body.should.be.instanceof(Array)
16 | done()
17 | })
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/template/server/api/user/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var express = require('express')
4 | var controller = require('./user.controller')
5 | // var config = require('../../../config').backend
6 | var auth = require('../../auth/auth.service')
7 |
8 | var router = express.Router()
9 |
10 | router.get('/', auth.hasRole('admin'), controller.index)
11 | router.delete('/:id', auth.hasRole('admin'), controller.destroy)
12 | router.get('/me', auth.isAuthenticated(), controller.me)
13 | router.put('/:id/password', auth.isAuthenticated(), controller.changePassword)
14 | router.get('/:id', auth.isAuthenticated(), controller.show)
15 | router.post('/', controller.create)
16 |
17 | module.exports = router
18 |
--------------------------------------------------------------------------------
/template/server/api/user/user.controller.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var User = require('./user.model')
4 | // var passport = require('passport')
5 | var config = require('../../../config').backend
6 | var jwt = require('jsonwebtoken')
7 | var paging = require('../paging')
8 | var _ = require('lodash')
9 |
10 | var validationError = function (res, err) {
11 | return res.status(422).json(err)
12 | }
13 |
14 | /**
15 | * Get list of users
16 | * restriction: 'admin'
17 | */
18 | exports.index = function (req, res) {
19 | var search = _.merge(req.query.search, { role: 'user' })
20 | paging.listQuery(User, search, '-salt -hashedPassword', {}, req.query.page, function (err, json) {
21 | if (err) return res.status(500).send(err)
22 | res.status(200).json(json)
23 | })
24 | }
25 |
26 | /**
27 | * Creates a new user
28 | */
29 | exports.create = function (req, res, next) {
30 | var newUser = new User(req.body)
31 | newUser.provider = 'local'
32 | newUser.role = 'user'
33 | newUser.save(function (err, user) {
34 | if (err) return validationError(res, err)
35 | var token = jwt.sign({ _id: user._id, name: user.name, role: user.role }, config.secrets.session, { expiresIn: '7d' })
36 | res.json({ token: token })
37 | })
38 | }
39 |
40 | /**
41 | * Get a single user
42 | */
43 | exports.show = function (req, res, next) {
44 | var userId = req.params.id
45 |
46 | User.findById(userId, function (err, user) {
47 | if (err) return next(err)
48 | if (!user) return res.sendStatus(404)
49 | res.json(user.profile)
50 | })
51 | }
52 |
53 | /**
54 | * Deletes a user
55 | * restriction: 'admin'
56 | */
57 | exports.destroy = function (req, res) {
58 | User.findByIdAndRemove(req.params.id, function (err, user) {
59 | if (err) return res.status(500).send(err)
60 | return res.sendStatus(204)
61 | })
62 | }
63 |
64 | /**
65 | * Change a users password
66 | */
67 | exports.changePassword = function (req, res, next) {
68 | var userId = req.user._id
69 | var oldPass = String(req.body.oldPassword)
70 | var newPass = String(req.body.newPassword)
71 |
72 | User.findById(userId, function (err, user) {
73 | if (err) {
74 | // handler error
75 | }
76 | if (user.authenticate(oldPass)) {
77 | user.password = newPass
78 | user.save(function (err) {
79 | if (err) return validationError(res, err)
80 | res.sendStatus(200)
81 | })
82 | } else {
83 | res.status(403).json({ message: 'Old password is not correct.' })
84 | }
85 | })
86 | }
87 |
88 | /**
89 | * Get my info
90 | */
91 | exports.me = function (req, res, next) {
92 | var userId = req.user._id
93 | User.findOne({
94 | _id: userId
95 | }, '-salt -hashedPassword', function (err, user) { // don't ever give out the password or salt
96 | if (err) return next(err)
97 | if (!user) return res.json(401)
98 | res.json(user)
99 | })
100 | }
101 |
--------------------------------------------------------------------------------
/template/server/api/user/user.model.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var mongoose = require('mongoose')
4 | var Schema = mongoose.Schema
5 | var crypto = require('crypto')
6 |
7 | var UserSchema = new Schema({
8 | name: String,
9 | username: { type: String, lowercase: true },
10 | role: {
11 | type: String,
12 | default: 'user'
13 | },
14 | hashedPassword: String,
15 | provider: String,
16 | salt: String
17 | })
18 |
19 | /**
20 | * Virtuals
21 | */
22 | UserSchema
23 | .virtual('password')
24 | .set(function (password) {
25 | this._password = password
26 | this.salt = this.makeSalt()
27 | this.hashedPassword = this.encryptPassword(password)
28 | })
29 | .get(function () {
30 | return this._password
31 | })
32 |
33 | // Public profile information
34 | UserSchema
35 | .virtual('profile')
36 | .get(function () {
37 | return {
38 | 'name': this.name,
39 | 'role': this.role
40 | }
41 | })
42 |
43 | // Non-sensitive info we'll be putting in the token
44 | UserSchema
45 | .virtual('token')
46 | .get(function () {
47 | return {
48 | '_id': this._id,
49 | 'role': this.role
50 | }
51 | })
52 |
53 | /**
54 | * Validations
55 | */
56 |
57 | // Validate empty username
58 | UserSchema
59 | .path('username')
60 | .validate(function (username) {
61 | return username.length
62 | }, 'Username cannot be blank')
63 |
64 | // Validate empty password
65 | UserSchema
66 | .path('hashedPassword')
67 | .validate(function (hashedPassword) {
68 | return hashedPassword.length
69 | }, 'Password cannot be blank')
70 |
71 | // Validate username is not taken
72 | UserSchema
73 | .path('username')
74 | .validate(function (value, respond) {
75 | var self = this
76 | this.constructor.findOne({ username: value }, function (err, user) {
77 | if (err) throw err
78 | if (user) {
79 | if (self.id === user.id) return respond(true)
80 | return respond(false)
81 | }
82 | respond(true)
83 | })
84 | }, 'The specified username is already in use.')
85 |
86 | var validatePresenceOf = function (value) {
87 | return value && value.length
88 | }
89 |
90 | /**
91 | * Pre-save hook
92 | */
93 | UserSchema
94 | .pre('save', function (next) {
95 | if (!this.isNew) return next()
96 |
97 | if (!validatePresenceOf(this.hashedPassword)) {
98 | next(new Error('Invalid password'))
99 | } else {
100 | next()
101 | }
102 | })
103 |
104 | /**
105 | * Methods
106 | */
107 | UserSchema.methods = {
108 | /**
109 | * Authenticate - check if the passwords are the same
110 | *
111 | * @param {String} plainText
112 | * @return {Boolean}
113 | * @api public
114 | */
115 | authenticate: function (plainText) {
116 | return this.encryptPassword(plainText) === this.hashedPassword
117 | },
118 |
119 | /**
120 | * Make salt
121 | *
122 | * @return {String}
123 | * @api public
124 | */
125 | makeSalt: function () {
126 | return crypto.randomBytes(16).toString('base64')
127 | },
128 |
129 | /**
130 | * Encrypt password
131 | *
132 | * @param {String} password
133 | * @return {String}
134 | * @api public
135 | */
136 | encryptPassword: function (password) {
137 | if (!password || !this.salt) return ''
138 | var salt = new Buffer(this.salt, 'base64')
139 | return crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('base64')
140 | }
141 | }
142 |
143 | module.exports = mongoose.model('User', UserSchema)
144 |
--------------------------------------------------------------------------------
/template/server/api/user/user.model.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var should = require('should')
4 | // var app = require('../../app')
5 | var User = require('./user.model')
6 |
7 | var user = new User({
8 | provider: 'local',
9 | name: 'Fake User',
10 | username: 'test@test.com',
11 | password: 'password'
12 | })
13 |
14 | describe('User Model', function () {
15 | before(function (done) {
16 | // Clear users before testing
17 | User.remove().exec().then(function () {
18 | done()
19 | })
20 | })
21 |
22 | afterEach(function (done) {
23 | User.remove().exec().then(function () {
24 | done()
25 | })
26 | })
27 |
28 | it('should begin with no users', function (done) {
29 | User.find({}, function (err, users) {
30 | users.should.have.length(0)
31 | done()
32 | })
33 | })
34 |
35 | it('should fail when saving a duplicate user', function (done) {
36 | user.save(function () {
37 | var userDup = new User(user)
38 | userDup.save(function (err) {
39 | should.exist(err)
40 | done()
41 | })
42 | })
43 | })
44 |
45 | it('should fail when saving without an username', function (done) {
46 | user.username = ''
47 | user.save(function (err) {
48 | should.exist(err)
49 | done()
50 | })
51 | })
52 |
53 | it('should authenticate user if password is valid', function () {
54 | return user.authenticate('password').should.be.true
55 | })
56 |
57 | it('should not authenticate user if password is invalid', function () {
58 | return user.authenticate('blah').should.not.be.true
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/template/server/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Main application file
3 | */
4 |
5 | 'use strict'
6 |
7 | // Set default node environment to development
8 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'
9 |
10 | var express = require('express')
11 | var mongoose = require('mongoose')
12 | var config = require('../config').backend
13 |
14 | // Connect to database
15 | mongoose.connect(config.mongo.uri, config.mongo.options)
16 |
17 | // insure DB with admin user data
18 | require('./config/seed')
19 |
20 | // Setup server
21 | var app = express()
22 | var server = require('http').createServer(app)
23 | var socketio = require('socket.io')(server)
24 | require('./config/socketio')(socketio)
25 | require('./config/express')(app)
26 | require('./routes')(app)
27 |
28 | // Start server
29 | server.listen(config.port, config.ip, function () {
30 | console.log('Express server listening on %d, in %s mode', config.port, app.get('env'))
31 | })
32 |
33 | // Expose app
34 | exports = module.exports = app
35 |
--------------------------------------------------------------------------------
/template/server/auth/auth.service.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | // var mongoose = require('mongoose')
4 | // var passport = require('passport')
5 | var config = require('../../config').backend
6 | var jwt = require('jsonwebtoken')
7 | var expressJwt = require('express-jwt')
8 | var compose = require('composable-middleware')
9 | var User = require('../api/user/user.model')
10 | var validateJwt = expressJwt({ secret: config.secrets.session })
11 |
12 | /**
13 | * Attaches the user object to the request if authenticated
14 | * Otherwise returns 403
15 | */
16 | function isAuthenticated () {
17 | return compose()
18 | // Validate jwt
19 | .use(function (req, res, next) {
20 | // allow access_token to be passed through query parameter as well
21 | if (req.query && req.query.hasOwnProperty('access_token')) {
22 | req.headers.authorization = 'Bearer ' + req.query.access_token // eslint-disable-line
23 | }
24 | validateJwt(req, res, next)
25 | })
26 | // Attach user to request
27 | .use(function (req, res, next) {
28 | User.findById(req.user._id, function (err, user) {
29 | if (err) return next(err)
30 | if (!user) return res.sendStatus(401)
31 |
32 | req.user = user
33 | next()
34 | })
35 | })
36 | }
37 |
38 | /**
39 | * Checks if the user role meets the minimum requirements of the route
40 | */
41 | function hasRole (roleRequired) {
42 | if (!roleRequired) throw new Error('Required role needs to be set')
43 |
44 | return compose()
45 | .use(isAuthenticated())
46 | .use(function meetsRequirements (req, res, next) {
47 | if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) {
48 | next()
49 | } else {
50 | res.sendStatus(403)
51 | }
52 | })
53 | }
54 |
55 | /**
56 | * Returns a jwt token signed by the app secret
57 | */
58 | function signToken (user) {
59 | return jwt.sign({ _id: user._id, name: user.name, role: user.role }, config.secrets.session, { expiresIn: '7d' })
60 | }
61 |
62 | /**
63 | * Set token cookie directly for oAuth strategies
64 | */
65 | function setTokenCookie (req, res) {
66 | if (!req.user) return res.json(404, { message: 'Something went wrong, please try again.' })
67 | var token = signToken(req.user)
68 | res.cookie('token', JSON.stringify(token))
69 | res.redirect('/')
70 | }
71 |
72 | exports.isAuthenticated = isAuthenticated
73 | exports.hasRole = hasRole
74 | exports.signToken = signToken
75 | exports.setTokenCookie = setTokenCookie
76 |
--------------------------------------------------------------------------------
/template/server/auth/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var express = require('express')
4 | // var passport = require('passport')
5 | var config = require('../../config').backend
6 | var User = require('../api/user/user.model')
7 |
8 | // Passport Configuration
9 | require('./local/passport').setup(User, config)
10 |
11 | var router = express.Router()
12 |
13 | router.use('/local', require('./local'))
14 |
15 | module.exports = router
16 |
--------------------------------------------------------------------------------
/template/server/auth/local/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var express = require('express')
4 | var passport = require('passport')
5 | var auth = require('../auth.service')
6 |
7 | var router = express.Router()
8 |
9 | router.post('/', function (req, res, next) {
10 | passport.authenticate('local', function (err, user, info) {
11 | var error = err || info
12 | if (error) return res.status(401).json(error)
13 | if (!user) return res.status(404).json({ message: 'Something went wrong, please try again.' })
14 |
15 | var token = auth.signToken(user)
16 | res.json({ token: token })
17 | })(req, res, next)
18 | })
19 |
20 | module.exports = router
21 |
--------------------------------------------------------------------------------
/template/server/auth/local/passport.js:
--------------------------------------------------------------------------------
1 | var passport = require('passport')
2 | var LocalStrategy = require('passport-local').Strategy
3 |
4 | exports.setup = function (User, config) {
5 | passport.use(new LocalStrategy({
6 | usernameField: 'username',
7 | passwordField: 'password' // this is the virtual field on the model
8 | },
9 | function (username, password, done) {
10 | User.findOne({
11 | username: username.toLowerCase()
12 | }, function (err, user) {
13 | if (err) return done(err)
14 |
15 | if (!user) {
16 | return done(null, false, { message: 'This username is not registered.' })
17 | }
18 | if (!user.authenticate(password)) {
19 | return done(null, false, { message: 'This password is not correct.' })
20 | }
21 | return done(null, user)
22 | })
23 | }))
24 | }
25 |
--------------------------------------------------------------------------------
/template/server/components/errors/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Error responses
3 | */
4 |
5 | 'use strict'
6 |
7 | module.exports[404] = function pageNotFound (req, res) {
8 | var viewFilePath = '404'
9 | var statusCode = 404
10 | var result = {
11 | status: statusCode
12 | }
13 |
14 | res.status(result.status)
15 | res.render(viewFilePath, function (err) {
16 | if (err) { return res.json(result.status) }
17 |
18 | res.render(viewFilePath)
19 | })
20 | }
21 |
--------------------------------------------------------------------------------
/template/server/config/express.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Express configuration
3 | */
4 |
5 | 'use strict'
6 |
7 | var express = require('express')
8 | var path = require('path')
9 | var compression = require('compression')
10 | var bodyParser = require('body-parser')
11 | var methodOverride = require('method-override')
12 | var cookieParser = require('cookie-parser')
13 | var config = require('../../config')
14 | var passport = require('passport')
15 |
16 | module.exports = function (app) {
17 | // render
18 | app.set('views', config.backend.root + '/server/views')
19 | app.engine('html', require('ejs').renderFile)
20 | app.set('view engine', 'html')
21 |
22 | app.use(compression())
23 | app.use(bodyParser.urlencoded({ extended: false }))
24 | app.use(bodyParser.json())
25 | app.use(methodOverride())
26 | app.use(cookieParser())
27 | app.use(passport.initialize())
28 |
29 | if (config.backend.serverFrontend) {
30 | var staticPath = path.posix.join(config.frontend.assetsPublicPath, config.frontend.assetsSubDirectory)
31 | app.use(staticPath, express.static(path.join(config.backend.frontend, '/static')))
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/template/server/config/seed.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Populate DB with admin user data on server start
3 | */
4 |
5 | 'use strict'
6 |
7 | var User = require('../api/user/user.model')
8 |
9 | // search for admin user, if no, create one
10 | User.find({ role: 'admin' }, function (err, admin) {
11 | if (err) throw err
12 | if (!(admin && admin.length)) {
13 | User.create({
14 | provider: 'local',
15 | role: 'admin',
16 | name: 'Admin',
17 | username: 'admin',
18 | password: 'admin'
19 | }, function () {
20 | console.log('finished populating users')
21 | })
22 | }
23 | })
24 |
--------------------------------------------------------------------------------
/template/server/config/socketio.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Socket.io configuration
3 | */
4 |
5 | 'use strict'
6 | var socketioJwt = require('socketio-jwt')
7 | var config = require('../../config').backend
8 |
9 | // When the user disconnects.. perform this
10 | function onDisconnect (socket) {
11 | }
12 |
13 | // When the user connects.. perform this
14 | function onConnect (socket) {
15 | // When the client emits 'info', this listens and executes
16 | socket.on('info', function (data) {
17 | console.info('[%s] %s', socket.address, JSON.stringify(data, null, 2))
18 | })
19 |
20 | socket.on('client:hello', function (data) {
21 | console.info('Received from client: %s', data)
22 | socket.emit('server:hello', 'server hello')
23 | })
24 |
25 | // Insert sockets below
26 | require('../api/thing/thing.socket').register(socket)
27 | }
28 |
29 | module.exports = function (socketio) {
30 | socketio.sockets
31 | .on('connection', socketioJwt.authorize({
32 | secret: config.secrets.session,
33 | timeout: 15000 // 15 seconds to send the authentication message
34 | }))
35 | .on('authenticated', function (socket) {
36 | // this socket is authenticated, we are good to handle more events from it.
37 | console.log('hello! ' + socket.decoded_token.name)
38 | socket.address = socket.handshake.address ||
39 | socket.handshake.headers.host || process.env.DOMAIN
40 |
41 | socket.connectedAt = new Date()
42 |
43 | // Call onDisconnect.
44 | socket.on('disconnect', function () {
45 | onDisconnect(socket)
46 | console.info('[%s] DISCONNECTED', socket.address)
47 | })
48 |
49 | // Call onConnect.
50 | onConnect(socket)
51 | console.info('[%s] CONNECTED', socket.address)
52 | })
53 | }
54 |
--------------------------------------------------------------------------------
/template/server/routes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Main application routes
3 | */
4 |
5 | 'use strict'
6 |
7 | var errors = require('./components/errors')
8 | var config = require('../config').backend
9 | var path = require('path')
10 |
11 | module.exports = function (app) {
12 | // Insert routes below
13 | app.use('/api/things', require('./api/thing'))
14 | app.use('/api/users', require('./api/user'))
15 |
16 | app.use('/api/auth', require('./auth'))
17 |
18 | // All undefined asset or api routes should return a 404
19 | app.route('/:url(api|auth|static)/*').get(errors[404])
20 |
21 | // All other routes should redirect to the index.html
22 | if (config.serverFrontend) {
23 | app.route('/*').get(function (req, res) {
24 | res.sendFile(path.join(config.frontend, '/index.html'))
25 | })
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/template/server/views/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Page Not Found :(
6 |
141 |
142 |
143 |
144 |
Not found :(
145 |
Sorry, but the page you were trying to view does not exist.
146 |
It looks like this was the result of either:
147 |
148 | - a mistyped address
149 | - an out-of-date link
150 |
151 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/template/tasks/mock.js:
--------------------------------------------------------------------------------
1 | require('shelljs/global')
2 | var path = require('path')
3 | var fs = require('fs')
4 | var remove = require('./remove')
5 | const YELLOW = '\x1b[33m'
6 | const BLUE = '\x1b[34m'
7 | const END = '\x1b[0m'
8 | console.log('mv client folder %s to root path %s', path.join(__dirname, '../client/'), path.join(__dirname, '..'))
9 |
10 | /* eslint-disable */
11 | mv(path.join(__dirname, '../client/*'), path.join(__dirname, '../'))
12 | rm('-rf', path.join(__dirname, '../client'))
13 | rm('-rf', path.join(__dirname, '../server'))
14 | remove(__filename)
15 |
16 | // rewrite package.json
17 | console.log(BLUE + 'Replace package.json......' + END)
18 | var pkg = require('../package.json')
19 | delete pkg.scripts['remove:mock']
20 | fs.writeFile(path.join(__dirname, '../package.json'), JSON.stringify(pkg, null, 2) + '\n', function (err) {
21 | if (err) {
22 | console.error(YELLOW + 'Error when replace package.json, you may remove `remove:mock` script by yourself.' + END)
23 | }
24 | })
25 |
26 | console.log(YELLOW + 'Finished. Now you can run the following script.' + END)
27 |
--------------------------------------------------------------------------------
/template/tasks/remove.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | require('shelljs/global')
3 | module.exports = function (filepath, cb) {
4 | /* eslint-disable */
5 | fs.readdir(__dirname, function (err, files) {
6 | if (files.length > 2) {
7 | rm('-f', filepath)
8 | if (cb) {
9 | cb()
10 | }
11 | } else {
12 | rm('-rf', __dirname)
13 | if (cb) {
14 | cb()
15 | }
16 | }
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/template/tasks/replaceI18n.js:
--------------------------------------------------------------------------------
1 | require('babel-register')
2 | var osLocale = require('os-locale')()
3 | var glob = require('glob')
4 | var path = require('path')
5 | var fs = require('fs')
6 | var _ = require('lodash')
7 | var remove = require('./remove')
8 |
9 | const YELLOW = '\x1b[33m'
10 | const BLUE = '\x1b[34m'
11 | const END = '\x1b[0m'
12 |
13 | var clientPath
14 | if (fs.existsSync(path.join(__dirname, '../client'))) {
15 | clientPath = path.join(__dirname, '../client')
16 | } else {
17 | clientPath = path.join(__dirname, '../')
18 | }
19 |
20 | // :prop="$t()"
21 | var syntax1 = /:([a-z-]+=")\$t\('([a-zA-Z_\w\.]+)'\)(")/g
22 |
23 | // {{$t()}}
24 | var syntax2 = /\{\{\$t\('([a-zA-Z_\w\.]+)'\)\}\}/g
25 |
26 | // this.$t()
27 | var syntax3 = /this.\$t\('([a-zA-Z_\w\.]+)'\)/g
28 |
29 | // :prop="xxx$t('')xxx$t()"
30 | var syntax4 = /:([a-z-]+=".+)\$t\('([a-zA-Z_\w\.]+)'\)(.+)\$t\('([a-zA-Z_\.]+)'\)(")/g
31 |
32 | // Vue.t('xxx')
33 | var syntax5 = /Vue\.t\('([a-zA-Z_\w\.]+)'\)/g
34 |
35 | var _locale = 'zh-CN'
36 | // os locale not math browser language
37 | var langMap = {
38 | 'zh_CN': 'zh-CN',
39 | 'en_US': 'en'
40 | }
41 | osLocale.then(locale => {
42 | // set locale
43 | if (langMap[locale] !== _locale) {
44 | _locale = 'en'
45 | }
46 | var langConfig = require(path.join(clientPath, 'src/locale/', _locale)).default
47 |
48 | // match files
49 | console.log(BLUE + 'Replace src files......' + END)
50 | glob(clientPath + '/src/**/*.{js,vue}', function (er, files) {
51 | files.forEach(function (file) {
52 | fs.readFile(file, 'utf8', function (err, data) {
53 | if (err) {
54 | console.error(YELLOW + 'Error when read file: ' + file + ', you may check the file yourself to see if \'$t\' syntax is exist.' + END)
55 | } else {
56 | var replaced = data.replace(syntax1, function () {
57 | return arguments[1] + _.get(langConfig, arguments[2]) + arguments[3]
58 | }).replace(syntax2, function () {
59 | return _.get(langConfig, arguments[1])
60 | }).replace(syntax3, function () {
61 | return '\'' + _.get(langConfig, arguments[1]) + '\''
62 | }).replace(syntax4, function () {
63 | return ':' + arguments[1] + '\'' + _.get(langConfig, arguments[2]) + '\'' +
64 | arguments[3] + '\'' + _.get(langConfig, arguments[4]) + '\'' + arguments[5]
65 | }).replace(syntax5, function () {
66 | return '\'' + _.get(langConfig, arguments[1]) + '\''
67 | })
68 | fs.writeFile(file, replaced, function (err) {
69 | if (err) {
70 | console.error(YELLOW + 'Error when replace file: ' + file + ', you may replace this file by yourself.' + END)
71 | }
72 | })
73 | }
74 | })
75 | })
76 | })
77 |
78 | // rewrite package.json
79 | console.log(BLUE + 'Replace package.json......' + END)
80 | var pkg = require('../package.json')
81 | delete pkg.devDependencies['os-locale']
82 | delete pkg.devDependencies['glob']
83 | delete pkg.devDependencies['babel-register']
84 | delete pkg.scripts['remove:i18n']
85 | fs.writeFile(path.join(__dirname, '../package.json'), JSON.stringify(pkg, null, 2) + '\n', function (err) {
86 | if (err) {
87 | console.error(YELLOW + 'Error when replace package.json, you may remove \'babel-register\' \'glob\' \'os-locale\' dependencies and `remove:i18n` script by yourself.' + END)
88 | }
89 | })
90 |
91 | // delete locale folder
92 | console.log(BLUE + 'Remove locale and task folder......' + END)
93 | require('shelljs/global')
94 | rm('-rf', path.join(clientPath, 'src/locale'))
95 | remove(__filename)
96 |
97 | console.log(YELLOW + 'Finished. Now you can run the following script.' + END)
98 | })
99 |
--------------------------------------------------------------------------------