├── template ├── static │ └── .gitkeep ├── server │ ├── logs │ │ └── .gitkeep │ ├── api │ │ ├── config.pro.json │ │ ├── config.dev.json │ │ ├── index.js │ │ └── http-request.js │ ├── favicon.ico │ ├── config │ │ ├── config.js │ │ ├── oss.conf.js │ │ └── wx.conf.js │ ├── routes │ │ └── index.js │ ├── package.json │ ├── bin │ │ └── www │ ├── logger.js │ └── app.js ├── src │ ├── assets │ │ ├── css │ │ │ └── my.css │ │ ├── fonts │ │ │ └── .gitkeep │ │ ├── images │ │ │ ├── .gitkeep │ │ │ ├── 404.gif │ │ │ ├── logo.png │ │ │ └── loading.gif │ │ └── js │ │ │ ├── config.js │ │ │ ├── tool.js │ │ │ ├── date.js │ │ │ └── makePY.js │ ├── components │ │ └── .gitkeep │ ├── store │ │ ├── customer │ │ │ ├── getters.js │ │ │ ├── type.js │ │ │ ├── mutations.js │ │ │ └── actions.js │ │ └── index.js │ ├── views │ │ ├── 404.vue │ │ └── Home.vue │ ├── plugins │ │ └── ajax │ │ │ ├── index.js │ │ │ └── http.js │ ├── router │ │ ├── home.js │ │ └── index.js │ ├── directive │ │ ├── input-quantity.js │ │ └── input-price.js │ ├── mixins │ │ └── index.js │ ├── App.vue │ └── main.js ├── .eslintignore ├── config │ ├── prod.env.js │ ├── test.env.js │ ├── dev.env.js │ └── index.js ├── test │ ├── unit │ │ ├── .eslintrc │ │ ├── specs │ │ │ └── Hello.spec.js │ │ ├── index.js │ │ └── karma.conf.js │ └── e2e │ │ ├── specs │ │ └── test.js │ │ ├── custom-assertions │ │ └── elementCount.js │ │ ├── nightwatch.conf.js │ │ └── runner.js ├── .editorconfig ├── .postcssrc.js ├── .gitignore ├── .babelrc ├── build │ ├── dev-client.js │ ├── vue-loader.conf.js │ ├── webpack.test.conf.js │ ├── build.js │ ├── webpack.dev.conf.js │ ├── check-versions.js │ ├── webpack.base.conf.js │ ├── utils.js │ ├── dev-server.js │ └── webpack.prod.conf.js ├── index.html ├── README.md ├── .eslintrc.js └── package.json ├── .gitignore ├── circle.yml ├── test.sh ├── deploy-docs.sh ├── package.json ├── docs ├── SUMMARY.md ├── README.md ├── linter.md ├── prerender.md ├── env.md ├── proxy.md ├── commands.md ├── e2e.md ├── unit.md ├── pre-processors.md ├── backend.md ├── structure.md └── static.md ├── meta.js └── README.md /template/static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /template/server/logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /template/src/assets/css/my.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /template/src/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /template/src/assets/fonts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /template/src/assets/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /template/server/api/config.pro.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /template/server/api/config.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /template/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | docs/_book 4 | .idea 5 | .vscode 6 | -------------------------------------------------------------------------------- /template/config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 6 4 | 5 | test: 6 | override: 7 | - bash test.sh 8 | -------------------------------------------------------------------------------- /template/server/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joneqian/vue2-express/HEAD/template/server/favicon.ico -------------------------------------------------------------------------------- /template/src/assets/images/404.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joneqian/vue2-express/HEAD/template/src/assets/images/404.gif -------------------------------------------------------------------------------- /template/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joneqian/vue2-express/HEAD/template/src/assets/images/logo.png -------------------------------------------------------------------------------- /template/src/store/customer/getters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/2/5. 3 | */ 4 | 5 | export default { 6 | }; 7 | -------------------------------------------------------------------------------- /template/src/assets/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joneqian/vue2-express/HEAD/template/src/assets/images/loading.gif -------------------------------------------------------------------------------- /template/src/assets/js/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 16/9/9. 3 | */ 4 | export default function () { 5 | return {}; 6 | }; 7 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | yes "" | ./node_modules/.bin/vue init . test 4 | 5 | cd test 6 | npm install 7 | npm run lint 8 | npm test 9 | npm run build 10 | -------------------------------------------------------------------------------- /template/test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /template/config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /template/server/api/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/4/17. 3 | */ 4 | /* 5 | * 与后端通讯的API模块 6 | * 请根据实际情况修改 7 | * */ 8 | 'use strict'; 9 | exports = module.exports = {}; -------------------------------------------------------------------------------- /template/config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /deploy-docs.sh: -------------------------------------------------------------------------------- 1 | cd docs 2 | rm -rf _book 3 | gitbook build 4 | cd _book 5 | git init 6 | git add -A 7 | git commit -m 'update book' 8 | git push -f git@github.com:vuejs-templates/webpack.git master:gh-pages 9 | -------------------------------------------------------------------------------- /template/src/store/customer/type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/2/4. 3 | */ 4 | export const GET_CUSTOMER_INFO = 'GET_CUSTOMER_INFO'; 5 | export const UPDATE_CUSTOMER_INFO = 'UPDATE_CUSTOMER_INFO'; 6 | -------------------------------------------------------------------------------- /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/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserlist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /template/src/views/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /template/src/store/customer/mutations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/2/4. 3 | */ 4 | import * as type from './type'; 5 | 6 | export default { 7 | [type.UPDATE_CUSTOMER_INFO] (state, {info}) { 8 | state.info = info; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | .idea 7 | .vscode 8 | {{#unit}} 9 | test/unit/coverage 10 | {{/unit}} 11 | {{#e2e}} 12 | test/e2e/reports 13 | selenium-debug.log 14 | {{/e2e}} 15 | server/logs/*.log 16 | server/public 17 | server/views -------------------------------------------------------------------------------- /template/src/plugins/ajax/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/3/13. 3 | */ 4 | import http from './http'; 5 | 6 | export default { 7 | install (Vue) { 8 | Vue.prototype.$http = http; 9 | Vue.http = http; 10 | }, 11 | $http: http 12 | }; 13 | 14 | export const $http = http; -------------------------------------------------------------------------------- /template/server/config/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2016/12/24. 3 | */ 4 | 'use strict'; 5 | const oss = require('./oss.conf'); 6 | const wx = require('./wx.conf'); 7 | const api = require('./api.conf'); 8 | const config = { 9 | oss, wx, api 10 | }; 11 | 12 | module.exports = config; 13 | -------------------------------------------------------------------------------- /template/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-2" 5 | ], 6 | "plugins": ["transform-runtime"], 7 | "comments": false, 8 | "env": { 9 | "test": { 10 | "presets": ["env", "stage-2"], 11 | "plugins": [ "istanbul" ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /template/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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue2-express", 3 | "version": "1.1.0", 4 | "license": "MIT", 5 | "description": "Vue 2.0全家桶套餐,包含vue-router2和vuex2,集成express作为web server。", 6 | "scripts": { 7 | "docs": "cd docs && gitbook serve", 8 | "docs:deploy": "bash ./deploy-docs.sh" 9 | }, 10 | "devDependencies": { 11 | "vue-cli": "^2.8.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /template/server/routes/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/2/13. 3 | */ 4 | const express = require('express'); 5 | const router = express.Router(); 6 | 7 | module.exports = router; 8 | 9 | router.get('/', function (req, res, next) { 10 | res.redirect('index'); 11 | }); 12 | 13 | router.get('/index', function (req, res, next) { 14 | res.render('index', {title: 'Vue'}); 15 | }); 16 | -------------------------------------------------------------------------------- /template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ name }} 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /template/server/config/oss.conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2016/12/24. 3 | */ 4 | 'use strict'; 5 | const oss = { 6 | dev: { 7 | region: '', 8 | accessKeyId: '', 9 | accessKeySecret: '', 10 | bucket: '' 11 | }, 12 | pro: { 13 | region: '', 14 | accessKeyId: '', 15 | accessKeySecret: '', 16 | bucket: '' 17 | } 18 | } 19 | 20 | module.exports = process.env.NODE_ENV === 'debug' ? oss.dev:oss.pro; 21 | 22 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Project Structure](structure.md) 4 | - [Build Commands](commands.md) 5 | - [Linter Configuration](linter.md) 6 | - [Pre-Processors](pre-processors.md) 7 | - [Handling Static Assets](static.md) 8 | - [Environment Variables](env.md) 9 | - [Integrate with Backend Framework](backend.md) 10 | - [API Proxying During Development](proxy.md) 11 | - [Unit Testing](unit.md) 12 | - [End-to-end Testing](e2e.md) 13 | - [Prerendering for SEO](prerender.md) 14 | -------------------------------------------------------------------------------- /template/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }), 12 | transformToRequire: { 13 | video: 'src', 14 | source: 'src', 15 | img: 'src', 16 | image: 'xlink:href' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /template/src/store/customer/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/2/4. 3 | */ 4 | import {$http} from '../../plugins/ajax'; 5 | import * as type from './type'; 6 | 7 | export default { 8 | [type.GET_CUSTOMER_INFO] ({commit, state}) { 9 | return new Promise((resolve, reject) => { 10 | $http('/url', {}) 11 | .then(data => { 12 | commit(type.UPDATE_CUSTOMER_INFO, data); 13 | resolve(); 14 | }) 15 | .catch(error => { 16 | reject(error); 17 | }); 18 | }); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /template/src/router/home.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/1/22. 3 | */ 4 | // require.ensure 是 Webpack 的特殊语法,用来设置 组件到底路径 5 | /** 6 | * 1.定义路由,每个路由应该映射一个组件 7 | * path : 浏览器的显示的路径 8 | * name : 路由的名字 9 | * component : 路由的组件路径 10 | */ 11 | const routers = [ 12 | { 13 | path: '/home', 14 | name: 'home', 15 | component(resolve) { 16 | require.ensure(['../views/Home.vue'], () => { 17 | resolve(require('../views/Home.vue')); 18 | }); 19 | }, 20 | meta: {requiresAuth: false, title: '首页'} 21 | } 22 | ]; 23 | 24 | export default routers; 25 | -------------------------------------------------------------------------------- /template/server/config/wx.conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/2/4. 3 | */ 4 | 'use strict'; 5 | const wx = { 6 | dev: { 7 | appId: '', 8 | secret: '', 9 | redirect_outh: '', 10 | url: '', 11 | merchantId: '', 12 | merchantKey: '', 13 | key: '', 14 | notifyUrl: '' 15 | }, 16 | pro: { 17 | appId: '', 18 | secret: '', 19 | redirect_outh: '', 20 | url: '', 21 | merchantId: '', 22 | merchantKey: '', 23 | key: '', 24 | notifyUrl: '' 25 | } 26 | } 27 | 28 | module.exports = process.env.NODE_ENV === 'debug' ? wx.dev : wx.pro; 29 | -------------------------------------------------------------------------------- /template/src/store/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/1/22. 3 | */ 4 | import Vue from 'vue'; 5 | import Vuex from 'vuex'; 6 | 7 | import customerMutations from './customer/mutations'; 8 | import customerAcions from './customer/actions'; 9 | import customerGetters from './customer/getters'; 10 | 11 | Vue.use(Vuex); 12 | 13 | const customer = { 14 | state: { 15 | info: {} 16 | }, 17 | getters: customerGetters, 18 | actions: customerAcions, 19 | mutations: customerMutations 20 | }; 21 | 22 | export default new Vuex.Store({ 23 | state: { 24 | token: '' 25 | }, 26 | modules: { 27 | customer 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /template/src/directive/input-quantity.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/2/14. 3 | */ 4 | export default (Vue, Options = {}) => { 5 | Vue.directive('inputQuantity', { 6 | bind: (el, binding) => { 7 | el.addEventListener('input', () => { 8 | let value = el.value; 9 | value = value.replace(/[^\d]/g, ''); // 清除“数字”以外的字符 10 | value = value.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.'); 11 | value = value.replace(/([0-9]+\.[0-9]{2})[0-9]*/, '$1'); 12 | el.value = value; 13 | }); 14 | }, 15 | unbind: (el) => { 16 | el.removeEventListener('input', undefined); 17 | } 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /template/test/unit/specs/Hello.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 2 | import Hello from '@/components/Hello'{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 3 | 4 | describe('Hello.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(Hello){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 7 | const vm = new Constructor().$mount(){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .to.equal('Welcome to Your Vue.js App'){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 10 | }){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 11 | }){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 12 | -------------------------------------------------------------------------------- /template/src/directive/input-price.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/2/14. 3 | */ 4 | export default (Vue, Options = {}) => { 5 | Vue.directive('inputPrice', { 6 | bind: (el, binding) => { 7 | el.addEventListener('input', () => { 8 | let value = el.value; 9 | value = value.replace(/[^\d.]/g, ''); // 清除“数字”和“.”以外的字符 10 | value = value.replace(/^\./g, ''); // 验证第一个字符是数字而不是. 11 | value = value.replace(/\.{2,}/g, '.'); // 只保留第一个. 清除多余的. 12 | value = value.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.').replace(/^(.*\..{2}).*$/, '$1'); 13 | el.value = value; 14 | }); 15 | }, 16 | unbind: (el, binding) => { 17 | el.removeEventListener('input', undefined); 18 | } 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /template/README.md: -------------------------------------------------------------------------------- 1 | # {{ name }} 2 | 3 | > {{ description }} 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | {{#unit}} 20 | 21 | # run unit tests 22 | npm run unit 23 | {{/unit}} 24 | {{#e2e}} 25 | 26 | # run e2e tests 27 | npm run e2e 28 | {{/e2e}} 29 | {{#if_or unit e2e}} 30 | 31 | # run all tests 32 | npm test 33 | {{/if_or}} 34 | ``` 35 | 36 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 37 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This boilerplate is targeted towards large, serious projects and assumes you are somewhat familiar with Webpack and `vue-loader`. Make sure to also read [`vue-loader`'s documentation](http://vuejs.github.io/vue-loader/index.html) for common workflow recipes. 4 | 5 | If you just want to try out `vue-loader` or whip out a quick prototype, use the [webpack-simple](https://github.com/vuejs-templates/webpack-simple) template instead. 6 | 7 | ## Quickstart 8 | 9 | To use this template, scaffold a project with [vue-cli](https://github.com/vuejs/vue-cli). **It is recommended to use npm 3+ for a more efficient dependency tree.** 10 | 11 | ``` bash 12 | $ npm install -g vue-cli 13 | $ vue init webpack my-project 14 | $ cd my-project 15 | $ npm install 16 | $ npm run dev 17 | ``` 18 | -------------------------------------------------------------------------------- /template/test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function {{#if_eq lintConfig "airbnb"}}test{{/if_eq}}(browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end(){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 18 | }{{#if_eq lintConfig "airbnb"}},{{/if_eq}} 19 | }{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 20 | -------------------------------------------------------------------------------- /docs/linter.md: -------------------------------------------------------------------------------- 1 | # Linter Configuration 2 | 3 | This boilerplate uses [ESLint](http://eslint.org/) as the linter, and uses the [Standard](https://github.com/feross/standard/blob/master/RULES.md) preset with some small customizations. 4 | 5 | If you are not happy with the default linting rules, you have several options: 6 | 7 | 1. Overwrite individual rules in `.eslintrc.js`. For example, you can add the following rule to enforce semicolons instead of omitting them: 8 | 9 | ``` js 10 | "semi": [2, "always"] 11 | ``` 12 | 13 | 2. Pick a different ESLint preset when generating the project, for example [eslint-config-airbnb](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb). 14 | 15 | 3. Pick "none" for ESLint preset when generating the project and define your own rules. See [ESLint documentation](http://eslint.org/docs/rules/) for more details. 16 | -------------------------------------------------------------------------------- /template/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue2-express-web-server", 3 | "version": "1.0.0", 4 | "description": "vue2-express-web-server", 5 | "author": "joneqian", 6 | "private": true, 7 | "scripts": { 8 | "start": "node ./bin/www", 9 | "start:strict": "node --use-strict ./bin/www" 10 | }, 11 | "dependencies": { 12 | "ali-oss": "^4.8.0", 13 | "async": "^1.5.2", 14 | "axios": "^0.15.3", 15 | "body-parser": "^1.15.2", 16 | "co": "^4.6.0", 17 | "compression": "^1.7.0", 18 | "connect-redis": "^3.3.0", 19 | "cookie-parser": "^1.4.3", 20 | "debug": "^2.2.0", 21 | "ejs": "^2.5.6", 22 | "excel-export": "^0.5.1", 23 | "exceljs": "^0.2.46", 24 | "express": "^4.14.1", 25 | "express-session": "^1.15.4", 26 | "log4js": "^1.1.1", 27 | "morgan": "^1.7.0", 28 | "serve-favicon": "^2.3.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /template/test/unit/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 2 | Vue.config.productionTip = false{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 3 | 4 | // Polyfill fn.bind() for PhantomJS 5 | /* eslint-disable no-extend-native */ 6 | Function.prototype.bind = require('function-bind'){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 7 | 8 | // require all test files (files that ends with .spec.js) 9 | const testsContext = require.context('./specs', true, /\.spec$/){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 10 | testsContext.keys().forEach(testsContext){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 11 | 12 | // require all src files except main.js for coverage. 13 | // you can also change this to match only the subset of files that 14 | // you want coverage for. 15 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 16 | srcContext.keys().forEach(srcContext){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 17 | -------------------------------------------------------------------------------- /template/build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | // This is the webpack config used for unit tests. 2 | 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseConfig = require('./webpack.base.conf') 7 | 8 | var webpackConfig = merge(baseConfig, { 9 | // use inline sourcemap for karma-sourcemap-loader 10 | module: { 11 | rules: utils.styleLoaders() 12 | }, 13 | devtool: '#inline-source-map', 14 | resolveLoader: { 15 | alias: { 16 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 17 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 18 | 'scss-loader': 'sass-loader' 19 | } 20 | }, 21 | plugins: [ 22 | new webpack.DefinePlugin({ 23 | 'process.env': require('../config/test.env') 24 | }) 25 | ] 26 | }) 27 | 28 | // no need for app entry during tests 29 | delete webpackConfig.entry 30 | 31 | module.exports = webpackConfig 32 | -------------------------------------------------------------------------------- /template/src/mixins/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/3/27. 3 | */ 4 | export default { 5 | methods: { 6 | jump (url, replace = false) { 7 | if (/^javas/.test(url) || !url) { 8 | return; 9 | } 10 | 11 | let useRouter = typeof url === 'object' || (this.$router && typeof url === 'string' && !/http/.test(url)); 12 | if (useRouter) { 13 | if (!replace) { 14 | this.$router.push(url); 15 | } else { 16 | this.$router.replace(url); 17 | } 18 | } else { 19 | if (!replace) { 20 | window.location.href = url; 21 | } else { 22 | window.location.replace(url); 23 | } 24 | } 25 | }, 26 | getUrl(url) { 27 | // Make sure the href is right in hash mode 28 | if (this.$router && !this.$router._history && typeof url === 'string' && !/http/.test(url)) { 29 | return `#!${url}`; 30 | } 31 | return url && typeof url !== 'object' ? url : 'javascript:void(0);'; 32 | } 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /template/src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 39 | -------------------------------------------------------------------------------- /template/build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /template/src/router/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/1/22. 3 | */ 4 | import Vue from 'vue'; 5 | import VueRouter from 'vue-router'; 6 | import home from './home'; 7 | import store from '../store'; 8 | 9 | Vue.use(VueRouter); 10 | 11 | const routes = [ 12 | { 13 | path: '', 14 | redirect: '/home', 15 | meta: {requiresAuth: false} 16 | }, 17 | ...home, 18 | { 19 | path: '*', 20 | component(resolve) { 21 | require.ensure(['../views/404.vue'], () => { 22 | resolve(require('../views/404.vue')); 23 | }); 24 | } 25 | } 26 | ]; 27 | 28 | // 页面刷新时,重新赋值token 29 | // if (window.localStorage.getItem('token')) { 30 | // store.commit(types.LOGIN, window.localStorage.getItem('token')) 31 | // } 32 | 33 | const router = new VueRouter({ 34 | routes 35 | }); 36 | 37 | router.beforeEach((to, from, next) => { 38 | if (to.matched.some(r => r.meta.requiresAuth)) { 39 | if (store.state.token) { 40 | next(); 41 | } else { 42 | next({ 43 | path: '/login', 44 | query: {redirect: to.fullPath} 45 | }); 46 | } 47 | } else { 48 | next(); 49 | } 50 | }); 51 | 52 | export default router; 53 | -------------------------------------------------------------------------------- /template/test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 11 | this.expected = count{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 12 | this.pass = function (val) { 13 | return val === this.expected{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 14 | } 15 | this.value = function (res) { 16 | return res.value{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 17 | } 18 | this.command = function (cb) { 19 | var self = this{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 22 | }, [selector], function (res) { 23 | cb.call(self, res){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 24 | }){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /template/test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/getingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/prerender.md: -------------------------------------------------------------------------------- 1 | # Prerendering for SEO 2 | 3 | If you want to prerender routes that will not significantly change once pushed to production, use this Webpack plugin: [prerender-spa-plugin](https://www.npmjs.com/package/prerender-spa-plugin), which has been tested for use with Vue. For pages that _do_ frequently change, [Prerender.io](https://prerender.io/) and [Netlify](https://www.netlify.com/pricing) both offer plans for regularly re-prerendering your content for search engines. 4 | 5 | ## Using `prerender-spa-plugin` 6 | 7 | 1. Install it as a dev dependency: 8 | 9 | ```bash 10 | npm install --save-dev prerender-spa-plugin 11 | ``` 12 | 13 | 2. Require it in **build/webpack.prod.conf.js**: 14 | 15 | ```js 16 | // This line should go at the top of the file where other 'imports' live in 17 | var PrerenderSpaPlugin = require('prerender-spa-plugin') 18 | ``` 19 | 20 | 3. Configure it in the `plugins` array (also in **build/webpack.prod.conf.js**): 21 | 22 | ```js 23 | new PrerenderSpaPlugin( 24 | // Path to compiled app 25 | path.join(__dirname, '../dist'), 26 | // List of endpoints you wish to prerender 27 | [ '/' ] 28 | ) 29 | ``` 30 | 31 | If you also wanted to prerender `/about` and `/contact`, then that array would be `[ '/', '/about', '/contact' ]`. 32 | -------------------------------------------------------------------------------- /template/test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var webpackConfig = require('../../build/webpack.test.conf'){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 7 | 8 | module.exports = function (config) { 9 | config.set({ 10 | // to run in additional browsers: 11 | // 1. install corresponding karma launcher 12 | // http://karma-runner.github.io/0.13/config/browsers.html 13 | // 2. add it to the `browsers` array below. 14 | browsers: ['PhantomJS'], 15 | frameworks: ['mocha', 'sinon-chai'], 16 | reporters: ['spec', 'coverage'], 17 | files: ['./index.js'], 18 | preprocessors: { 19 | './index.js': ['webpack', 'sourcemap'] 20 | }, 21 | webpack: webpackConfig, 22 | webpackMiddleware: { 23 | noInfo: true{{#if_eq lintConfig "airbnb"}},{{/if_eq}} 24 | }, 25 | coverageReporter: { 26 | dir: './coverage', 27 | reporters: [ 28 | { type: 'lcov', subdir: '.' }, 29 | { type: 'text-summary' }{{#if_eq lintConfig "airbnb"}},{{/if_eq}} 30 | ] 31 | }{{#if_eq lintConfig "airbnb"}},{{/if_eq}} 32 | }){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 33 | }{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 34 | -------------------------------------------------------------------------------- /template/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | 32 | 33 | 52 | -------------------------------------------------------------------------------- /template/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }), 33 | new FriendlyErrorsPlugin() 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /template/src/main.js: -------------------------------------------------------------------------------- 1 | {{#if_eq build "standalone"}} 2 | // The Vue build version to load with the `import` command 3 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 4 | {{/if_eq}} 5 | 6 | import Vue from 'vue'; 7 | import App from './App.vue'; 8 | import router from './router'; 9 | import store from './store'; 10 | import AjaxPlugin from './plugins/ajax'; 11 | import VueLazyload from 'vue-lazyload'; // 引入图片懒加载模块 12 | import inputPrice from './directive/input-price'; 13 | import inputQuantity from './directive/input-quantity'; 14 | import mixins from './mixins'; 15 | import './assets/css/my.css'; 16 | 17 | Vue.use(AjaxPlugin); 18 | Vue.use(inputPrice); 19 | Vue.use(inputQuantity); 20 | Vue.use(require('vue-wechat-title')); 21 | 22 | Vue.mixin(mixins); 23 | 24 | // error,loading是图片路径, 用require引入 25 | Vue.use(VueLazyload, { 26 | error: require('./assets/images/404.gif'), 27 | loading: require('./assets/images/loading.gif'), 28 | attempt: 1{{#if_eq lintConfig "airbnb"}},{{/if_eq}} 29 | }); 30 | 31 | /* eslint-disable no-new */ 32 | new Vue({ 33 | el: '#app', 34 | store, 35 | router, 36 | {{#if_eq build "runtime"}} 37 | render: h => h(App){{#if_eq lintConfig "airbnb"}},{{/if_eq}} 38 | {{/if_eq}} 39 | {{#if_eq build "standalone"}} 40 | template: '', 41 | components: { App }{{#if_eq lintConfig "airbnb"}},{{/if_eq}} 42 | {{/if_eq}} 43 | }); 44 | -------------------------------------------------------------------------------- /docs/env.md: -------------------------------------------------------------------------------- 1 | # Environment Variables 2 | 3 | Sometimes it is practical to have different config values according to the environment that the application is running in. 4 | 5 | As an example: 6 | 7 | ```js 8 | // config/prod.env.js 9 | module.exports = { 10 | NODE_ENV: '"production"', 11 | DEBUG_MODE: false, 12 | API_KEY: '"..."' // this is shared between all environments 13 | } 14 | 15 | // config/dev.env.js 16 | module.exports = merge(prodEnv, { 17 | NODE_ENV: '"development"', 18 | DEBUG_MODE: true // this overrides the DEBUG_MODE value of prod.env 19 | }) 20 | 21 | // config/test.env.js 22 | module.exports = merge(devEnv, { 23 | NODE_ENV: '"testing"' 24 | }) 25 | ``` 26 | 27 | > **Note:** string variables need to be wrapped into single and double quotes `'"..."'` 28 | 29 | So, the environment variables are: 30 | - Production 31 | - NODE_ENV = 'production', 32 | - DEBUG_MODE = false, 33 | - API_KEY = '...' 34 | - Development 35 | - NODE_ENV = 'development', 36 | - DEBUG_MODE = true, 37 | - API_KEY = '...' 38 | - Testing 39 | - NODE_ENV = 'testing', 40 | - DEBUG_MODE = true, 41 | - API_KEY = '...' 42 | 43 | As we can see, `test.env` inherits the `dev.env` and the `dev.env` inherits the `prod.env`. 44 | 45 | ### Usage 46 | 47 | It is simple to use the environment variables in your code. For example: 48 | 49 | ```js 50 | Vue.config.debug = process.env.DEBUG_MODE 51 | ``` -------------------------------------------------------------------------------- /template/build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /template/config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../server/views/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../server/views'), 9 | assetsSubDirectory: '../public', 10 | assetsPublicPath: '/', 11 | productionSourceMap: false, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 8080, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: { // debug 模式下请配置post代理到express相应的路径 31 | 32 | }, 33 | // CSS Sourcemaps off by default because relative paths are "buggy" 34 | // with this option, according to the CSS-Loader README 35 | // (https://github.com/webpack/css-loader#sourcemaps) 36 | // In our experience, they generally work as expected, 37 | // just be aware of this issue when enabling this option. 38 | cssSourceMap: false 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /template/test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing'{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 3 | var server = require('../../build/dev-server.js'){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 4 | 5 | // 2. run the nightwatch test suite against it 6 | // to run in additional browsers: 7 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 8 | // 2. add it to the --env flag below 9 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 10 | // For more information on Nightwatch's config file, see 11 | // http://nightwatchjs.org/guide#settings-file 12 | var opts = process.argv.slice(2){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 13 | if (opts.indexOf('--config') === -1) { 14 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 15 | } 16 | if (opts.indexOf('--env') === -1) { 17 | opts = opts.concat(['--env', 'chrome']){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 18 | } 19 | 20 | var spawn = require('cross-spawn'){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 21 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 22 | 23 | runner.on('exit', function (code) { 24 | server.close(){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 25 | process.exit(code){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 26 | }){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 27 | 28 | runner.on('error', function (err) { 29 | server.close(){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 30 | throw err{{#if_eq lintConfig "airbnb"}};{{/if_eq}} 31 | }){{#if_eq lintConfig "airbnb"}};{{/if_eq}} 32 | -------------------------------------------------------------------------------- /docs/proxy.md: -------------------------------------------------------------------------------- 1 | # API Proxying During Development 2 | 3 | When integrating this boilerplate with an existing backend, a common need is to access the backend API when using the dev server. To achieve that, we can run the dev server and the API backend side-by-side (or remotely), and let the dev server proxy all API requests to the actual backend. 4 | 5 | To configure the proxy rules, edit `dev.proxyTable` option in `config/index.js`. The dev server is using [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) for proxying, so you should refer to its docs for detailed usage. But here's a simple example: 6 | 7 | ``` js 8 | // config/index.js 9 | module.exports = { 10 | // ... 11 | dev: { 12 | proxyTable: { 13 | // proxy all requests starting with /api to jsonplaceholder 14 | '/api': { 15 | target: 'http://jsonplaceholder.typicode.com', 16 | changeOrigin: true, 17 | pathRewrite: { 18 | '^/api': '' 19 | } 20 | } 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | The above example will proxy the request `/api/posts/1` to `http://jsonplaceholder.typicode.com/posts/1`. 27 | 28 | ## URL Matching 29 | 30 | In addition to static urls you can also use glob patterns to match URLs, e.g. `/api/**`. See [Context Matching](https://github.com/chimurai/http-proxy-middleware#context-matching) for more details. In addition, you can provide a `filter` option that can be a custom function to determine whether a request should be proxied: 31 | 32 | ``` js 33 | proxyTable: { 34 | '*': { 35 | target: 'http://jsonplaceholder.typicode.com', 36 | filter: function (pathname, req) { 37 | return pathname.match('^/api') && req.method === 'GET' 38 | } 39 | } 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /template/server/api/http-request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/4/17. 3 | */ 4 | const axios = require('axios'); 5 | const logger = require('../logger').api; 6 | // axios 配置 7 | // axios.defaults.timeout = 25000; 8 | // axios.defaults.baseURL = 'https://api.github.com'; 9 | 10 | /*** 11 | * ajax请求函数 12 | * @param url 13 | * @param json 14 | * @param method 15 | * @param timeout 16 | * @returns {*} 17 | */ 18 | exports = module.exports = function (cloud, url, json, method = 'post', timeout = 25000) { 19 | var promise = new Promise((resolve, reject) => { 20 | if (!cloud || !url || !json) { 21 | reject('cloud, url, data参数错误'); 22 | return; 23 | } 24 | 25 | let req = { 26 | url: `http://${cloud.ip}:${cloud.port}${cloud.basePath}${url}`, 27 | method: method, 28 | data: json, 29 | timeout: timeout, 30 | headers: {'Accept': 'application/json', 'Content-Type': 'application/json'} 31 | }; 32 | logger.debug(`${req.url} ###: ${JSON.stringify(json)}`); 33 | axios(req) 34 | .then((response) => { 35 | let res = response.data; 36 | if (res.HasError) { 37 | logger.error(`${req.url} request error: ${res.Fault.ErrorDescription}`); 38 | reject({IsSuccess: 0, Data: res.Body, ErrorMessage: res.Fault.ErrorDescription}); 39 | } else { 40 | if (Array.isArray(res.Body)) { 41 | resolve({IsSuccess: 1, Data: {List: res.Body, Count: res.Paging.TotalCount}, ErrorMessage: ''}); 42 | } else { 43 | resolve({IsSuccess: 1, Data: res.Body, ErrorMessage: ''}); 44 | } 45 | } 46 | }) 47 | .catch((error) => { 48 | logger.error(`${req.url} request error: ${error.message}`); 49 | reject({IsSuccess: 0, Data: {}, ErrorMessage: error.message}); 50 | }); 51 | }); 52 | 53 | return promise; 54 | }; -------------------------------------------------------------------------------- /docs/commands.md: -------------------------------------------------------------------------------- 1 | # Build Commands 2 | 3 | All build commands are executed via [NPM Scripts](https://docs.npmjs.com/misc/scripts). 4 | 5 | ### `npm run dev` 6 | 7 | > Starts a Node.js local development server. See [API Proxying During Development](proxy.md) for more details. 8 | 9 | - Webpack + `vue-loader` for single file Vue components. 10 | - State preserving hot-reload 11 | - State preserving compilation error overlay 12 | - Lint-on-save with ESLint 13 | - Source maps 14 | 15 | ### `npm run build` 16 | 17 | > Build assets for production. See [Integrating with Backend Framework](backend.md) for more details. 18 | 19 | - JavaScript minified with [UglifyJS](https://github.com/mishoo/UglifyJS2). 20 | - HTML minified with [html-minifier](https://github.com/kangax/html-minifier). 21 | - CSS across all components extracted into a single file and minified with [cssnano](https://github.com/ben-eb/cssnano). 22 | - All static assets compiled with version hashes for efficient long-term caching, and a production `index.html` is auto-generated with proper URLs to these generated assets. 23 | - Also see [deployment notes](#how-do-i-deploy-built-assets-with-my-backend-framework). 24 | 25 | ### `npm run unit` 26 | 27 | > Run unit tests in PhantomJS with [Karma](https://karma-runner.github.io/). See [Unit Testing](unit.md) for more details. 28 | 29 | - Supports ES2015+ in test files. 30 | - Supports all webpack loaders. 31 | - Easy [mock injection](http://vuejs.github.io/vue-loader/en/workflow/testing-with-mocks.html). 32 | 33 | ### `npm run e2e` 34 | 35 | > Run end-to-end tests with [Nightwatch](http://nightwatchjs.org/). See [End-to-end Testing](e2e.md) for more details. 36 | 37 | - Run tests in multiple browsers in parallel. 38 | - Works with one command out of the box: 39 | - Selenium and chromedriver dependencies automatically handled. 40 | - Automatically spawns the Selenium server. 41 | -------------------------------------------------------------------------------- /template/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | commonjs: true, 12 | es6: true, 13 | jquery: true 14 | }, 15 | {{#if_eq lintConfig "standard"}} 16 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 17 | extends: 'standard', 18 | {{/if_eq}} 19 | {{#if_eq lintConfig "airbnb"}} 20 | extends: 'airbnb-base', 21 | {{/if_eq}} 22 | // required to lint *.vue files 23 | plugins: [ 24 | 'html' 25 | ], 26 | {{#if_eq lintConfig "airbnb"}} 27 | // check if imports actually resolve 28 | 'settings': { 29 | 'import/resolver': { 30 | 'webpack': { 31 | 'config': 'build/webpack.base.conf.js' 32 | } 33 | } 34 | }, 35 | {{/if_eq}} 36 | // add your custom rules here 37 | 'rules': { 38 | {{#if_eq lintConfig "standard"}} 39 | // allow paren-less arrow functions 40 | 'arrow-parens': 0, 41 | // allow async-await 42 | 'generator-star-spacing': 0, 43 | 'semi': ['error', 'always'], 44 | 'indent': 0, 45 | 'space-before-function-paren': 0, 46 | "new-cap": 0, 47 | "eol-last": 0, 48 | "no-multiple-empty-lines": [1, {"max": 2}], 49 | {{/if_eq}} 50 | {{#if_eq lintConfig "airbnb"}} 51 | // don't require .vue extension when importing 52 | 'import/extensions': ['error', 'always', { 53 | 'js': 'never', 54 | 'vue': 'never' 55 | }], 56 | // allow optionalDependencies 57 | 'import/no-extraneous-dependencies': ['error', { 58 | 'optionalDependencies': ['test/unit/index.js'] 59 | }], 60 | {{/if_eq}} 61 | // allow debugger during development 62 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docs/e2e.md: -------------------------------------------------------------------------------- 1 | # End-to-end Testing 2 | 3 | This boilerplate uses [Nightwatch.js](http://nightwatchjs.org) for e2e tests. Nightwatch.js is a highly integrated e2e test runner built on top of Selenium. This boilerplate comes with Selenium server and chromedriver binaries pre-configured for you, so you don't have to mess with these yourself. 4 | 5 | Let's take a look at the files in the `test/e2e` directory: 6 | 7 | - `runner.js` 8 | 9 | A Node.js script that starts the dev server, and then launches Nightwatch to run tests against it. This is the script that will run when you run `npm run e2e`. 10 | 11 | - `nightwatch.conf.js` 12 | 13 | Nightwatch configuration file. See [Nightwatch's docs on configuration](http://nightwatchjs.org/guide#settings-file) for more details. 14 | 15 | - `custom-assertions/` 16 | 17 | Custom assertions that can be used in Nightwatch tests. See [Nightwatch's docs on writing custom assertions](http://nightwatchjs.org/guide#writing-custom-assertions) for more details. 18 | 19 | - `specs/` 20 | 21 | You actual tests! See [Nightwatch's docs on writing tests](http://nightwatchjs.org/guide#writing-tests) and [API reference](http://nightwatchjs.org/api) for more details. 22 | 23 | ### Running Tests in More Browsers 24 | 25 | To configure which browsers to run the tests in, add an entry under "test_settings" in [`test/e2e/nightwatch.conf.js`](https://github.com/vuejs-templates/webpack/blob/master/template/test/e2e/nightwatch.conf.js#L17-L39) , and also the `--env` flag in [`test/e2e/runner.js`](https://github.com/vuejs-templates/webpack/blob/master/template/test/e2e/runner.js#L15). If you wish to configure remote testing on services like SauceLabs, you can either make the Nightwatch config conditional based on environment variables, or use a separate config file altogether. Consult [Nightwatch's docs on Selenium](http://nightwatchjs.org/guide#selenium-settings) for more details. 26 | -------------------------------------------------------------------------------- /docs/unit.md: -------------------------------------------------------------------------------- 1 | # Unit Testing 2 | 3 | An overview of the tools used by this boilerplate for unit testing: 4 | 5 | - [Karma](https://karma-runner.github.io/): the test runner that launches browsers, runs the tests and reports the results to us. 6 | - [karma-webpack](https://github.com/webpack/karma-webpack): the plugin for Karma that bundles our tests using Webpack. 7 | - [Mocha](https://mochajs.org/): the test framework that we write test specs with. 8 | - [Chai](http://chaijs.com/): test assertion library that provides better assertion syntax. 9 | - [Sinon](http://sinonjs.org/): test utility library that provides spies, stubs and mocks. 10 | 11 | Chai and Sinon are integrated using [karma-sinon-chai](https://github.com/kmees/karma-sinon-chai), so all Chai interfaces (`should`, `expect`, `assert`) and `sinon` are globally available in test files. 12 | 13 | And the files: 14 | 15 | - `index.js` 16 | 17 | This is the entry file used by `karma-webpack` to bundle all the test code and source code (for coverage purposes). You can ignore it for the most part. 18 | 19 | - `specs/` 20 | 21 | This directory is where you write your actual tests. You can use full ES2015+ and all supported Webpack loaders in your tests. 22 | 23 | - `karma.conf.js` 24 | 25 | This is the Karma configuration file. See [Karma docs](https://karma-runner.github.io/) for more details. 26 | 27 | ## Running Tests in More Browsers 28 | 29 | You can run the tests in multiple real browsers by installing more [karma launchers](https://karma-runner.github.io/1.0/config/browsers.html) and adjusting the `browsers` field in `test/unit/karma.conf.js`. 30 | 31 | ## Mocking Dependencies 32 | 33 | This boilerplate comes with [inject-loader](https://github.com/plasticine/inject-loader) installed by default. For usage with `*.vue` components, see [vue-loader docs on testing with mocks](http://vue-loader.vuejs.org/en/workflow/testing-with-mocks.html). 34 | -------------------------------------------------------------------------------- /template/server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('express-template:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '10091'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /template/server/logger.js: -------------------------------------------------------------------------------- 1 | var log4js = require('log4js'); 2 | 3 | log4js.configure({ 4 | 'appenders': [ 5 | { 6 | 'category': 'access', 7 | 'type': 'dateFile', 8 | 'filename': __dirname + '/logs/access.log', 9 | 'pattern': '-yyyy-MM-dd', 10 | 'backups': 10 11 | }, 12 | { 13 | 'category': 'system', 14 | 'type': 'dateFile', 15 | 'filename': __dirname + '/logs/system.log', 16 | 'pattern': '-yyyy-MM-dd', 17 | 'backups': 10 18 | }, 19 | { 20 | 'category': 'error', 21 | 'type': 'dateFile', 22 | 'filename': __dirname + '/logs/error.log', 23 | 'pattern': '-yyyy-MM-dd', 24 | 'backups': 10 25 | }, 26 | { 27 | 'category': 'oss', 28 | 'type': 'dateFile', 29 | 'filename': __dirname + '/logs/oss.log', 30 | 'pattern': '-yyyy-MM-dd', 31 | 'backups': 10 32 | }, 33 | { 34 | 'category': 'api', 35 | 'type': 'dateFile', 36 | 'filename': __dirname + '/logs/api.log', 37 | 'pattern': '-yyyy-MM-dd', 38 | 'backups': 10 39 | }, 40 | { 41 | 'type': 'console' 42 | }] 43 | }); 44 | 45 | var environment = process.env.NODE_ENV || 'production'; 46 | 47 | if (environment === 'production') { 48 | log4js.getLogger('access').setLevel('INFO'); 49 | log4js.getLogger('system').setLevel('INFO'); 50 | log4js.getLogger('error').setLevel('INFO'); 51 | log4js.getLogger('oss').setLevel('INFO'); 52 | log4js.getLogger('api').setLevel('INFO'); 53 | } else { 54 | log4js.getLogger('access').setLevel('DEBUG'); 55 | log4js.getLogger('system').setLevel('DEBUG'); 56 | log4js.getLogger('error').setLevel('DEBUG'); 57 | log4js.getLogger('oss').setLevel('DEBUG'); 58 | log4js.getLogger('api').setLevel('DEBUG'); 59 | } 60 | 61 | module.exports = { 62 | access: log4js.getLogger('access'), 63 | system: log4js.getLogger('system'), 64 | oss: log4js.getLogger('oss'), 65 | api: log4js.getLogger('api'), 66 | express: log4js.connectLogger(log4js.getLogger('access'), {level: log4js.levels.INFO, format:':remote-addr :method :url :status'}), 67 | }; 68 | -------------------------------------------------------------------------------- /docs/pre-processors.md: -------------------------------------------------------------------------------- 1 | # Pre-Processors 2 | 3 | This boilerplate has pre-configured CSS extraction for most popular CSS pre-processors including LESS, SASS, Stylus, and PostCSS. To use a pre-processor, all you need to do is installing the appropriate webpack loader for it. For example, to use SASS: 4 | 5 | ``` bash 6 | npm install sass-loader node-sass --save-dev 7 | ``` 8 | 9 | Note you also need to install `node-sass` because `sass-loader` depends on it as a peer dependency. 10 | 11 | ### Using Pre-Processors inside Components 12 | 13 | Once installed, you can use the pre-processors inside your `*.vue` components using the `lang` attribute on ` 19 | ``` 20 | 21 | ### A note on SASS syntax 22 | 23 | - `lang="scss"` corresponds to the CSS-superset syntax (with curly braces and semicolones). 24 | - `lang="sass"` corresponds to the indentation-based syntax. 25 | 26 | ### PostCSS 27 | 28 | Styles in `*.vue` files are piped through PostCSS by default, so you don't need to use a specific loader for it. You can simply add PostCSS plugins you want to use in `build/webpack.base.conf.js` under the `vue` block: 29 | 30 | ``` js 31 | // build/webpack.base.conf.js 32 | module.exports = { 33 | // ... 34 | vue: { 35 | postcss: [/* your plugins */] 36 | } 37 | } 38 | ``` 39 | 40 | See [vue-loader's related documentation](http://vuejs.github.io/vue-loader/en/features/postcss.html) for more details. 41 | 42 | ### Standalone CSS Files 43 | 44 | To ensure consistent extraction and processing, it is recommended to import global, standalone style files from your root `App.vue` component, for example: 45 | 46 | ``` html 47 | 48 | 49 | ``` 50 | 51 | Note you should probably only do this for the styles written by yourself for your application. For existing libraries e.g. Bootstrap or Semantic UI, you can place them inside `/static` and reference them directly in `index.html`. This avoids extra build time and also is better for browser caching. (See [Static Asset Handling](static.md)) 52 | -------------------------------------------------------------------------------- /template/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js' 13 | }, 14 | output: { 15 | path: config.build.assetsRoot, 16 | filename: '[name].js', 17 | publicPath: process.env.NODE_ENV === 'production' 18 | ? config.build.assetsPublicPath 19 | : config.dev.assetsPublicPath 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.vue', '.json'], 23 | alias: { 24 | {{#if_eq build "standalone"}} 25 | 'vue$': 'vue/dist/vue.esm.js', 26 | {{/if_eq}} 27 | '@': resolve('src') 28 | } 29 | }, 30 | module: { 31 | rules: [ 32 | {{#lint}} 33 | { 34 | test: /\.(js|vue)$/, 35 | loader: 'eslint-loader', 36 | enforce: 'pre', 37 | include: [resolve('src'), resolve('test')], 38 | options: { 39 | formatter: require('eslint-friendly-formatter') 40 | } 41 | }, 42 | {{/lint}} 43 | { 44 | test: /\.vue$/, 45 | loader: 'vue-loader', 46 | options: vueLoaderConfig 47 | }, 48 | { 49 | test: /\.js$/, 50 | loader: 'babel-loader', 51 | include: [resolve('src'), resolve('test')] 52 | }, 53 | { 54 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 55 | loader: 'url-loader', 56 | options: { 57 | limit: 10000, 58 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 59 | } 60 | }, 61 | { 62 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 63 | loader: 'url-loader', 64 | options: { 65 | limit: 10000, 66 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 67 | } 68 | }, 69 | { 70 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 71 | loader: 'url-loader', 72 | options: { 73 | limit: 10000, 74 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 75 | } 76 | } 77 | ] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /template/build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader] 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }) 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /template/server/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qianqing 3 | * @create by 16-4-23 4 | * @description 5 | */ 6 | var express = require('express'); 7 | var path = require('path'); 8 | var favicon = require('serve-favicon'); 9 | var logger = require('./logger'); 10 | var cookieParser = require('cookie-parser'); 11 | var bodyParser = require('body-parser'); 12 | var compression = require('compression'); 13 | var session = require('express-session'); 14 | var RedisStore = require('connect-redis')(session); 15 | var ejs = require('ejs'); 16 | var index = require('./routes/index'); 17 | var app = express(); 18 | 19 | 20 | // view engine setup 21 | app.set('views', path.join(__dirname, 'views')); 22 | app.set('view engine', 'ejs'); 23 | app.engine('.html', ejs.__express); 24 | app.set('view engine', 'html'); 25 | 26 | app.use(compression()); 27 | 28 | // uncomment after placing your favicon in /public 29 | app.use(favicon(path.join(__dirname, '', 'favicon.ico'))); 30 | app.use(logger.express); 31 | app.use(bodyParser.json({ limit: '50mb' })); 32 | app.use(bodyParser.urlencoded({ limit: '50mb', extended: false })); 33 | app.use(cookieParser('myunweb_')); 34 | 35 | app.use(session({ 36 | store: new RedisStore({ 37 | host: "127.0.0.1", 38 | port: 6379, 39 | db: 2 40 | }), 41 | secret: 'MYun 123!@# web', 42 | key: 'sid', 43 | cookie: { secure: false, maxAge: 3 * 24 * 3600 * 1000 }, 44 | resave: false, 45 | saveUninitialized: true 46 | })); 47 | app.use(express.static(__dirname)); 48 | 49 | //处理webpack服务请求 50 | app.get('/__webpack_hmr', function(req, res) { 51 | res.send('') 52 | }) 53 | app.use('/', index); 54 | 55 | // catch 404 and forward to error handler 56 | app.use(function(req, res, next) { 57 | var err = new Error('Not Found'); 58 | err.status = 404; 59 | next(err); 60 | }); 61 | 62 | // error handlers 63 | 64 | // development error handler 65 | // will print stacktrace 66 | if (app.get('env') === 'development') { 67 | app.use(function(err, req, res, next) { 68 | res.status(err.status || 500); 69 | res.render('error', { 70 | message: err.message, 71 | error: err 72 | }); 73 | }); 74 | } 75 | 76 | // production error handler 77 | // no stacktraces leaked to user 78 | app.use(function(err, req, res, next) { 79 | res.status(err.status || 500); 80 | res.render('error', { 81 | message: err.message, 82 | error: {} 83 | }); 84 | }); 85 | 86 | module.exports = app; 87 | -------------------------------------------------------------------------------- /template/src/plugins/ajax/http.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qianqing on 2017/3/13. 3 | */ 4 | import axios from 'axios'; 5 | // import store from '../../store'; 6 | // import router from '../../router'; 7 | 8 | // 将token直接放在前端还是有风险,目前采取在server端统一存储token 9 | // axios 配置 10 | // axios.defaults.timeout = 25000; 11 | // axios.defaults.baseURL = 'https://api.github.com'; 12 | 13 | // http request 拦截器 14 | // axios.interceptors.request.use( 15 | // config => { 16 | // if (store.state.token) { 17 | // config.headers.Authorization = `token ${store.state.token}`; 18 | // } 19 | // return config; 20 | // }, 21 | // err => { 22 | // return Promise.reject(err); 23 | // }); 24 | 25 | // http response 拦截器 26 | // axios.interceptors.response.use( 27 | // response => { 28 | // return response; 29 | // }, 30 | // error => { 31 | // if (error.response) { 32 | // switch (error.response.status) { 33 | // case 401: 34 | // // 401 清除token信息并跳转到登录页面 35 | // router.replace({ 36 | // path: 'login', 37 | // query: {redirect: router.currentRoute.fullPath} 38 | // }); 39 | // } 40 | // } 41 | // // console.log(JSON.stringify(error));//console : Error: Request failed with status code 402 42 | // return Promise.reject(error.response.data); 43 | // }); 44 | 45 | /*** 46 | * ajax请求函数 47 | * @param url 48 | * @param json 49 | * @param method 50 | * @param timeout 51 | * @returns {*} 52 | */ 53 | export default function (url, json, method = 'post', timeout = 25000) { 54 | return new Promise((resolve, reject) => { 55 | if (!url || !json) { 56 | reject(`url or josn is null`); 57 | return; 58 | } 59 | 60 | let req = { 61 | url: url, 62 | method: method, 63 | data: json, 64 | timeout: timeout, 65 | headers: {'Accept': 'application/json', 'Content-Type': 'application/json'} 66 | }; 67 | 68 | axios(req) 69 | .then((response) => { 70 | let res = response.data; 71 | if (res.IsSuccess === 1) { 72 | resolve(res.Data); 73 | } else { 74 | if (res.ErrorMessage === 'Request failed with status code 401') { // 如需对token过期做特殊处理,请修改 75 | reject(`${url} ${res.ErrorMessage}`); 76 | } else { 77 | reject(`${url} ${res.ErrorMessage}`); 78 | } 79 | } 80 | }) 81 | .catch((error) => { 82 | console.error(`ajax error: ${url} ### ${error}`); 83 | if (error.message) { 84 | reject(`${url} ${error.message}`); 85 | } else { 86 | reject(`ajax 异常: ${url}`); 87 | } 88 | }); 89 | }); 90 | }; -------------------------------------------------------------------------------- /meta.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "helpers": { 3 | "if_or": function (v1, v2, options) { 4 | if (v1 || v2) { 5 | return options.fn(this); 6 | } 7 | 8 | return options.inverse(this); 9 | } 10 | }, 11 | "prompts": { 12 | "name": { 13 | "type": "string", 14 | "required": true, 15 | "message": "Project name" 16 | }, 17 | "description": { 18 | "type": "string", 19 | "required": false, 20 | "message": "Project description", 21 | "default": "A Vue.js project" 22 | }, 23 | "author": { 24 | "type": "string", 25 | "message": "Author" 26 | }, 27 | "build": { 28 | "type": "list", 29 | "message": "Vue build", 30 | "choices": [ 31 | { 32 | "name": "Runtime + Compiler: recommended for most users", 33 | "value": "standalone", 34 | "short": "standalone" 35 | }, 36 | { 37 | "name": "Runtime-only: about 6KB lighter min+gzip, but templates (or any Vue-specific HTML) are ONLY allowed in .vue files - render functions are required elsewhere", 38 | "value": "runtime", 39 | "short": "runtime" 40 | } 41 | ] 42 | }, 43 | "lint": { 44 | "type": "confirm", 45 | "message": "Use ESLint to lint your code?" 46 | }, 47 | "lintConfig": { 48 | "when": "lint", 49 | "type": "list", 50 | "message": "Pick an ESLint preset", 51 | "choices": [ 52 | { 53 | "name": "Standard (https://github.com/feross/standard)", 54 | "value": "standard", 55 | "short": "Standard" 56 | }, 57 | { 58 | "name": "AirBNB (https://github.com/airbnb/javascript)", 59 | "value": "airbnb", 60 | "short": "AirBNB" 61 | }, 62 | { 63 | "name": "none (configure it yourself)", 64 | "value": "none", 65 | "short": "none" 66 | } 67 | ] 68 | }, 69 | "unit": { 70 | "type": "confirm", 71 | "message": "Setup unit tests with Karma + Mocha?" 72 | }, 73 | "e2e": { 74 | "type": "confirm", 75 | "message": "Setup e2e tests with Nightwatch?" 76 | } 77 | }, 78 | "filters": { 79 | ".eslintrc.js": "lint", 80 | ".eslintignore": "lint", 81 | "config/test.env.js": "unit || e2e", 82 | "test/unit/**/*": "unit", 83 | "build/webpack.test.conf.js": "unit", 84 | "test/e2e/**/*": "e2e" 85 | }, 86 | "skipInterpolation": [ 87 | "src/assets/**/*", 88 | "src/directive/**/*", 89 | "src/router/**/*", 90 | "src/store/**/*" 91 | ], 92 | "completeMessage": "To get started:\n\n {{^inPlace}}cd {{destDirName}}\n {{/inPlace}}npm install\n npm run dev\n\nDocumentation can be found at https://vuejs-templates.github.io/webpack" 93 | }; 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue2-express 2 | 3 | > Vue 2.0全家桶套餐,包含vue-router2.0和vuex2.0,集成express作为web server。 4 | > 目前支持vue2.4和webpack3.0。 5 | 6 | ## 前言 7 | 该脚手架以express作为web server,集成了vue2.0、vue-router2.0和vuex2.0。 8 | 用于方便快速的创建工程,并实现生产环境的部署。 9 | 10 | ## 运行及构建 11 | ``` bash 12 | $ npm install -g vue-cli 13 | $ vue init joneqian/vue2-express my-project 14 | $ cd my-project 15 | $ npm install 16 | $ npm run dev //运行调试模式 17 | ``` 18 | 如果你的8080端口被占用,请修改`/config/index.js`文件。 19 | 20 | ## 生产环境部署和运行 21 | 将代码clone到服务器,运行: 22 | ``` bash 23 | $ npm install 24 | $ cd server 25 | $ npm install 26 | $ cd .. 27 | $ npm run build 28 | ``` 29 | 脚本将在server目录自动创建`public`和`views`目录。 30 | 通过`node server/bin/www`启动项目(生产环境建议使用pm2)。 31 | 32 | ## 项目结构 33 | 34 | ``` 35 | ├── README.md 36 | ├── build // webpack build文件 37 | │ 38 | ├── config // webpack 配置文件 39 | │ 40 | ├── server // express(server端) 41 | │   ├── bin 42 | │   ├── config // server端配置项,包含api、oss、微信等 43 | │   ├── logs // server端运行日志 44 | │   ├── routes // express 路由 45 | │   ├── app.js 46 | │   ├── favicon.ico 47 | │   ├── logger.js // 日志模块 48 | │   ├── package.json 49 | │   ├── public // 打包构建后的资源文件夹 50 | │   └── views // 打包构建后的页面文件夹 51 | │ 52 | ├── src 53 | │   ├── assets 54 | │   │   ├── css 55 | │   │   ├── fonts 56 | │   │   ├── images 57 | │   │   └── js // 前端js工具集 58 | │   │ 59 | │   ├── components // vue组件 60 | │   ├── directive // vue指令 61 | │   ├── plugins // vue插件 62 | │   ├── router // vue-router 63 | │   ├── store // vuex store 64 | │   ├── views // vue 页面 65 | │   ├── main.js 66 | │   └── App.vue 67 | │ 68 | ├── static // 静态资源(ui、html页面) 69 | │ 70 | ├── test 71 | │ 72 | ├── index.html 73 | └── package.json 74 | ``` 75 | 76 | ## 页面跳转 77 | 78 | 我在mixins中加入了jump方法,用于支持原生的跳转和vue-router的跳转。 79 | 你可以查看`src/main.js` 80 | ``` js 81 | import mixins from './mixins'; 82 | Vue.mixin(mixins); 83 | ``` 84 | 85 | jump方法有url和replace两个参数。 86 | url代表跳转的路径,如果是一个object或者是一个不包含`http`的字符串,则使用vue-router跳转。如果url是一个带`http`的字符串则使用原生跳转。 87 | replace代表是否替换当前页面,默认是false。 88 | 89 | 90 | 你可以直接在method中使用`this.jump(url)`,或者在html上直接使用`@click.prevent="jump('/url')"`。 91 | 92 | ## 发送 ajax 请求 93 | 94 | 由于我非常非常懒,并且觉得`axios`名字比较奇怪,因此利用`VUX`直接把`axios`封装成插件,你可以直接引用插件。 95 | 96 | 你可以查看`src/main.js` 97 | ``` js 98 | import AjaxPlugin from './plugins/ajax'; 99 | Vue.use(AjaxPlugin); 100 | ``` 101 | 102 | 然后你就可以愉快的偷懒了,使用`this.$http`进行调用了。 103 | ``` js 104 | export default { 105 | name: 'hello', 106 | mounted () { 107 | this.$http(url, {}) 108 | .then(data => {}) 109 | .catch(error => {}); 110 | } 111 | }; 112 | ``` 113 | 由于我自己项目的需要,所以在`src/plugins/ajax/http.js`中对axios做了封装,你可以根据自己情况和喜好进行修改。 114 | 115 | ## 在dev中和express通讯 116 | 请在`config/index.js`的proxyTable中设置代理,比如 117 | ```js 118 | proxyTable: { 119 | '/customer': 'http://localhost:10091' 120 | } 121 | ``` -------------------------------------------------------------------------------- /template/build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = {{#if_or unit e2e}}process.env.NODE_ENV === 'testing' 14 | ? require('./webpack.prod.conf') 15 | : {{/if_or}}require('./webpack.dev.conf') 16 | 17 | // default port where dev server listens for incoming traffic 18 | var port = process.env.PORT || config.dev.port 19 | // automatically open browser, if not set will be false 20 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 21 | // Define HTTP proxies to your custom API backend 22 | // https://github.com/chimurai/http-proxy-middleware 23 | var proxyTable = config.dev.proxyTable 24 | 25 | var app = express() 26 | var compiler = webpack(webpackConfig) 27 | 28 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 29 | publicPath: webpackConfig.output.publicPath, 30 | quiet: true 31 | }) 32 | 33 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 34 | log: () => {}, 35 | heartbeat: 2000 36 | }) 37 | // force page reload when html-webpack-plugin template changes 38 | compiler.plugin('compilation', function (compilation) { 39 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 40 | hotMiddleware.publish({ action: 'reload' }) 41 | cb() 42 | }) 43 | }) 44 | 45 | // proxy api requests 46 | Object.keys(proxyTable).forEach(function (context) { 47 | var options = proxyTable[context] 48 | if (typeof options === 'string') { 49 | options = { target: options } 50 | } 51 | app.use(proxyMiddleware(options.filter || context, options)) 52 | }) 53 | 54 | // handle fallback for HTML5 history API 55 | app.use(require('connect-history-api-fallback')()) 56 | 57 | // serve webpack bundle output 58 | app.use(devMiddleware) 59 | 60 | // enable hot-reload and state-preserving 61 | // compilation error display 62 | app.use(hotMiddleware) 63 | 64 | // serve pure static assets 65 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 66 | app.use(staticPath, express.static('./static')) 67 | 68 | var uri = 'http://localhost:' + port 69 | 70 | var _resolve 71 | var readyPromise = new Promise(resolve => { 72 | _resolve = resolve 73 | }) 74 | 75 | console.log('> Starting dev server...') 76 | devMiddleware.waitUntilValid(() => { 77 | console.log('> Listening at ' + uri + '\n') 78 | // when env is testing, don't need open it 79 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 80 | opn(uri) 81 | } 82 | _resolve() 83 | }) 84 | 85 | var server = app.listen(port) 86 | 87 | module.exports = { 88 | ready: readyPromise, 89 | close: () => { 90 | server.close() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /docs/backend.md: -------------------------------------------------------------------------------- 1 | # Integrating with Backend Framework 2 | 3 | If you are building a purely-static app (one that is deployed separately from the backend API), then you probably don't even need to edit `config/index.js`. However, if you want to integrate this template with an existing backend framework, e.g. Rails/Django/Laravel, which comes with their own project structures, you can edit `config/index.js` to directly generate front-end assets into your backend project. 4 | 5 | Let's take a look at the default `config/index.js`: 6 | 7 | ``` js 8 | var path = require('path') 9 | 10 | module.exports = { 11 | build: { 12 | index: path.resolve(__dirname, 'dist/index.html'), 13 | assetsRoot: path.resolve(__dirname, 'dist'), 14 | assetsSubDirectory: 'static', 15 | assetsPublicPath: '/', 16 | productionSourceMap: true 17 | }, 18 | dev: { 19 | port: 8080, 20 | proxyTable: {} 21 | } 22 | } 23 | ``` 24 | 25 | Inside the `build` section, we have the following options: 26 | 27 | ### `build.index` 28 | 29 | > Must be an absolute path on your local file system. 30 | 31 | This is where the `index.html` (with injected asset URLs) will be generated. 32 | 33 | If you are using this template with a backend-framework, you can edit `index.html` accordingly and point this path to a view file rendered by your backend app, e.g. `app/views/layouts/application.html.erb` for a Rails app, or `resources/views/index.blade.php` for a Laravel app. 34 | 35 | ### `build.assetsRoot` 36 | 37 | > Must be an absolute path on your local file system. 38 | 39 | This should point to the root directory that contains all the static assets for your app. For example, `public/` for both Rails/Laravel. 40 | 41 | ### `build.assetsSubDirectory` 42 | 43 | Nest webpack-generated assets under this directory in `build.assetsRoot`, so that they are not mixed with other files you may have in `build.assetsRoot`. For example, if `build.assetsRoot` is `/path/to/dist`, and `build.assetsSubDirectory` is `static`, then all Webpack assets will be generated in `path/to/dist/static`. 44 | 45 | This directory will be cleaned before each build, so it should only contain assets generated by the build. 46 | 47 | Files inside `static/` will be copied into this directory as-is during build. This means if you change this prefix, all your absolute URLs referencing files in `static/` will also need to be changed. See [Handling Static Assets](static.md) for more details. 48 | 49 | ### `build.assetsPublicPath` 50 | 51 | This should be the URL path where your `build.assetsRoot` will be served from over HTTP. In most cases, this will be root (`/`). Only change this if your backend framework serves static assets with a path prefix. Internally, this is passed to Webpack as `output.publicPath`. 52 | 53 | ### `build.productionSourceMap` 54 | 55 | Whether to generate source maps for production build. 56 | 57 | ### `dev.port` 58 | 59 | Specify the port for the dev server to listen to. 60 | 61 | ### `dev.proxyTable` 62 | 63 | Define proxy rules for the dev server. See [API Proxying During Development](proxy.md) for more details. 64 | -------------------------------------------------------------------------------- /docs/structure.md: -------------------------------------------------------------------------------- 1 | # Project Structure 2 | 3 | ``` bash 4 | . 5 | ├── build/ # webpack config files 6 | │ └── ... 7 | ├── config/ 8 | │   ├── index.js # main project config 9 | │ └── ... 10 | ├── src/ 11 | │   ├── main.js # app entry file 12 | │   ├── App.vue # main app component 13 | │   ├── components/ # ui components 14 | │   │   └── ... 15 | │   └── assets/ # module assets (processed by webpack) 16 | │      └── ... 17 | ├── static/ # pure static assets (directly copied) 18 | ├── test/ 19 | │ └── unit/ # unit tests 20 | │ │   ├── specs/ # test spec files 21 | │ │   ├── index.js # test build entry file 22 | │ │   └── karma.conf.js # test runner config file 23 | │ └── e2e/ # e2e tests 24 | │ │   ├── specs/ # test spec files 25 | │ │   ├── custom-assertions/ # custom assertions for e2e tests 26 | │ │   ├── runner.js # test runner script 27 | │ │   └── nightwatch.conf.js # test runner config file 28 | ├── .babelrc # babel config 29 | ├── .postcssrc.js # postcss config 30 | ├── .eslintrc.js # eslint config 31 | ├── .editorconfig # editor config 32 | ├── index.html # index.html template 33 | └── package.json # build scripts and dependencies 34 | ``` 35 | 36 | ### `build/` 37 | 38 | This directory holds the actual configurations for both the development server and the production webpack build. Normally you don't need to touch these files unless you want to customize Webpack loaders, in which case you should probably look at `build/webpack.base.conf.js`. 39 | 40 | ### `config/index.js` 41 | 42 | This is the main configuration file that exposes some of the most common configuration options for the build setup. See [API Proxying During Development](proxy.md) and [Integrating with Backend Framework](backend.md) for more details. 43 | 44 | ### `src/` 45 | 46 | This is where most of your application code will live in. How to structure everything inside this directory is largely up to you; if you are using Vuex, you can consult the [recommendations for Vuex applications](http://vuex.vuejs.org/en/structure.html). 47 | 48 | ### `static/` 49 | 50 | This directory is an escape hatch for static assets that you do not want to process with Webpack. They will be directly copied into the same directory where webpack-built assets are generated. 51 | 52 | See [Handling Static Assets](static.md) for more details. 53 | 54 | ### `test/unit` 55 | 56 | Contains unit test related files. See [Unit Testing](unit.md) for more details. 57 | 58 | ### `test/e2e` 59 | 60 | Contains e2e test related files. See [End-to-end Testing](e2e.md) for more details. 61 | 62 | ### `index.html` 63 | 64 | This is the **template** `index.html` for our single page application. During development and builds, Webpack will generate assets, and the URLs for those generated assets will be automatically injected into this template to render the final HTML. 65 | 66 | ### `package.json` 67 | 68 | The NPM package meta file that contains all the build dependencies and [build commands](commands.md). 69 | -------------------------------------------------------------------------------- /docs/static.md: -------------------------------------------------------------------------------- 1 | # Handing Static Assets 2 | 3 | You will notice in the project structure we have two directories for static assets: `src/assets` and `static/`. What is the difference between them? 4 | 5 | ### Webpacked Assets 6 | 7 | To answer this question, we first need to understand how Webpack deals with static assets. In `*.vue` components, all your templates and CSS are parsed by `vue-html-loader` and `css-loader` to look for asset URLs. For example, in `` and `background: url(./logo.png)`, `"./logo.png"` is a relative asset path and will be **resolved by Webpack as a module dependency**. 8 | 9 | Because `logo.png` is not JavaScript, when treated as a module dependency, we need to use `url-loader` and `file-loader` to process it. This boilerplate has already configured these loaders for you, so you basically get features such as filename fingerprinting and conditional base64 inlining for free, while being able to use relative/module paths without worrying about deployment. 10 | 11 | Since these assets may be inlined/copied/renamed during build, they are essentially part of your source code. This is why it is recommended to place Webpack-processed static assets inside `/src`, along side other source files. In fact, you don't even have to put them all in `/src/assets`: you can organize them based on the module/component using them. For example, you can put each component in its own directory, with its static assets right next to it. 12 | 13 | ### Asset Resolving Rules 14 | 15 | - **Relative URLs**, e.g. `./assets/logo.png` will be interpreted as a module dependency. They will be replaced with an auto-generated URL based on your Webpack output configuration. 16 | 17 | - **Non-prefixed URLs**, e.g. `assets/logo.png` will be treated the same as the relative URLs and translated into `./assets/logo.png`. 18 | 19 | - **URLs prefixed with `~`** are treated as a module request, similar to `require('some-module/image.png')`. You need to use this prefix if you want to leverage Webpack's module resolving configurations. For example if you have a resolve alias for `assets`, you need to use `` to ensure that alias is respected. 20 | 21 | - **Root-relative URLs**, e.g. `/assets/logo.png` are not processed at all. 22 | 23 | ### Getting Asset Paths in JavaScript 24 | 25 | In order for Webpack to return the correct asset paths, you need to use `require('./relative/path/to/file.jpg')`, which will get processed by `file-loader` and returns the resolved URL. For example: 26 | 27 | ``` js 28 | computed: { 29 | background () { 30 | return require('./bgs/' + this.id + '.jpg') 31 | } 32 | } 33 | ``` 34 | 35 | **Note the above example will include every image under `./bgs/` in the final build.** This is because Webpack cannot guess which of them will be used at runtime, so it includes them all. 36 | 37 | ### "Real" Static Assets 38 | 39 | In comparison, files in `static/` are not processed by Webpack at all: they are directly copied to their final destination as-is, with the same filename. You must reference these files using absolute paths, which is determined by joining `build.assetsPublicPath` and `build.assetsSubDirectory` in `config.js`. 40 | 41 | As an example, with the following default values: 42 | 43 | ``` js 44 | // config.js 45 | module.exports = { 46 | // ... 47 | build: { 48 | assetsPublicPath: '/', 49 | assetsSubDirectory: 'static' 50 | } 51 | } 52 | ``` 53 | 54 | Any file placed in `static/` should be referenced using the absolute URL `/static/[filename]`. If you change `assetSubDirectory` to `assets`, then these URLs will need to be changed to `/assets/[filename]`. 55 | 56 | We will learn more about the config file in the section about [backend integration](backend.md). 57 | -------------------------------------------------------------------------------- /template/src/assets/js/tool.js: -------------------------------------------------------------------------------- 1 | // 通用方法函数集 2 | /*** 3 | * 设备检查 4 | * @returns {*} 5 | */ 6 | 7 | export function checkDevice() { 8 | const ua = navigator.userAgent; 9 | const isAndroid = /(Android);?[\s/]+([\d.]+)?/.test(ua); 10 | const isIpad = /(iPad).*OS\s([\d_]+)/.test(ua); 11 | const isIpod = /(iPod)(.*OS\s([\d_]+))?/.test(ua); 12 | const isIphone = !isIpad && /(iPhone\sOS)\s([\d_]+)/.test(ua); 13 | const isWechat = /micromessenger/i.test(ua); 14 | 15 | return { 16 | isAndroid, 17 | isIpad, 18 | isIpod, 19 | isIphone, 20 | isWechat 21 | }; 22 | } 23 | 24 | /*** 25 | * 检查手机号 26 | * @param phone 27 | * @returns boolean 28 | */ 29 | export function checkMobile(phone) { // 检查手机号 30 | let re = /^1\d{10}$/; 31 | return re.test(phone); 32 | } 33 | 34 | /*** 35 | * 获取url中'?'符后的字串 36 | * @param location 37 | * @returns {*} 38 | */ 39 | export function getSearch(location) { 40 | let url = location.search; // 41 | let searchObj = {}; 42 | if (url.indexOf('?') !== -1) { 43 | var str = url.substr(1); 44 | var strs = str.split('&'); 45 | for (var i = 0; i < strs.length; i++) { 46 | searchObj[strs[i].split('=')[0]] = unescape(strs[i].split('=')[1]); 47 | } 48 | } 49 | return searchObj; 50 | } 51 | 52 | /*** 53 | * 是否为正整数 54 | * @param s 55 | * @returns boolean 56 | */ 57 | export function isPositiveInteger(s) { // 是否为正整数 58 | let re = /^[0-9]*[1-9][0-9]*$/; 59 | return re.test(s); 60 | } 61 | 62 | /*** 63 | * 去除字符串空格和替换回车换行 64 | * @param str 65 | * @param replaceBreak 66 | * @returns string 67 | */ 68 | export function stringTrim(str, replaceBreak) { 69 | str = str.replace(/^\s\s*/, ''); 70 | let ws = /\s/; 71 | let i = str.length; 72 | let rs = ''; 73 | while (ws.test(str.charAt(--i))) { 74 | rs = str.slice(0, i + 1); 75 | } 76 | if (!rs) { 77 | return ''; 78 | } 79 | if (!replaceBreak) { 80 | return rs; 81 | } else { 82 | return rs.replace(/(?:\r\n|\r|\n)/g, ''); 83 | } 84 | } 85 | 86 | /*** 87 | * 获取浏览器缓存 88 | * @param key 89 | * @param cacheType 90 | * @param dataType 91 | * @returns string 92 | */ 93 | export function getCache(key, cacheType = 'l', dataType = 'json') { 94 | let _position = cacheType === 's' ? 'sessionStorage' : 'localStorage'; 95 | let _data = window[_position].getItem(key); 96 | if (dataType === 'json' && _data) { 97 | // 存在字符串null的情况 98 | return _data === 'null' ? null : JSON.parse(_data); 99 | } 100 | return _data; 101 | } 102 | 103 | /*** 104 | * 设置浏览器缓存 setCache('syj2-xx',{},'s' or 'l' or undefined) 105 | * @param key 106 | * @param data 107 | * @param cacheType 108 | */ 109 | export function setCache(key, data, cacheType = 'l') { 110 | let _position = cacheType === 's' ? 'sessionStorage' : 'localStorage'; 111 | // 浏览器不支持 112 | if (window[_position]) { 113 | try { 114 | // IOS-5手机浏览器支持cache,但是奇葩的是储存容量为几kb,可能发生错误 115 | window[_position].setItem(key, typeof data === 'object' ? JSON.stringify(data) : data); 116 | } catch (e) { 117 | console.error('缓存数据时发生错误:', e); 118 | } 119 | } else { 120 | console.error(`您的浏览器不支持${_position}`); 121 | } 122 | } 123 | 124 | /*** 125 | * 清除缓存根据key 126 | * @param key 127 | * @param data 128 | * @param cacheType 129 | */ 130 | export function clearCache(key, cacheType = 'l') { 131 | let _position = cacheType === 's' ? 'sessionStorage' : 'localStorage'; 132 | window[_position].removeItem(key); 133 | } 134 | 135 | /*** 136 | * 导出 excel 137 | * @param url 138 | * @param param 139 | */ 140 | export function exportExcel(url, param) { 141 | let queryString = ''; 142 | for (let key in param) { 143 | queryString += `${key}=${typeof param[key] === 'object' ? JSON.stringify(param[key]) : param[key]}&`; 144 | } 145 | window.open(url + '?' + queryString); 146 | } 147 | -------------------------------------------------------------------------------- /template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ name }}", 3 | "version": "1.0.0", 4 | "description": "{{ description }}", 5 | "author": "{{ author }}", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "build": "node build/build.js"{{#unit}}, 10 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run"{{/unit}}{{#e2e}}, 11 | "e2e": "node test/e2e/runner.js"{{/e2e}}{{#if_or unit e2e}}, 12 | "test": "{{#unit}}npm run unit{{/unit}}{{#unit}}{{#e2e}} && {{/e2e}}{{/unit}}{{#e2e}}npm run e2e{{/e2e}}"{{/if_or}}{{#lint}}, 13 | "lint": "eslint --ext .js,.vue src{{#unit}} test/unit/specs{{/unit}}{{#e2e}} test/e2e/specs{{/e2e}}"{{/lint}} 14 | }, 15 | "dependencies": { 16 | "axios": "^0.16.2", 17 | "formidable": "^1.1.1", 18 | "vue": "^2.4.2", 19 | "vue-lazyload": "^1.0.6", 20 | "vue-router": "^2.7.0", 21 | "vuex": "^2.3.1", 22 | "vue-wechat-title": "^2.0.4" 23 | }, 24 | "devDependencies": { 25 | "autoprefixer": "^6.7.7", 26 | "babel-core": "^6.25.0", 27 | {{#lint}} 28 | "babel-eslint": "^7.2.3", 29 | {{/lint}} 30 | "babel-loader": "^7.1.1", 31 | "babel-plugin-transform-runtime": "^6.23.0", 32 | "babel-polyfill": "^6.23.0", 33 | "babel-preset-es2015": "^6.24.1", 34 | "babel-preset-env": "^1.6.0", 35 | "babel-preset-stage-2": "^6.24.1", 36 | "babel-register": "^6.24.1", 37 | "chalk": "^1.1.3", 38 | "connect-history-api-fallback": "^1.3.0", 39 | "copy-webpack-plugin": "^4.0.1", 40 | "css-loader": "^0.26.4", 41 | {{#lint}} 42 | "eslint": "^3.19.0", 43 | "eslint-friendly-formatter": "^2.0.7", 44 | "eslint-loader": "^1.9.0", 45 | "eslint-plugin-html": "^2.0.3", 46 | {{#if_eq lintConfig "standard"}} 47 | "eslint-config-standard": "^6.2.1", 48 | "eslint-plugin-promise": "^3.5.0", 49 | "eslint-plugin-standard": "^2.3.1", 50 | {{/if_eq}} 51 | {{#if_eq lintConfig "airbnb"}} 52 | "eslint-config-airbnb-base": "^11.0.1", 53 | "eslint-import-resolver-webpack": "^0.8.1", 54 | "eslint-plugin-import": "^2.2.0", 55 | {{/if_eq}} 56 | {{/lint}} 57 | "eventsource-polyfill": "^0.9.6", 58 | "extract-text-webpack-plugin": "^3.0.0", 59 | "file-loader": "^0.10.1", 60 | "friendly-errors-webpack-plugin": "^1.6.1", 61 | "function-bind": "^1.1.0", 62 | "html-webpack-plugin": "^2.29.0", 63 | "http-proxy-middleware": "^0.17.4", 64 | "webpack-bundle-analyzer": "^2.2.1", 65 | {{#unit}} 66 | "cross-env": "^3.2.4", 67 | "karma": "^1.7.0", 68 | "karma-coverage": "^1.1.1", 69 | "karma-mocha": "^1.3.0", 70 | "karma-phantomjs-launcher": "^1.0.4", 71 | "karma-sinon-chai": "^1.3.1", 72 | "karma-sourcemap-loader": "^0.3.7", 73 | "karma-spec-reporter": "0.0.26", 74 | "karma-webpack": "^2.0.4", 75 | "less": "^2.7.2", 76 | "less-loader": "^4.0.5", 77 | "lolex": "^1.6.0", 78 | "mocha": "^3.4.2", 79 | "chai": "^3.5.0", 80 | "sinon": "^2.2.1", 81 | "sinon-chai": "^2.12.0", 82 | "inject-loader": "^2.0.1", 83 | "babel-plugin-istanbul": "^3.1.2", 84 | "phantomjs-prebuilt": "^2.1.14", 85 | {{/unit}} 86 | {{#e2e}} 87 | "chromedriver": "^2.31.0", 88 | "cross-spawn": "^5.1.0", 89 | "nightwatch": "^0.9.16", 90 | "selenium-server": "^3.4.0", 91 | {{/e2e}} 92 | "semver": "^5.4.1", 93 | "opn": "^4.0.2", 94 | "optimize-css-assets-webpack-plugin": "^1.3.2", 95 | "ora": "^1.3.0", 96 | "rimraf": "^2.6.1", 97 | "url-loader": "^0.5.9", 98 | "vue-loader": "^11.3.4", 99 | "vue-style-loader": "^2.0.5", 100 | "vue-template-compiler": "^2.4.2", 101 | "webpack": "^3.4.1", 102 | "webpack-bundle-analyzer": "^2.8.3", 103 | "webpack-dev-middleware": "^1.11.0", 104 | "webpack-hot-middleware": "^2.18.2", 105 | "webpack-merge": "^4.1.0", 106 | "webpack-uglify-parallel": "^0.1.3" 107 | }, 108 | "engines": { 109 | "node": ">= 4.0.0", 110 | "npm": ">= 3.0.0" 111 | }, 112 | "browserslist": [ 113 | "> 1%", 114 | "last 2 versions", 115 | "not ie <= 8" 116 | ] 117 | } 118 | -------------------------------------------------------------------------------- /template/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = {{#if_or unit e2e}}process.env.NODE_ENV === 'testing' 13 | ? require('../config/test.env') 14 | : {{/if_or}}config.build.env 15 | 16 | var webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true 21 | }) 22 | }, 23 | devtool: config.build.productionSourceMap ? '#source-map' : false, 24 | output: { 25 | path: config.build.assetsRoot, 26 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.optimize.ModuleConcatenationPlugin(), 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new webpack.optimize.UglifyJsPlugin({ 36 | compress: { 37 | warnings: false 38 | }, 39 | sourceMap: true 40 | }), 41 | // extract css into its own file 42 | new ExtractTextPlugin({ 43 | filename: utils.assetsPath('css/[name].[contenthash].css') 44 | }), 45 | // Compress extracted CSS. We are using this plugin so that possible 46 | // duplicated CSS from different components can be deduped. 47 | new OptimizeCSSPlugin({ 48 | cssProcessorOptions: { 49 | safe: true 50 | } 51 | }), 52 | // generate dist index.html with correct asset hash for caching. 53 | // you can customize output by editing /index.html 54 | // see https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: {{#if_or unit e2e}}process.env.NODE_ENV === 'testing' 57 | ? 'index.html' 58 | : {{/if_or}}config.build.index, 59 | template: 'index.html', 60 | inject: true, 61 | minify: { 62 | removeComments: true, 63 | collapseWhitespace: true, 64 | removeAttributeQuotes: true 65 | // more options: 66 | // https://github.com/kangax/html-minifier#options-quick-reference 67 | }, 68 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 69 | chunksSortMode: 'dependency' 70 | }), 71 | // split vendor js into its own file 72 | new webpack.optimize.CommonsChunkPlugin({ 73 | name: 'vendor', 74 | minChunks: function (module, count) { 75 | // any required modules inside node_modules are extracted to vendor 76 | return ( 77 | module.resource && 78 | /\.js$/.test(module.resource) && 79 | module.resource.indexOf( 80 | path.join(__dirname, '../node_modules') 81 | ) === 0 82 | ) 83 | } 84 | }), 85 | // extract webpack runtime and module manifest to its own file in order to 86 | // prevent vendor hash from being updated whenever app bundle is updated 87 | new webpack.optimize.CommonsChunkPlugin({ 88 | name: 'manifest', 89 | chunks: ['vendor'] 90 | }), 91 | // copy custom static assets 92 | new CopyWebpackPlugin([ 93 | { 94 | from: path.resolve(__dirname, '../static'), 95 | to: config.build.assetsSubDirectory, 96 | ignore: ['.*'] 97 | } 98 | ]) 99 | ] 100 | }) 101 | 102 | if (config.build.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.build.productionGzipExtensions.join('|') + 112 | ')$' 113 | ), 114 | threshold: 10240, 115 | minRatio: 0.8 116 | }) 117 | ) 118 | } 119 | 120 | if (config.build.bundleAnalyzerReport) { 121 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 122 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 123 | } 124 | 125 | module.exports = webpackConfig 126 | -------------------------------------------------------------------------------- /template/src/assets/js/date.js: -------------------------------------------------------------------------------- 1 | // 时间处理函数集 2 | 3 | /*** 4 | * 格式化时间 5 | * @param date 6 | * @param fmt 7 | * @returns {*} 8 | */ 9 | export function dateFormat(date, fmt) { 10 | let o = { 11 | 'M+': date.getMonth() + 1, //月份 12 | 'd+': date.getDate(), //日 13 | 'h+': date.getHours(), //小时 14 | 'm+': date.getMinutes(), //分 15 | 's+': date.getSeconds(), //秒 16 | 'q+': Math.floor((date.getMonth() + 3) / 3), //季度 17 | 'S': date.getMilliseconds() //毫秒 18 | }; 19 | if (/(y+)/.test(fmt)) { 20 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); 21 | } 22 | for (let k in o) { 23 | if (new RegExp('(' + k + ')').test(fmt)) { 24 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))); 25 | } 26 | } 27 | return fmt; 28 | }; 29 | 30 | /*** 31 | * 格式化字符串时间 32 | * @param strDate 33 | * @param strFormat 34 | * @returns {*} 35 | */ 36 | export function dateStrFormat(strDate, strFormat) { 37 | let tmpDate = new Date(); 38 | let longTime = Date.parse(strDate); 39 | if (isNaN(longTime)) { //IE兼容 40 | let dates = strDate.split(' ')[0].split('-'); 41 | let times = strDate.split(' ')[1].split(':'); 42 | 43 | if (strFormat.indexOf('yyyy') > -1) { 44 | tmpDate.setFullYear(parseInt(dates[0], 10)); 45 | } 46 | if (strFormat.indexOf('MM') > -1) { 47 | tmpDate.setMonth(parseInt(dates[1], 10) - 1); 48 | } 49 | if (strFormat.indexOf('dd') > -1) { 50 | tmpDate.setDate(parseInt(dates[2], 10)); 51 | } 52 | if (strFormat.indexOf('hh') > -1) { 53 | tmpDate.setHours(parseInt(times[0], 10)); 54 | } 55 | if (strFormat.indexOf('mm') > -1) { 56 | tmpDate.setMinutes(parseInt(times[1], 10)); 57 | } 58 | if (strFormat.indexOf('ss') > -1) { 59 | tmpDate.setSeconds(parseInt(times[2], 10)); 60 | } 61 | } else { 62 | tmpDate = new Date(longTime); 63 | } 64 | return dateFormat(tmpDate, strFormat); 65 | }; 66 | 67 | /*** 68 | * 获得本周起止时间 69 | */ 70 | export function getCurrentWeek() { 71 | //起止日期数组 72 | let startStop = new Array(); 73 | //获取当前时间 74 | let currentDate = new Date(); 75 | //返回date是一周中的某一天 76 | let week = currentDate.getDay(); 77 | //返回date是一个月中的某一天 78 | let month = currentDate.getDate(); 79 | 80 | //一天的毫秒数 81 | let millisecond = 1000 * 60 * 60 * 24; 82 | //减去的天数 83 | let minusDay = week !== 0 ? week - 1 : 6; 84 | //alert(minusDay); 85 | //本周 周一 86 | let monday = new Date(currentDate.getTime() - (minusDay * millisecond)); 87 | //本周 周日 88 | let sunday = new Date(monday.getTime() + (6 * millisecond)); 89 | //添加本周时间 90 | startStop.push(dateFormat(monday, 'yyyy-MM-dd'));//本周起始时间 91 | //添加本周最后一天时间 92 | startStop.push(dateFormat(sunday, 'yyyy-MM-dd'));//本周终止时间 93 | //返回 94 | return startStop; 95 | }; 96 | 97 | /*** 98 | * 获得本月的起止时间 99 | */ 100 | export function getCurrentMonth() { 101 | //起止日期数组 102 | let startStop = new Array(); 103 | //获取当前时间 104 | let currentDate = new Date(); 105 | //获得当前月份0-11 106 | let currentMonth = currentDate.getMonth(); 107 | //获得当前年份4位年 108 | let currentYear = currentDate.getFullYear(); 109 | //求出本月第一天 110 | let firstDay = new Date(currentYear, currentMonth, 1); 111 | 112 | //当为12月的时候年份需要加1 113 | //月份需要更新为0 也就是下一年的第一个月 114 | if (currentMonth === 11) { 115 | currentYear++; 116 | currentMonth = 0;//就为 117 | } else { 118 | //否则只是月份增加,以便求的下一月的第一天 119 | currentMonth++; 120 | } 121 | //一天的毫秒数 122 | let millisecond = 1000 * 60 * 60 * 24; 123 | //下月的第一天 124 | let nextMonthDayOne = new Date(currentYear, currentMonth, 1); 125 | //求出上月的最后一天 126 | let lastDay = new Date(nextMonthDayOne.getTime() - millisecond); 127 | 128 | //添加至数组中返回 129 | startStop.push(dateFormat(firstDay, 'yyyy-MM-dd')); 130 | startStop.push(dateFormat(lastDay, 'yyyy-MM-dd')); 131 | //返回 132 | return startStop; 133 | }; 134 | 135 | /** 136 | * 得到本季度开始的月份 137 | * @param month 需要计算的月份 138 | ***/ 139 | export function getQuarterSeasonStartMonth(month) { 140 | let quarterMonthStart = 0; 141 | let spring = 0; //春 142 | let summer = 3; //夏 143 | let fall = 6; //秋 144 | let winter = 9;//冬 145 | //月份从0-11 146 | if (month < 3) { 147 | return spring; 148 | } 149 | if (month < 6) { 150 | return summer; 151 | } 152 | if (month < 9) { 153 | return fall; 154 | } 155 | return winter; 156 | }; 157 | 158 | /** 159 | * 获得该月的天数 160 | * @param year年份 161 | * @param month月份 162 | * */ 163 | export function getMonthDays(year, month) { 164 | //本月第一天 1-31 165 | let relativeDate = new Date(year, month, 1); 166 | //获得当前月份0-11 167 | let relativeMonth = relativeDate.getMonth(); 168 | //获得当前年份4位年 169 | let relativeYear = relativeDate.getFullYear(); 170 | 171 | //当为12月的时候年份需要加1 172 | //月份需要更新为0 也就是下一年的第一个月 173 | if (relativeMonth === 11) { 174 | relativeYear++; 175 | relativeMonth = 0; 176 | } else { 177 | //否则只是月份增加,以便求的下一月的第一天 178 | relativeMonth++; 179 | } 180 | //一天的毫秒数 181 | let millisecond = 1000 * 60 * 60 * 24; 182 | //下月的第一天 183 | let nextMonthDayOne = new Date(relativeYear, relativeMonth, 1); 184 | //返回得到上月的最后一天,也就是本月总天数 185 | return new Date(nextMonthDayOne.getTime() - millisecond).getDate(); 186 | }; 187 | 188 | /** 189 | * 获得本季度的起止日期 190 | */ 191 | export function getCurrentSeason() { 192 | //起止日期数组 193 | let startStop = new Array(); 194 | //获取当前时间 195 | let currentDate = new Date(); 196 | //获得当前月份0-11 197 | let currentMonth = currentDate.getMonth(); 198 | //获得当前年份4位年 199 | let currentYear = currentDate.getFullYear(); 200 | //获得本季度开始月份 201 | let quarterSeasonStartMonth = getQuarterSeasonStartMonth(currentMonth); 202 | //获得本季度结束月份 203 | let quarterSeasonEndMonth = quarterSeasonStartMonth + 2; 204 | 205 | //获得本季度开始的日期 206 | let quarterSeasonStartDate = new Date(currentYear, quarterSeasonStartMonth, 1); 207 | //获得本季度结束的日期 208 | let quarterSeasonEndDate = new Date(currentYear, quarterSeasonEndMonth, getMonthDays(currentYear, quarterSeasonEndMonth)); 209 | //加入数组返回 210 | startStop.push(dateFormat(quarterSeasonStartDate, 'yyyy-MM-dd')); 211 | startStop.push(dateFormat(quarterSeasonEndDate, 'yyyy-MM-dd')); 212 | //返回 213 | return startStop; 214 | }; 215 | 216 | /*** 217 | * 得到本年的起止日期 218 | * 219 | */ 220 | export function getCurrentYear() { 221 | //起止日期数组 222 | let startStop = new Array(); 223 | //获取当前时间 224 | let currentDate = new Date(); 225 | //获得当前年份4位年 226 | let currentYear = currentDate.getFullYear(); 227 | 228 | //本年第一天 229 | let currentYearFirstDate = new Date(currentYear, 0, 1); 230 | //本年最后一天 231 | let currentYearLastDate = new Date(currentYear, 11, 31); 232 | //添加至数组 233 | startStop.push(dateFormat(currentYearFirstDate, 'yyyy-MM-dd')); 234 | startStop.push(dateFormat(currentYearLastDate, 'yyyy-MM-dd')); 235 | //返回 236 | return startStop; 237 | }; 238 | 239 | /** 240 | * 返回上一个月的第一天Date类型 241 | * @param year 年 242 | * @param month 月 243 | **/ 244 | export function getPriorMonthFirstDay(year, month) { 245 | //年份为0代表,是本年的第一月,所以不能减 246 | if (month === 0) { 247 | month = 11;//月份为上年的最后月份 248 | year--;//年份减1 249 | return new Date(year, month, 1); 250 | } 251 | //否则,只减去月份 252 | month--; 253 | return new Date(year, month, 1); 254 | ; 255 | }; 256 | 257 | /** 258 | * 获得上一月的起止日期 259 | * ***/ 260 | export function getPreviousMonth() { 261 | //起止日期数组 262 | let startStop = new Array(); 263 | //获取当前时间 264 | let currentDate = new Date(); 265 | //获得当前月份0-11 266 | let currentMonth = currentDate.getMonth(); 267 | //获得当前年份4位年 268 | let currentYear = currentDate.getFullYear(); 269 | //获得上一个月的第一天 270 | let priorMonthFirstDay = getPriorMonthFirstDay(currentYear, currentMonth); 271 | //获得上一月的最后一天 272 | let priorMonthLastDay = new Date(priorMonthFirstDay.getFullYear(), priorMonthFirstDay.getMonth(), getMonthDays(priorMonthFirstDay.getFullYear(), priorMonthFirstDay.getMonth())); 273 | //添加至数组 274 | startStop.push(dateFormat(priorMonthFirstDay, 'yyyy-MM-dd')); 275 | startStop.push(dateFormat(priorMonthLastDay, 'yyyy-MM-dd')); 276 | //返回 277 | return startStop; 278 | }; 279 | 280 | 281 | /** 282 | * 获得上一周的起止日期 283 | * **/ 284 | export function getPreviousWeek() { 285 | //起止日期数组 286 | let startStop = new Array(); 287 | //获取当前时间 288 | let currentDate = new Date(); 289 | //返回date是一周中的某一天 290 | let week = currentDate.getDay(); 291 | //返回date是一个月中的某一天 292 | let month = currentDate.getDate(); 293 | //一天的毫秒数 294 | let millisecond = 1000 * 60 * 60 * 24; 295 | //减去的天数 296 | let minusDay = week !== 0 ? week - 1 : 6; 297 | //获得当前周的第一天 298 | let currentWeekDayOne = new Date(currentDate.getTime() - (millisecond * minusDay)); 299 | //上周最后一天即本周开始的前一天 300 | let priorWeekLastDay = new Date(currentWeekDayOne.getTime() - millisecond); 301 | //上周的第一天 302 | let priorWeekFirstDay = new Date(priorWeekLastDay.getTime() - (millisecond * 6)); 303 | 304 | //添加至数组 305 | startStop.push(dateFormat(priorWeekFirstDay, 'yyyy-MM-dd')); 306 | startStop.push(dateFormat(priorWeekLastDay, 'yyyy-MM-dd')); 307 | 308 | return startStop; 309 | }; 310 | 311 | /** 312 | * 得到上季度的起始日期 313 | * year 这个年应该是运算后得到的当前本季度的年份 314 | * month 这个应该是运算后得到的当前季度的开始月份 315 | * */ 316 | export function getPriorSeasonFirstDay(year, month) { 317 | let quarterMonthStart = 0; 318 | let spring = 0; //春 319 | let summer = 3; //夏 320 | let fall = 6; //秋 321 | let winter = 9;//冬 322 | //月份从0-11 323 | switch (month) {//季度的其实月份 324 | case spring: 325 | //如果是第一季度则应该到去年的冬季 326 | year--; 327 | month = winter; 328 | break; 329 | case summer: 330 | month = spring; 331 | break; 332 | case fall: 333 | month = summer; 334 | break; 335 | case winter: 336 | month = fall; 337 | break; 338 | } 339 | return new Date(year, month, 1); 340 | }; 341 | 342 | /** 343 | * 得到上季度的起止日期 344 | * **/ 345 | export function getPreviousSeason() { 346 | //起止日期数组 347 | let startStop = new Array(); 348 | //获取当前时间 349 | let currentDate = new Date(); 350 | //获得当前月份0-11 351 | let currentMonth = currentDate.getMonth(); 352 | //获得当前年份4位年 353 | let currentYear = currentDate.getFullYear(); 354 | //上季度的第一天 355 | let priorSeasonFirstDay = getPriorSeasonFirstDay(currentYear, currentMonth); 356 | //上季度的最后一天 357 | let priorSeasonLastDay = new Date(priorSeasonFirstDay.getFullYear(), priorSeasonFirstDay.getMonth() + 2, getMonthDays(priorSeasonFirstDay.getFullYear(), priorSeasonFirstDay.getMonth() + 2)); 358 | //添加至数组 359 | startStop.push(dateFormat(priorSeasonFirstDay, 'yyyy-MM-dd')); 360 | startStop.push(dateFormat(priorSeasonLastDay, 'yyyy-MM-dd')); 361 | return startStop; 362 | }; 363 | 364 | /** 365 | * 得到去年的起止日期 366 | * **/ 367 | export function getPreviousYear() { 368 | //起止日期数组 369 | let startStop = new Array(); 370 | //获取当前时间 371 | let currentDate = new Date(); 372 | //获得当前年份4位年 373 | let currentYear = currentDate.getFullYear(); 374 | currentYear--; 375 | let priorYearFirstDay = new Date(currentYear, 0, 1); 376 | let priorYearLastDay = new Date(currentYear, 11, 1); 377 | //添加至数组 378 | startStop.push(dateFormat(priorYearFirstDay, 'yyyy-MM-dd')); 379 | startStop.push(dateFormat(priorYearLastDay, 'yyyy-MM-dd')); 380 | return startStop; 381 | }; 382 | 383 | /** 384 | * 获取两个日期相差天数 385 | * @param strDateStart 386 | * @param strDateEnd 387 | * @returns {Number|*} 388 | */ 389 | export function getDiffDays(strDateStart, strDateEnd) { 390 | let oDate1; 391 | let oDate2; 392 | let iDays; 393 | oDate1 = strDateStart.split('-'); 394 | oDate2 = strDateEnd.split('-'); 395 | let strDateS = new Date(oDate1[0], oDate1[1] - 1, oDate1[2]); 396 | let strDateE = new Date(oDate2[0], oDate2[1] - 1, oDate2[2]); 397 | iDays = parseInt((strDateS - strDateE) / 3600000 / 24); 398 | return iDays; 399 | }; 400 | 401 | /** 402 | * 指定日期向前多少天 403 | * @param dateStr 404 | * @param days 405 | */ 406 | export function getBeforeDays(dateStr, days) { 407 | let now = new Date(dateStr); 408 | now = new Date((now / 1000 - 86400 * days) * 1000); 409 | return dateFormat(now, 'yyyy-MM-dd'); 410 | }; 411 | -------------------------------------------------------------------------------- /template/src/assets/js/makePY.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tiangestar on 2015-06-05. 3 | */ 4 | // 汉字拼音首字母列表 本列表包含了20902个汉字,用于配合 ToChineseSpell 5 | // 函数使用,本表收录的字符的Unicode编码范围为19968至40869, XDesigner 整理 6 | const strChineseFirstPY = 'YDYQSXMWZSSXJBYMGCCZQPSSQBYCDSCDQLDYLYBSSJGYZZJJFKCCLZDHWDWZJLJPFYYNWJJTMYHZWZHFLZPPQHGSCYYYNJQYXXGJHHSDSJNKKTMOMLCRXYPSNQSECCQZGGLLYJLMYZZSECYKYYHQWJSSGGYXYZYJWWKDJHYCHMYXJTLXJYQBYXZLDWRDJRWYSRLDZJPCBZJJBRCFTLECZSTZFXXZHTRQHYBDLYCZSSYMMRFMYQZPWWJJYFCRWFDFZQPYDDWYXKYJAWJFFXYPSFTZYHHYZYSWCJYXSCLCXXWZZXNBGNNXBXLZSZSBSGPYSYZDHMDZBQBZCWDZZYYTZHBTSYYBZGNTNXQYWQSKBPHHLXGYBFMJEBJHHGQTJCYSXSTKZHLYCKGLYSMZXYALMELDCCXGZYRJXSDLTYZCQKCNNJWHJTZZCQLJSTSTBNXBTYXCEQXGKWJYFLZQLYHYXSPSFXLMPBYSXXXYDJCZYLLLSJXFHJXPJBTFFYABYXBHZZBJYZLWLCZGGBTSSMDTJZXPTHYQTGLJSCQFZKJZJQNLZWLSLHDZBWJNCJZYZSQQYCQYRZCJJWYBRTWPYFTWEXCSKDZCTBZHYZZYYJXZCFFZZMJYXXSDZZOTTBZLQWFCKSZSXFYRLNYJMBDTHJXSQQCCSBXYYTSYFBXDZTGBCNSLCYZZPSAZYZZSCJCSHZQYDXLBPJLLMQXTYDZXSQJTZPXLCGLQTZWJBHCTSYJSFXYEJJTLBGXSXJMYJQQPFZASYJNTYDJXKJCDJSZCBARTDCLYJQMWNQNCLLLKBYBZZSYHQQLTWLCCXTXLLZNTYLNEWYZYXCZXXGRKRMTCNDNJTSYYSSDQDGHSDBJGHRWRQLYBGLXHLGTGXBQJDZPYJSJYJCTMRNYMGRZJCZGJMZMGXMPRYXKJNYMSGMZJYMKMFXMLDTGFBHCJHKYLPFMDXLQJJSMTQGZSJLQDLDGJYCALCMZCSDJLLNXDJFFFFJCZFMZFFPFKHKGDPSXKTACJDHHZDDCRRCFQYJKQCCWJDXHWJLYLLZGCFCQDSMLZPBJJPLSBCJGGDCKKDEZSQCCKJGCGKDJTJDLZYCXKLQSCGJCLTFPCQCZGWPJDQYZJJBYJHSJDZWGFSJGZKQCCZLLPSPKJGQJHZZLJPLGJGJJTHJJYJZCZMLZLYQBGJWMLJKXZDZNJQSYZMLJLLJKYWXMKJLHSKJGBMCLYYMKXJQLBMLLKMDXXKWYXYSLMLPSJQQJQXYXFJTJDXMXXLLCXQBSYJBGWYMBGGBCYXPJYGPEPFGDJGBHBNSQJYZJKJKHXQFGQZKFHYGKHDKLLSDJQXPQYKYBNQSXQNSZSWHBSXWHXWBZZXDMNSJBSBKBBZKLYLXGWXDRWYQZMYWSJQLCJXXJXKJEQXSCYETLZHLYYYSDZPAQYZCMTLSHTZCFYZYXYLJSDCJQAGYSLCQLYYYSHMRQQKLDXZSCSSSYDYCJYSFSJBFRSSZQSBXXPXJYSDRCKGJLGDKZJZBDKTCSYQPYHSTCLDJDHMXMCGXYZHJDDTMHLTXZXYLYMOHYJCLTYFBQQXPFBDFHHTKSQHZYYWCNXXCRWHOWGYJLEGWDQCWGFJYCSNTMYTOLBYGWQWESJPWNMLRYDZSZTXYQPZGCWXHNGPYXSHMYQJXZTDPPBFYHZHTJYFDZWKGKZBLDNTSXHQEEGZZYLZMMZYJZGXZXKHKSTXNXXWYLYAPSTHXDWHZYMPXAGKYDXBHNHXKDPJNMYHYLPMGOCSLNZHKXXLPZZLBMLSFBHHGYGYYGGBHSCYAQTYWLXTZQCEZYDQDQMMHTKLLSZHLSJZWFYHQSWSCWLQAZYNYTLSXTHAZNKZZSZZLAXXZWWCTGQQTDDYZTCCHYQZFLXPSLZYGPZSZNGLNDQTBDLXGTCTAJDKYWNSYZLJHHZZCWNYYZYWMHYCHHYXHJKZWSXHZYXLYSKQYSPSLYZWMYPPKBYGLKZHTYXAXQSYSHXASMCHKDSCRSWJPWXSGZJLWWSCHSJHSQNHCSEGNDAQTBAALZZMSSTDQJCJKTSCJAXPLGGXHHGXXZCXPDMMHLDGTYBYSJMXHMRCPXXJZCKZXSHMLQXXTTHXWZFKHCCZDYTCJYXQHLXDHYPJQXYLSYYDZOZJNYXQEZYSQYAYXWYPDGXDDXSPPYZNDLTWRHXYDXZZJHTCXMCZLHPYYYYMHZLLHNXMYLLLMDCPPXHMXDKYCYRDLTXJCHHZZXZLCCLYLNZSHZJZZLNNRLWHYQSNJHXYNTTTKYJPYCHHYEGKCTTWLGQRLGGTGTYGYHPYHYLQYQGCWYQKPYYYTTTTLHYHLLTYTTSPLKYZXGZWGPYDSSZZDQXSKCQNMJJZZBXYQMJRTFFBTKHZKBXLJJKDXJTLBWFZPPTKQTZTGPDGNTPJYFALQMKGXBDCLZFHZCLLLLADPMXDJHLCCLGYHDZFGYDDGCYYFGYDXKSSEBDHYKDKDKHNAXXYBPBYYHXZQGAFFQYJXDMLJCSQZLLPCHBSXGJYNDYBYQSPZWJLZKSDDTACTBXZDYZYPJZQSJNKKTKNJDJGYYPGTLFYQKASDNTCYHBLWDZHBBYDWJRYGKZYHEYYFJMSDTYFZJJHGCXPLXHLDWXXJKYTCYKSSSMTWCTTQZLPBSZDZWZXGZAGYKTYWXLHLSPBCLLOQMMZSSLCMBJCSZZKYDCZJGQQDSMCYTZQQLWZQZXSSFPTTFQMDDZDSHDTDWFHTDYZJYQJQKYPBDJYYXTLJHDRQXXXHAYDHRJLKLYTWHLLRLLRCXYLBWSRSZZSYMKZZHHKYHXKSMDSYDYCJPBZBSQLFCXXXNXKXWYWSDZYQOGGQMMYHCDZTTFJYYBGSTTTYBYKJDHKYXBELHTYPJQNFXFDYKZHQKZBYJTZBXHFDXKDASWTAWAJLDYJSFHBLDNNTNQJTJNCHXFJSRFWHZFMDRYJYJWZPDJKZYJYMPCYZNYNXFBYTFYFWYGDBNZZZDNYTXZEMMQBSQEHXFZMBMFLZZSRXYMJGSXWZJSPRYDJSJGXHJJGLJJYNZZJXHGXKYMLPYYYCXYTWQZSWHWLYRJLPXSLSXMFSWWKLCTNXNYNPSJSZHDZEPTXMYYWXYYSYWLXJQZQXZDCLEEELMCPJPCLWBXSQHFWWTFFJTNQJHJQDXHWLBYZNFJLALKYYJLDXHHYCSTYYWNRJYXYWTRMDRQHWQCMFJDYZMHMYYXJWMYZQZXTLMRSPWWCHAQBXYGZYPXYYRRCLMPYMGKSJSZYSRMYJSNXTPLNBAPPYPYLXYYZKYNLDZYJZCZNNLMZHHARQMPGWQTZMXXMLLHGDZXYHXKYXYCJMFFYYHJFSBSSQLXXNDYCANNMTCJCYPRRNYTYQNYYMBMSXNDLYLYSLJRLXYSXQMLLYZLZJJJKYZZCSFBZXXMSTBJGNXYZHLXNMCWSCYZYFZLXBRNNNYLBNRTGZQYSATSWRYHYJZMZDHZGZDWYBSSCSKXSYHYTXXGCQGXZZSHYXJSCRHMKKBXCZJYJYMKQHZJFNBHMQHYSNJNZYBKNQMCLGQHWLZNZSWXKHLJHYYBQLBFCDSXDLDSPFZPSKJYZWZXZDDXJSMMEGJSCSSMGCLXXKYYYLNYPWWWGYDKZJGGGZGGSYCKNJWNJPCXBJJTQTJWDSSPJXZXNZXUMELPXFSXTLLXCLJXJJLJZXCTPSWXLYDHLYQRWHSYCSQYYBYAYWJJJQFWQCQQCJQGXALDBZZYJGKGXPLTZYFXJLTPADKYQHPMATLCPDCKBMTXYBHKLENXDLEEGQDYMSAWHZMLJTWYGXLYQZLJEEYYBQQFFNLYXRDSCTGJGXYYNKLLYQKCCTLHJLQMKKZGCYYGLLLJDZGYDHZWXPYSJBZKDZGYZZHYWYFQYTYZSZYEZZLYMHJJHTSMQWYZLKYYWZCSRKQYTLTDXWCTYJKLWSQZWBDCQYNCJSRSZJLKCDCDTLZZZACQQZZDDXYPLXZBQJYLZLLLQDDZQJYJYJZYXNYYYNYJXKXDAZWYRDLJYYYRJLXLLDYXJCYWYWNQCCLDDNYYYNYCKCZHXXCCLGZQJGKWPPCQQJYSBZZXYJSQPXJPZBSBDSFNSFPZXHDWZTDWPPTFLZZBZDMYYPQJRSDZSQZSQXBDGCPZSWDWCSQZGMDHZXMWWFYBPDGPHTMJTHZSMMBGZMBZJCFZWFZBBZMQCFMBDMCJXLGPNJBBXGYHYYJGPTZGZMQBQTCGYXJXLWZKYDPDYMGCFTPFXYZTZXDZXTGKMTYBBCLBJASKYTSSQYYMSZXFJEWLXLLSZBQJJJAKLYLXLYCCTSXMCWFKKKBSXLLLLJYXTYLTJYYTDPJHNHNNKBYQNFQYYZBYYESSESSGDYHFHWTCJBSDZZTFDMXHCNJZYMQWSRYJDZJQPDQBBSTJGGFBKJBXTGQHNGWJXJGDLLTHZHHYYYYYYSXWTYYYCCBDBPYPZYCCZYJPZYWCBDLFWZCWJDXXHYHLHWZZXJTCZLCDPXUJCZZZLYXJJTXPHFXWPYWXZPTDZZBDZCYHJHMLXBQXSBYLRDTGJRRCTTTHYTCZWMXFYTWWZCWJWXJYWCSKYBZSCCTZQNHXNWXXKHKFHTSWOCCJYBCMPZZYKBNNZPBZHHZDLSYDDYTYFJPXYNGFXBYQXCBHXCPSXTYZDMKYSNXSXLHKMZXLYHDHKWHXXSSKQYHHCJYXGLHZXCSNHEKDTGZXQYPKDHEXTYKCNYMYYYPKQYYYKXZLTHJQTBYQHXBMYHSQCKWWYLLHCYYLNNEQXQWMCFBDCCMLJGGXDQKTLXKGNQCDGZJWYJJLYHHQTTTNWCHMXCXWHWSZJYDJCCDBQCDGDNYXZTHCQRXCBHZTQCBXWGQWYYBXHMBYMYQTYEXMQKYAQYRGYZSLFYKKQHYSSQYSHJGJCNXKZYCXSBXYXHYYLSTYCXQTHYSMGSCPMMGCCCCCMTZTASMGQZJHKLOSQYLSWTMXSYQKDZLJQQYPLSYCZTCQQPBBQJZCLPKHQZYYXXDTDDTSJCXFFLLCHQXMJLWCJCXTSPYCXNDTJSHJWXDQQJSKXYAMYLSJHMLALYKXCYYDMNMDQMXMCZNNCYBZKKYFLMCHCMLHXRCJJHSYLNMTJZGZGYWJXSRXCWJGJQHQZDQJDCJJZKJKGDZQGJJYJYLXZXXCDQHHHEYTMHLFSBDJSYYSHFYSTCZQLPBDRFRZTZYKYWHSZYQKWDQZRKMSYNBCRXQBJYFAZPZZEDZCJYWBCJWHYJBQSZYWRYSZPTDKZPFPBNZTKLQYHBBZPNPPTYZZYBQNYDCPJMMCYCQMCYFZZDCMNLFPBPLNGQJTBTTNJZPZBBZNJKLJQYLNBZQHKSJZNGGQSZZKYXSHPZSNBCGZKDDZQANZHJKDRTLZLSWJLJZLYWTJNDJZJHXYAYNCBGTZCSSQMNJPJYTYSWXZFKWJQTKHTZPLBHSNJZSYZBWZZZZLSYLSBJHDWWQPSLMMFBJDWAQYZTCJTBNNWZXQXCDSLQGDSDPDZHJTQQPSWLYYJZLGYXYZLCTCBJTKTYCZJTQKBSJLGMGZDMCSGPYNJZYQYYKNXRPWSZXMTNCSZZYXYBYHYZAXYWQCJTLLCKJJTJHGDXDXYQYZZBYWDLWQCGLZGJGQRQZCZSSBCRPCSKYDZNXJSQGXSSJMYDNSTZTPBDLTKZWXQWQTZEXNQCZGWEZKSSBYBRTSSSLCCGBPSZQSZLCCGLLLZXHZQTHCZMQGYZQZNMCOCSZJMMZSQPJYGQLJYJPPLDXRGZYXCCSXHSHGTZNLZWZKJCXTCFCJXLBMQBCZZWPQDNHXLJCTHYZLGYLNLSZZPCXDSCQQHJQKSXZPBAJYEMSMJTZDXLCJYRYYNWJBNGZZTMJXLTBSLYRZPYLSSCNXPHLLHYLLQQZQLXYMRSYCXZLMMCZLTZSDWTJJLLNZGGQXPFSKYGYGHBFZPDKMWGHCXMSGDXJMCJZDYCABXJDLNBCDQYGSKYDQTXDJJYXMSZQAZDZFSLQXYJSJZYLBTXXWXQQZBJZUFBBLYLWDSLJHXJYZJWTDJCZFQZQZZDZSXZZQLZCDZFJHYSPYMPQZMLPPLFFXJJNZZYLSJEYQZFPFZKSYWJJJHRDJZZXTXXGLGHYDXCSKYSWMMZCWYBAZBJKSHFHJCXMHFQHYXXYZFTSJYZFXYXPZLCHMZMBXHZZSXYFYMNCWDABAZLXKTCSHHXKXJJZJSTHYGXSXYYHHHJWXKZXSSBZZWHHHCWTZZZPJXSNXQQJGZYZYWLLCWXZFXXYXYHXMKYYSWSQMNLNAYCYSPMJKHWCQHYLAJJMZXHMMCNZHBHXCLXTJPLTXYJHDYYLTTXFSZHYXXSJBJYAYRSMXYPLCKDUYHLXRLNLLSTYZYYQYGYHHSCCSMZCTZQXKYQFPYYRPFFLKQUNTSZLLZMWWTCQQYZWTLLMLMPWMBZSSTZRBPDDTLQJJBXZCSRZQQYGWCSXFWZLXCCRSZDZMCYGGDZQSGTJSWLJMYMMZYHFBJDGYXCCPSHXNZCSBSJYJGJMPPWAFFYFNXHYZXZYLREMZGZCYZSSZDLLJCSQFNXZKPTXZGXJJGFMYYYSNBTYLBNLHPFZDCYFBMGQRRSSSZXYSGTZRNYDZZCDGPJAFJFZKNZBLCZSZPSGCYCJSZLMLRSZBZZLDLSLLYSXSQZQLYXZLSKKBRXBRBZCYCXZZZEEYFGKLZLYYHGZSGZLFJHGTGWKRAAJYZKZQTSSHJJXDCYZUYJLZYRZDQQHGJZXSSZBYKJPBFRTJXLLFQWJHYLQTYMBLPZDXTZYGBDHZZRBGXHWNJTJXLKSCFSMWLSDQYSJTXKZSCFWJLBXFTZLLJZLLQBLSQMQQCGCZFPBPHZCZJLPYYGGDTGWDCFCZQYYYQYSSCLXZSKLZZZGFFCQNWGLHQYZJJCZLQZZYJPJZZBPDCCMHJGXDQDGDLZQMFGPSYTSDYFWWDJZJYSXYYCZCYHZWPBYKXRYLYBHKJKSFXTZJMMCKHLLTNYYMSYXYZPYJQYCSYCWMTJJKQYRHLLQXPSGTLYYCLJSCPXJYZFNMLRGJJTYZBXYZMSJYJHHFZQMSYXRSZCWTLRTQZSSTKXGQKGSPTGCZNJSJCQCXHMXGGZTQYDJKZDLBZSXJLHYQGGGTHQSZPYHJHHGYYGKGGCWJZZYLCZLXQSFTGZSLLLMLJSKCTBLLZZSZMMNYTPZSXQHJCJYQXYZXZQZCPSHKZZYSXCDFGMWQRLLQXRFZTLYSTCTMJCXJJXHJNXTNRZTZFQYHQGLLGCXSZSJDJLJCYDSJTLNYXHSZXCGJZYQPYLFHDJSBPCCZHJJJQZJQDYBSSLLCMYTTMQTBHJQNNYGKYRQYQMZGCJKPDCGMYZHQLLSLLCLMHOLZGDYYFZSLJCQZLYLZQJESHNYLLJXGJXLYSYYYXNBZLJSSZCQQCJYLLZLTJYLLZLLBNYLGQCHXYYXOXCXQKYJXXXYKLXSXXYQXCYKQXQCSGYXXYQXYGYTQOHXHXPYXXXULCYEYCHZZCBWQBBWJQZSCSZSSLZYLKDESJZWMYMCYTSDSXXSCJPQQSQYLYYZYCMDJDZYWCBTJSYDJKCYDDJLBDJJSODZYSYXQQYXDHHGQQYQHDYXWGMMMAJDYBBBPPBCMUUPLJZSMTXERXJMHQNUTPJDCBSSMSSSTKJTSSMMTRCPLZSZMLQDSDMJMQPNQDXCFYNBFSDQXYXHYAYKQYDDLQYYYSSZBYDSLNTFQTZQPZMCHDHCZCWFDXTMYQSPHQYYXSRGJCWTJTZZQMGWJJTJHTQJBBHWZPXXHYQFXXQYWYYHYSCDYDHHQMNMTMWCPBSZPPZZGLMZFOLLCFWHMMSJZTTDHZZYFFYTZZGZYSKYJXQYJZQBHMBZZLYGHGFMSHPZFZSNCLPBQSNJXZSLXXFPMTYJYGBXLLDLXPZJYZJYHHZCYWHJYLSJEXFSZZYWXKZJLUYDTMLYMQJPWXYHXSKTQJEZRPXXZHHMHWQPWQLYJJQJJZSZCPHJLCHHNXJLQWZJHBMZYXBDHHYPZLHLHLGFWLCHYYTLHJXCJMSCPXSTKPNHQXSRTYXXTESYJCTLSSLSTDLLLWWYHDHRJZSFGXTSYCZYNYHTDHWJSLHTZDQDJZXXQHGYLTZPHCSQFCLNJTCLZPFSTPDYNYLGMJLLYCQHYSSHCHYLHQYQTMZYPBYWRFQYKQSYSLZDQJMPXYYSSRHZJNYWTQDFZBWWTWWRXCWHGYHXMKMYYYQMSMZHNGCEPMLQQMTCWCTMMPXJPJJHFXYYZSXZHTYBMSTSYJTTQQQYYLHYNPYQZLCYZHZWSMYLKFJXLWGXYPJYTYSYXYMZCKTTWLKSMZSYLMPWLZWXWQZSSAQSYXYRHSSNTSRAPXCPWCMGDXHXZDZYFJHGZTTSBJHGYZSZYSMYCLLLXBTYXHBBZJKSSDMALXHYCFYGMQYPJYCQXJLLLJGSLZGQLYCJCCZOTYXMTMTTLLWTGPXYMZMKLPSZZZXHKQYSXCTYJZYHXSHYXZKXLZWPSQPYHJWPJPWXQQYLXSDHMRSLZZYZWTTCYXYSZZSHBSCCSTPLWSSCJCHNLCGCHSSPHYLHFHHXJSXYLLNYLSZDHZXYLSXLWZYKCLDYAXZCMDDYSPJTQJZLNWQPSSSWCTSTSZLBLNXSMNYYMJQBQHRZWTYYDCHQLXKPZWBGQYBKFCMZWPZLLYYLSZYDWHXPSBCMLJBSCGBHXLQHYRLJXYSWXWXZSLDFHLSLYNJLZYFLYJYCDRJLFSYZFSLLCQYQFGJYHYXZLYLMSTDJCYHBZLLNWLXXYGYYHSMGDHXXHHLZZJZXCZZZCYQZFNGWPYLCPKPYYPMCLQKDGXZGGWQBDXZZKZFBXXLZXJTPJPTTBYTSZZDWSLCHZHSLTYXHQLHYXXXYYZYSWTXZKHLXZXZPYHGCHKCFSYHUTJRLXFJXPTZTWHPLYXFCRHXSHXKYXXYHZQDXQWULHYHMJTBFLKHTXCWHJFWJCFPQRYQXCYYYQYGRPYWSGSUNGWCHKZDXYFLXXHJJBYZWTSXXNCYJJYMSWZJQRMHXZWFQSYLZJZGBHYNSLBGTTCSYBYXXWXYHXYYXNSQYXMQYWRGYQLXBBZLJSYLPSYTJZYHYZAWLRORJMKSCZJXXXYXCHDYXRYXXJDTSQFXLYLTSFFYXLMTYJMJUYYYXLTZCSXQZQHZXLYYXZHDNBRXXXJCTYHLBRLMBRLLAXKYLLLJLYXXLYCRYLCJTGJCMTLZLLCYZZPZPCYAWHJJFYBDYYZSMPCKZDQYQPBPCJPDCYZMDPBCYYDYCNNPLMTMLRMFMMGWYZBSJGYGSMZQQQZTXMKQWGXLLPJGZBQCDJJJFPKJKCXBLJMSWMDTQJXLDLPPBXCWRCQFBFQJCZAHZGMYKPHYYHZYKNDKZMBPJYXPXYHLFPNYYGXJDBKXNXHJMZJXSTRSTLDXSKZYSYBZXJLXYSLBZYSLHXJPFXPQNBYLLJQKYGZMCYZZYMCCSLCLHZFWFWYXZMWSXTYNXJHPYYMCYSPMHYSMYDYSHQYZCHMJJMZCAAGCFJBBHPLYZYLXXSDJGXDHKXXTXXNBHRMLYJSLTXMRHNLXQJXYZLLYSWQGDLBJHDCGJYQYCMHWFMJYBMBYJYJWYMDPWHXQLDYGPDFXXBCGJSPCKRSSYZJMSLBZZJFLJJJLGXZGYXYXLSZQYXBEXYXHGCXBPLDYHWETTWWCJMBTXCHXYQXLLXFLYXLLJLSSFWDPZSMYJCLMWYTCZPCHQEKCQBWLCQYDPLQPPQZQFJQDJHYMMCXTXDRMJWRHXCJZYLQXDYYNHYYHRSLSRSYWWZJYMTLTLLGTQCJZYABTCKZCJYCCQLJZQXALMZYHYWLWDXZXQDLLQSHGPJFJLJHJABCQZDJGTKHSSTCYJLPSWZLXZXRWGLDLZRLZXTGSLLLLZLYXXWGDZYGBDPHZPBRLWSXQBPFDWOFMWHLYPCBJCCLDMBZPBZZLCYQXLDOMZBLZWPDWYYGDSTTHCSQSCCRSSSYSLFYBFNTYJSZDFNDPDHDZZMBBLSLCMYFFGTJJQWFTMTPJWFNLBZCMMJTGBDZLQLPYFHYYMJYLSDCHDZJWJCCTLJCLDTLJJCPDDSQDSSZYBNDBJLGGJZXSXNLYCYBJXQYCBYLZCFZPPGKCXZDZFZTJJFJSJXZBNZYJQTTYJYHTYCZHYMDJXTTMPXSPLZCDWSLSHXYPZGTFMLCJTYCBPMGDKWYCYZCDSZZYHFLYCTYGWHKJYYLSJCXGYWJCBLLCSNDDBTZBSCLYZCZZSSQDLLMQYYHFSLQLLXFTYHABXGWNYWYYPLLSDLDLLBJCYXJZMLHLJDXYYQYTDLLLBUGBFDFBBQJZZMDPJHGCLGMJJPGAEHHBWCQXAXHHHZCHXYPHJAXHLPHJPGPZJQCQZGJJZZUZDMQYYBZZPHYHYBWHAZYJHYKFGDPFQSDLZMLJXKXGALXZDAGLMDGXMWZQYXXDXXPFDMMSSYMPFMDMMKXKSYZYSHDZKXSYSMMZZZMSYDNZZCZXFPLSTMZDNMXCKJMZTYYMZMZZMSXHHDCZJEMXXKLJSTLWLSQLYJZLLZJSSDPPMHNLZJCZYHMXXHGZCJMDHXTKGRMXFWMCGMWKDTKSXQMMMFZZYDKMSCLCMPCGMHSPXQPZDSSLCXKYXTWLWJYAHZJGZQMCSNXYYMMPMLKJXMHLMLQMXCTKZMJQYSZJSYSZHSYJZJCDAJZYBSDQJZGWZQQXFKDMSDJLFWEHKZQKJPEYPZYSZCDWYJFFMZZYLTTDZZEFMZLBNPPLPLPEPSZALLTYLKCKQZKGENQLWAGYXYDPXLHSXQQWQCQXQCLHYXXMLYCCWLYMQYSKGCHLCJNSZKPYZKCQZQLJPDMDZHLASXLBYDWQLWDNBQCRYDDZTJYBKBWSZDXDTNPJDTCTQDFXQQMGNXECLTTBKPWSLCTYQLPWYZZKLPYGZCQQPLLKCCYLPQMZCZQCLJSLQZDJXLDDHPZQDLJJXZQDXYZQKZLJCYQDYJPPYPQYKJYRMPCBYMCXKLLZLLFQPYLLLMBSGLCYSSLRSYSQTMXYXZQZFDZUYSYZTFFMZZSMZQHZSSCCMLYXWTPZGXZJGZGSJSGKDDHTQGGZLLBJDZLCBCHYXYZHZFYWXYZYMSDBZZYJGTSMTFXQYXQSTDGSLNXDLRYZZLRYYLXQHTXSRTZNGZXBNQQZFMYKMZJBZYMKBPNLYZPBLMCNQYZZZSJZHJCTZKHYZZJRDYZHNPXGLFZTLKGJTCTSSYLLGZRZBBQZZKLPKLCZYSSUYXBJFPNJZZXCDWXZYJXZZDJJKGGRSRJKMSMZJLSJYWQSKYHQJSXPJZZZLSNSHRNYPZTWCHKLPSRZLZXYJQXQKYSJYCZTLQZYBBYBWZPQDWWYZCYTJCJXCKCWDKKZXSGKDZXWWYYJQYYTCYTDLLXWKCZKKLCCLZCQQDZLQLCSFQCHQHSFSMQZZLNBJJZBSJHTSZDYSJQJPDLZCDCWJKJZZLPYCGMZWDJJBSJQZSYZYHHXJPBJYDSSXDZNCGLQMBTSFSBPDZDLZNFGFJGFSMPXJQLMBLGQCYYXBQKDJJQYRFKZTJDHCZKLBSDZCFJTPLLJGXHYXZCSSZZXSTJYGKGCKGYOQXJPLZPBPGTGYJZGHZQZZLBJLSQFZGKQQJZGYCZBZQTLDXRJXBSXXPZXHYZYCLWDXJJHXMFDZPFZHQHQMQGKSLYHTYCGFRZGNQXCLPDLBZCSCZQLLJBLHBZCYPZZPPDYMZZSGYHCKCPZJGSLJLNSCDSLDLXBMSTLDDFJMKDJDHZLZXLSZQPQPGJLLYBDSZGQLBZLSLKYYHZTTNTJYQTZZPSZQZTLLJTYYLLQLLQYZQLBDZLSLYYZYMDFSZSNHLXZNCZQZPBWSKRFBSYZMTHBLGJPMCZZLSTLXSHTCSYZLZBLFEQHLXFLCJLYLJQCBZLZJHHSSTBRMHXZHJZCLXFNBGXGTQJCZTMSFZKJMSSNXLJKBHSJXNTNLZDNTLMSJXGZJYJCZXYJYJWRWWQNZTNFJSZPZSHZJFYRDJSFSZJZBJFZQZZHZLXFYSBZQLZSGYFTZDCSZXZJBQMSZKJRHYJZCKMJKHCHGTXKXQGLXPXFXTRTYLXJXHDTSJXHJZJXZWZLCQSBTXWXGXTXXHXFTSDKFJHZYJFJXRZSDLLLTQSQQZQWZXSYQTWGWBZCGZLLYZBCLMQQTZHZXZXLJFRMYZFLXYSQXXJKXRMQDZDMMYYBSQBHGZMWFWXGMXLZPYYTGZYCCDXYZXYWGSYJYZNBHPZJSQSYXSXRTFYZGRHZTXSZZTHCBFCLSYXZLZQMZLMPLMXZJXSFLBYZMYQHXJSXRXSQZZZSSLYFRCZJRCRXHHZXQYDYHXSJJHZCXZBTYNSYSXJBQLPXZQPYMLXZKYXLXCJLCYSXXZZLXDLLLJJYHZXGYJWKJRWYHCPSGNRZLFZWFZZNSXGXFLZSXZZZBFCSYJDBRJKRDHHGXJLJJTGXJXXSTJTJXLYXQFCSGSWMSBCTLQZZWLZZKXJMLTMJYHSDDBXGZHDLBMYJFRZFSGCLYJBPMLYSMSXLSZJQQHJZFXGFQFQBPXZGYYQXGZTCQWYLTLGWSGWHRLFSFGZJMGMGBGTJFSYZZGZYZAFLSSPMLPFLCWBJZCLJJMZLPJJLYMQDMYYYFBGYGYZMLYZDXQYXRQQQHSYYYQXYLJTYXFSFSLLGNQCYHYCWFHCCCFXPYLYPLLZYXXXXXKQHHXSHJZCFZSCZJXCPZWHHHHHAPYLQALPQAFYHXDYLUKMZQGGGDDESRNNZLTZGCHYPPYSQJJHCLLJTOLNJPZLJLHYMHEYDYDSQYCDDHGZUNDZCLZYZLLZNTNYZGSLHSLPJJBDGWXPCDUTJCKLKCLWKLLCASSTKZZDNQNTTLYYZSSYSSZZRYLJQKCQDHHCRXRZYDGRGCWCGZQFFFPPJFZYNAKRGYWYQPQXXFKJTSZZXSWZDDFBBXTBGTZKZNPZZPZXZPJSZBMQHKCYXYLDKLJNYPKYGHGDZJXXEAHPNZKZTZCMXCXMMJXNKSZQNMNLWBWWXJKYHCPSTMCSQTZJYXTPCTPDTNNPGLLLZSJLSPBLPLQHDTNJNLYYRSZFFJFQWDPHZDWMRZCCLODAXNSSNYZRESTYJWJYJDBCFXNMWTTBYLWSTSZGYBLJPXGLBOCLHPCBJLTMXZLJYLZXCLTPNCLCKXTPZJSWCYXSFYSZDKNTLBYJCYJLLSTGQCBXRYZXBXKLYLHZLQZLNZCXWJZLJZJNCJHXMNZZGJZZXTZJXYCYYCXXJYYXJJXSSSJSTSSTTPPGQTCSXWZDCSYFPTFBFHFBBLZJCLZZDBXGCXLQPXKFZFLSYLTUWBMQJHSZBMDDBCYSCCLDXYCDDQLYJJWMQLLCSGLJJSYFPYYCCYLTJANTJJPWYCMMGQYYSXDXQMZHSZXPFTWWZQSWQRFKJLZJQQYFBRXJHHFWJJZYQAZMYFRHCYYBYQWLPEXCCZSTYRLTTDMQLYKMBBGMYYJPRKZNPBSXYXBHYZDJDNGHPMFSGMWFZMFQMMBCMZZCJJLCNUXYQLMLRYGQZCYXZLWJGCJCGGMCJNFYZZJHYCPRRCMTZQZXHFQGTJXCCJEAQCRJYHPLQLSZDJRBCQHQDYRHYLYXJSYMHZYDWLDFRYHBPYDTSSCNWBXGLPZMLZZTQSSCPJMXXYCSJYTYCGHYCJWYRXXLFEMWJNMKLLSWTXHYYYNCMMCWJDQDJZGLLJWJRKHPZGGFLCCSCZMCBLTBHBQJXQDSPDJZZGKGLFQYWBZYZJLTSTDHQHCTCBCHFLQMPWDSHYYTQWCNZZJTLBYMBPDYYYXSQKXWYYFLXXNCWCXYPMAELYKKJMZZZBRXYYQJFLJPFHHHYTZZXSGQQMHSPGDZQWBWPJHZJDYSCQWZKTXXSQLZYYMYSDZGRXCKKUJLWPYSYSCSYZLRMLQSYLJXBCXTLWDQZPCYCYKPPPNSXFYZJJRCEMHSZMSXLXGLRWGCSTLRSXBZGBZGZTCPLUJLSLYLYMTXMTZPALZXPXJTJWTCYYZLBLXBZLQMYLXPGHDSLSSDMXMBDZZSXWHAMLCZCPJMCNHJYSNSYGCHSKQMZZQDLLKABLWJXSFMOCDXJRRLYQZKJMYBYQLYHETFJZFRFKSRYXFJTWDSXXSYSQJYSLYXWJHSNLXYYXHBHAWHHJZXWMYLJCSSLKYDZTXBZSYFDXGXZJKHSXXYBSSXDPYNZWRPTQZCZENYGCXQFJYKJBZMLJCMQQXUOXSLYXXLYLLJDZBTYMHPFSTTQQWLHOKYBLZZALZXQLHZWRRQHLSTMYPYXJJXMQSJFNBXYXYJXXYQYLTHYLQYFMLKLJTMLLHSZWKZHLJMLHLJKLJSTLQXYLMBHHLNLZXQJHXCFXXLHYHJJGBYZZKBXSCQDJQDSUJZYYHZHHMGSXCSYMXFEBCQWWRBPYYJQTYZCYQYQQZYHMWFFHGZFRJFCDPXNTQYZPDYKHJLFRZXPPXZDBBGZQSTLGDGYLCQMLCHHMFYWLZYXKJLYPQHSYWMQQGQZMLZJNSQXJQSYJYCBEHSXFSZPXZWFLLBCYYJDYTDTHWZSFJMQQYJLMQXXLLDTTKHHYBFPWTYYSQQWNQWLGWDEBZWCMYGCULKJXTMXMYJSXHYBRWFYMWFRXYQMXYSZTZZTFYKMLDHQDXWYYNLCRYJBLPSXCXYWLSPRRJWXHQYPHTYDNXHHMMYWYTZCSQMTSSCCDALWZTCPQPYJLLQZYJSWXMZZMMYLMXCLMXCZMXMZSQTZPPQQBLPGXQZHFLJJHYTJSRXWZXSCCDLXTYJDCQJXSLQYCLZXLZZXMXQRJMHRHZJBHMFLJLMLCLQNLDXZLLLPYPSYJYSXCQQDCMQJZZXHNPNXZMEKMXHYKYQLXSXTXJYYHWDCWDZHQYYBGYBCYSCFGPSJNZDYZZJZXRZRQJJYMCANYRJTLDPPYZBSTJKXXZYPFDWFGZZRPYMTNGXZQBYXNBUFNQKRJQZMJEGRZGYCLKXZDSKKNSXKCLJSPJYYZLQQJYBZSSQLLLKJXTBKTYLCCDDBLSPPFYLGYDTZJYQGGKQTTFZXBDKTYYHYBBFYTYYBCLPDYTGDHRYRNJSPTCSNYJQHKLLLZSLYDXXWBCJQSPXBPJZJCJDZFFXXBRMLAZHCSNDLBJDSZBLPRZTSWSBXBCLLXXLZDJZSJPYLYXXYFTFFFBHJJXGBYXJPMMMPSSJZJMTLYZJXSWXTYLEDQPJMYGQZJGDJLQJWJQLLSJGJGYGMSCLJJXDTYGJQJQJCJZCJGDZZSXQGSJGGCXHQXSNQLZZBXHSGZXCXYLJXYXYYDFQQJHJFXDHCTXJYRXYSQTJXYEFYYSSYYJXNCYZXFXMSYSZXYYSCHSHXZZZGZZZGFJDLTYLNPZGYJYZYYQZPBXQBDZTZCZYXXYHHSQXSHDHGQHJHGYWSZTMZMLHYXGEBTYLZKQWYTJZRCLEKYSTDBCYKQQSAYXCJXWWGSBHJYZYDHCSJKQCXSWXFLTYNYZPZCCZJQTZWJQDZZZQZLJJXLSBHPYXXPSXSHHEZTXFPTLQYZZXHYTXNCFZYYHXGNXMYWXTZSJPTHHGYMXMXQZXTSBCZYJYXXTYYZYPCQLMMSZMJZZLLZXGXZAAJZYXJMZXWDXZSXZDZXLEYJJZQBHZWZZZQTZPSXZTDSXJJJZNYAZPHXYYSRNQDTHZHYYKYJHDZXZLSWCLYBZYECWCYCRYLCXNHZYDZYDYJDFRJJHTRSQTXYXJRJHOJYNXELXSFSFJZGHPZSXZSZDZCQZBYYKLSGSJHCZSHDGQGXYZGXCHXZJWYQWGYHKSSEQZZNDZFKWYSSTCLZSTSYMCDHJXXYWEYXCZAYDMPXMDSXYBSQMJMZJMTZQLPJYQZCGQHXJHHLXXHLHDLDJQCLDWBSXFZZYYSCHTYTYYBHECXHYKGJPXHHYZJFXHWHBDZFYZBCAPNPGNYDMSXHMMMMAMYNBYJTMPXYYMCTHJBZYFCGTYHWPHFTWZZEZSBZEGPFMTSKFTYCMHFLLHGPZJXZJGZJYXZSBBQSCZZLZCCSTPGXMJSFTCCZJZDJXCYBZLFCJSYZFGSZLYBCWZZBYZDZYPSWYJZXZBDSYUXLZZBZFYGCZXBZHZFTPBGZGEJBSTGKDMFHYZZJHZLLZZGJQZLSFDJSSCBZGPDLFZFZSZYZYZSYGCXSNXXCHCZXTZZLJFZGQSQYXZJQDCCZTQCDXZJYQJQCHXZTDLGSCXZSYQJQTZWLQDQZTQCHQQJZYEZZZPBWKDJFCJPZTYPQYQTTYNLMBDKTJZPQZQZZFPZSBNJLGYJDXJDZZKZGQKXDLPZJTCJDQBXDJQJSTCKNXBXZMSLYJCQMTJQWWCJQNJNLLLHJCWQTBZQYDZCZPZZDZYDDCYZZZCCJTTJFZDPRRTZTJDCQTQZDTJNPLZBCLLCTZSXKJZQZPZLBZRBTJDCXFCZDBCCJJLTQQPLDCGZDBBZJCQDCJWYNLLZYZCCDWLLXWZLXRXNTQQCZXKQLSGDFQTDDGLRLAJJTKUYMKQLLTZYTDYYCZGJWYXDXFRSKSTQTENQMRKQZHHQKDLDAZFKYPBGGPZREBZZYKZZSPEGJXGYKQZZZSLYSYYYZWFQZYLZZLZHWCHKYPQGNPGBLPLRRJYXCCSYYHSFZFYBZYYTGZXYLXCZWXXZJZBLFFLGSKHYJZEYJHLPLLLLCZGXDRZELRHGKLZZYHZLYQSZZJZQLJZFLNBHGWLCZCFJYSPYXZLZLXGCCPZBLLCYBBBBUBBCBPCRNNZCZYRBFSRLDCGQYYQXYGMQZWTZYTYJXYFWTEHZZJYWLCCNTZYJJZDEDPZDZTSYQJHDYMBJNYJZLXTSSTPHNDJXXBYXQTZQDDTJTDYYTGWSCSZQFLSHLGLBCZPHDLYZJYCKWTYTYLBNYTSDSYCCTYSZYYEBHEXHQDTWNYGYCLXTSZYSTQMYGZAZCCSZZDSLZCLZRQXYYELJSBYMXSXZTEMBBLLYYLLYTDQYSHYMRQWKFKBFXNXSBYCHXBWJYHTQBPBSBWDZYLKGZSKYHXQZJXHXJXGNLJKZLYYCDXLFYFGHLJGJYBXQLYBXQPQGZTZPLNCYPXDJYQYDYMRBESJYYHKXXSTMXRCZZYWXYQYBMCLLYZHQYZWQXDBXBZWZMSLPDMYSKFMZKLZCYQYCZLQXFZZYDQZPZYGYJYZMZXDZFYFYTTQTZHGSPCZMLCCYTZXJCYTJMKSLPZHYSNZLLYTPZCTZZCKTXDHXXTQCYFKSMQCCYYAZHTJPCYLZLYJBJXTPNYLJYYNRXSYLMMNXJSMYBCSYSYLZYLXJJQYLDZLPQBFZZBLFNDXQKCZFYWHGQMRDSXYCYTXNQQJZYYPFZXDYZFPRXEJDGYQBXRCNFYYQPGHYJDYZXGRHTKYLNWDZNTSMPKLBTHBPYSZBZTJZSZZJTYYXZPHSSZZBZCZPTQFZMYFLYPYBBJQXZMXXDJMTSYSKKBJZXHJCKLPSMKYJZCXTMLJYXRZZQSLXXQPYZXMKYXXXJCLJPRMYYGADYSKQLSNDHYZKQXZYZTCGHZTLMLWZYBWSYCTBHJHJFCWZTXWYTKZLXQSHLYJZJXTMPLPYCGLTBZZTLZJCYJGDTCLKLPLLQPJMZPAPXYZLKKTKDZCZZBNZDYDYQZJYJGMCTXLTGXSZLMLHBGLKFWNWZHDXUHLFMKYSLGXDTWWFRJEJZTZHYDXYKSHWFZCQSHKTMQQHTZHYMJDJSKHXZJZBZZXYMPAGQMSTPXLSKLZYNWRTSQLSZBPSPSGZWYHTLKSSSWHZZLYYTNXJGMJSZSUFWNLSOZTXGXLSAMMLBWLDSZYLAKQCQCTMYCFJBSLXCLZZCLXXKSBZQCLHJPSQPLSXXCKSLNHPSFQQYTXYJZLQLDXZQJZDYYDJNZPTUZDSKJFSLJHYLZSQZLBTXYDGTQFDBYAZXDZHZJNHHQBYKNXJJQCZMLLJZKSPLDYCLBBLXKLELXJLBQYCXJXGCNLCQPLZLZYJTZLJGYZDZPLTQCSXFDMNYCXGBTJDCZNBGBQYQJWGKFHTNPYQZQGBKPBBYZMTJDYTBLSQMPSXTBNPDXKLEMYYCJYNZCTLDYKZZXDDXHQSHDGMZSJYCCTAYRZLPYLTLKXSLZCGGEXCLFXLKJRTLQJAQZNCMBYDKKCXGLCZJZXJHPTDJJMZQYKQSECQZDSHHADMLZFMMZBGNTJNNLGBYJBRBTMLBYJDZXLCJLPLDLPCQDHLXZLYCBLCXZZJADJLNZMMSSSMYBHBSQKBHRSXXJMXSDZNZPXLGBRHWGGFCXGMSKLLTSJYYCQLTSKYWYYHYWXBXQYWPYWYKQLSQPTNTKHQCWDQKTWPXXHCPTHTWUMSSYHBWCRWXHJMKMZNGWTMLKFGHKJYLSYYCXWHYECLQHKQHTTQKHFZLDXQWYZYYDESBPKYRZPJFYYZJCEQDZZDLATZBBFJLLCXDLMJSSXEGYGSJQXCWBXSSZPDYZCXDNYXPPZYDLYJCZPLTXLSXYZYRXCYYYDYLWWNZSAHJSYQYHGYWWAXTJZDAXYSRLTDPSSYYFNEJDXYZHLXLLLZQZSJNYQYQQXYJGHZGZCYJCHZLYCDSHWSHJZYJXCLLNXZJJYYXNFXMWFPYLCYLLABWDDHWDXJMCXZTZPMLQZHSFHZYNZTLLDYWLSLXHYMMYLMBWWKYXYADTXYLLDJPYBPWUXJMWMLLSAFDLLYFLBHHHBQQLTZJCQJLDJTFFKMMMBYTHYGDCQRDDWRQJXNBYSNWZDBYYTBJHPYBYTTJXAAHGQDQTMYSTQXKBTZPKJLZRBEQQSSMJJBDJOTGTBXPGBKTLHQXJJJCTHXQDWJLWRFWQGWSHCKRYSWGFTGYGBXSDWDWRFHWYTJJXXXJYZYSLPYYYPAYXHYDQKXSHXYXGSKQHYWFDDDPPLCJLQQEEWXKSYYKDYPLTJTHKJLTCYYHHJTTPLTZZCDLTHQKZXQYSTEEYWYYZYXXYYSTTJKLLPZMCYHQGXYHSRMBXPLLNQYDQHXSXXWGDQBSHYLLPJJJTHYJKYPPTHYYKTYEZYENMDSHLCRPQFDGFXZPSFTLJXXJBSWYYSKSFLXLPPLBBBLBSFXFYZBSJSSYLPBBFFFFSSCJDSTZSXZRYYSYFFSYZYZBJTBCTSBSDHRTJJBYTCXYJEYLXCBNEBJDSYXYKGSJZBXBYTFZWGENYHHTHZHHXFWGCSTBGXKLSXYWMTMBYXJSTZSCDYQRCYTWXZFHMYMCXLZNSDJTTTXRYCFYJSBSDYERXJLJXBBDEYNJGHXGCKGSCYMBLXJMSZNSKGXFBNBPTHFJAAFXYXFPXMYPQDTZCXZZPXRSYWZDLYBBKTYQPQJPZYPZJZNJPZJLZZFYSBTTSLMPTZRTDXQSJEHBZYLZDHLJSQMLHTXTJECXSLZZSPKTLZKQQYFSYGYWPCPQFHQHYTQXZKRSGTTSQCZLPTXCDYYZXSQZSLXLZMYCPCQBZYXHBSXLZDLTCDXTYLZJYYZPZYZLTXJSJXHLPMYTXCQRBLZSSFJZZTNJYTXMYJHLHPPLCYXQJQQKZZSCPZKSWALQSBLCCZJSXGWWWYGYKTJBBZTDKHXHKGTGPBKQYSLPXPJCKBMLLXDZSTBKLGGQKQLSBKKTFXRMDKBFTPZFRTBBRFERQGXYJPZSSTLBZTPSZQZSJDHLJQLZBPMSMMSXLQQNHKNBLRDDNXXDHDDJCYYGYLXGZLXSYGMQQGKHBPMXYXLYTQWLWGCPBMQXCYZYDRJBHTDJYHQSHTMJSBYPLWHLZFFNYPMHXXHPLTBQPFBJWQDBYGPNZTPFZJGSDDTQSHZEAWZZYLLTYYBWJKXXGHLFKXDJTMSZSQYNZGGSWQSPHTLSSKMCLZXYSZQZXNCJDQGZDLFNYKLJCJLLZLMZZNHYDSSHTHZZLZZBBHQZWWYCRZHLYQQJBEYFXXXWHSRXWQHWPSLMSSKZTTYGYQQWRSLALHMJTQJSMXQBJJZJXZYZKXBYQXBJXSHZTSFJLXMXZXFGHKZSZGGYLCLSARJYHSLLLMZXELGLXYDJYTLFBHBPNLYZFBBHPTGJKWETZHKJJXZXXGLLJLSTGSHJJYQLQZFKCGNNDJSSZFDBCTWWSEQFHQJBSAQTGYPQLBXBMMYWXGSLZHGLZGQYFLZBYFZJFRYSFMBYZHQGFWZSYFYJJPHZBYYZFFWODGRLMFTWLBZGYCQXCDJYGZYYYYTYTYDWEGAZYHXJLZYYHLRMGRXXZCLHNELJJTJTPWJYBJJBXJJTJTEEKHWSLJPLPSFYZPQQBDLQJJTYYQLYZKDKSQJYYQZLDQTGJQYZJSUCMRYQTHTEJMFCTYHYPKMHYZWJDQFHYYXWSHCTXRLJHQXHCCYYYJLTKTTYTMXGTCJTZAYYOCZLYLBSZYWJYTSJYHBYSHFJLYGJXXTMZYYLTXXYPZLXYJZYZYYPNHMYMDYYLBLHLSYYQQLLNJJYMSOYQBZGDLYXYLCQYXTSZEGXHZGLHWBLJHEYXTWQMAKBPQCGYSHHEGQCMWYYWLJYJHYYZLLJJYLHZYHMGSLJLJXCJJYCLYCJPCPZJZJMMYLCQLNQLJQJSXYJMLSZLJQLYCMMHCFMMFPQQMFYLQMCFFQMMMMHMZNFHHJGTTHHKHSLNCHHYQDXTMMQDCYZYXYQMYQYLTDCYYYZAZZCYMZYDLZFFFMMYCQZWZZMABTBYZTDMNZZGGDFTYPCGQYTTSSFFWFDTZQSSYSTWXJHXYTSXXYLBYQHWWKXHZXWZNNZZJZJJQJCCCHYYXBZXZCYZTLLCQXYNJYCYYCYNZZQYYYEWYCZDCJYCCHYJLBTZYYCQWMPWPYMLGKDLDLGKQQBGYCHJXY'; 7 | // 此处收录了375个多音字 8 | const oMultiDiff = { 9 | '19969': 'DZ', 10 | '19975': 'WM', 11 | '19988': 'QJ', 12 | '20048': 'YL', 13 | '20056': 'SC', 14 | '20060': 'NM', 15 | '20094': 'QG', 16 | '20127': 'QJ', 17 | '20167': 'QC', 18 | '20193': 'YG', 19 | '20250': 'KH', 20 | '20256': 'ZC', 21 | '20282': 'SC', 22 | '20285': 'QJG', 23 | '20291': 'TD', 24 | '20314': 'YD', 25 | '20340': 'NE', 26 | '20375': 'TD', 27 | '20389': 'YJ', 28 | '20391': 'CZ', 29 | '20415': 'PB', 30 | '20446': 'YS', 31 | '20447': 'SQ', 32 | '20504': 'TC', 33 | '20608': 'KG', 34 | '20854': 'QJ', 35 | '20857': 'ZC', 36 | '20911': 'PF', 37 | '20985': 'AW', 38 | '21032': 'PB', 39 | '21048': 'XQ', 40 | '21049': 'SC', 41 | '21089': 'YS', 42 | '21119': 'JC', 43 | '21242': 'SB', 44 | '21273': 'SC', 45 | '21305': 'YP', 46 | '21306': 'QO', 47 | '21330': 'ZC', 48 | '21333': 'SDC', 49 | '21345': 'QK', 50 | '21378': 'CA', 51 | '21397': 'SC', 52 | '21414': 'XS', 53 | '21442': 'SC', 54 | '21477': 'JG', 55 | '21480': 'TD', 56 | '21484': 'ZS', 57 | '21494': 'YX', 58 | '21505': 'YX', 59 | '21512': 'HG', 60 | '21523': 'XH', 61 | '21537': 'PB', 62 | '21542': 'PF', 63 | '21549': 'KH', 64 | '21571': 'E', 65 | '21574': 'DA', 66 | '21588': 'TD', 67 | '21589': 'O', 68 | '21618': 'ZC', 69 | '21621': 'KHA', 70 | '21632': 'ZJ', 71 | '21654': 'KG', 72 | '21679': 'LKG', 73 | '21683': 'KH', 74 | '21710': 'A', 75 | '21719': 'YH', 76 | '21734': 'WOE', 77 | '21769': 'A', 78 | '21780': 'WN', 79 | '21804': 'XH', 80 | '21834': 'A', 81 | '21899': 'ZD', 82 | '21903': 'RN', 83 | '21908': 'WO', 84 | '21939': 'ZC', 85 | '21956': 'SA', 86 | '21964': 'YA', 87 | '21970': 'TD', 88 | '22003': 'A', 89 | '22031': 'JG', 90 | '22040': 'XS', 91 | '22060': 'ZC', 92 | '22066': 'ZC', 93 | '22079': 'MH', 94 | '22129': 'XJ', 95 | '22179': 'XA', 96 | '22237': 'NJ', 97 | '22244': 'TD', 98 | '22280': 'JQ', 99 | '22300': 'YH', 100 | '22313': 'XW', 101 | '22331': 'YQ', 102 | '22343': 'YJ', 103 | '22351': 'PH', 104 | '22395': 'DC', 105 | '22412': 'TD', 106 | '22484': 'PB', 107 | '22500': 'PB', 108 | '22534': 'ZD', 109 | '22549': 'DH', 110 | '22561': 'PB', 111 | '22612': 'TD', 112 | '22771': 'KQ', 113 | '22831': 'HB', 114 | '22841': 'JG', 115 | '22855': 'QJ', 116 | '22865': 'XQ', 117 | '23013': 'ML', 118 | '23081': 'WM', 119 | '23487': 'SX', 120 | '23558': 'QJ', 121 | '23561': 'YW', 122 | '23586': 'YW', 123 | '23614': 'YW', 124 | '23615': 'SN', 125 | '23631': 'PB', 126 | '23646': 'ZS', 127 | '23663': 'ZT', 128 | '23673': 'YG', 129 | '23762': 'TD', 130 | '23769': 'ZS', 131 | '23780': 'QJ', 132 | '23884': 'QK', 133 | '24055': 'XH', 134 | '24113': 'DC', 135 | '24162': 'ZC', 136 | '24191': 'GA', 137 | '24273': 'QJ', 138 | '24324': 'NL', 139 | '24377': 'TD', 140 | '24378': 'QJ', 141 | '24439': 'PF', 142 | '24554': 'ZS', 143 | '24683': 'TD', 144 | '24694': 'WE', 145 | '24733': 'LK', 146 | '24925': 'TN', 147 | '25094': 'ZG', 148 | '25100': 'XQ', 149 | '25103': 'XH', 150 | '25153': 'PB', 151 | '25170': 'PB', 152 | '25179': 'KG', 153 | '25203': 'PB', 154 | '25240': 'ZS', 155 | '25282': 'FB', 156 | '25303': 'NA', 157 | '25324': 'KG', 158 | '25341': 'ZY', 159 | '25373': 'WZ', 160 | '25375': 'XJ', 161 | '25384': 'A', 162 | '25457': 'A', 163 | '25528': 'SD', 164 | '25530': 'SC', 165 | '25552': 'TD', 166 | '25774': 'ZC', 167 | '25874': 'ZC', 168 | '26044': 'YW', 169 | '26080': 'WM', 170 | '26292': 'PB', 171 | '26333': 'PB', 172 | '26355': 'ZY', 173 | '26366': 'CZ', 174 | '26397': 'ZC', 175 | '26399': 'QJ', 176 | '26415': 'ZS', 177 | '26451': 'SB', 178 | '26526': 'ZC', 179 | '26552': 'JG', 180 | '26561': 'TD', 181 | '26588': 'JG', 182 | '26597': 'CZ', 183 | '26629': 'ZS', 184 | '26638': 'YL', 185 | '26646': 'XQ', 186 | '26653': 'KG', 187 | '26657': 'XJ', 188 | '26727': 'HG', 189 | '26894': 'ZC', 190 | '26937': 'ZS', 191 | '26946': 'ZC', 192 | '26999': 'KJ', 193 | '27099': 'KJ', 194 | '27449': 'YQ', 195 | '27481': 'XS', 196 | '27542': 'ZS', 197 | '27663': 'ZS', 198 | '27748': 'TS', 199 | '27784': 'SC', 200 | '27788': 'ZD', 201 | '27795': 'TD', 202 | '27812': 'O', 203 | '27850': 'PB', 204 | '27852': 'MB', 205 | '27895': 'SL', 206 | '27898': 'PL', 207 | '27973': 'QJ', 208 | '27981': 'KH', 209 | '27986': 'HX', 210 | '27994': 'XJ', 211 | '28044': 'YC', 212 | '28065': 'WG', 213 | '28177': 'SM', 214 | '28267': 'QJ', 215 | '28291': 'KH', 216 | '28337': 'ZQ', 217 | '28463': 'TL', 218 | '28548': 'DC', 219 | '28601': 'TD', 220 | '28689': 'PB', 221 | '28805': 'JG', 222 | '28820': 'QG', 223 | '28846': 'PB', 224 | '28952': 'TD', 225 | '28975': 'ZC', 226 | '29100': 'A', 227 | '29325': 'QJ', 228 | '29575': 'SL', 229 | '29602': 'FB', 230 | '30010': 'TD', 231 | '30044': 'CX', 232 | '30058': 'PF', 233 | '30091': 'YSP', 234 | '30111': 'YN', 235 | '30229': 'XJ', 236 | '30427': 'SC', 237 | '30465': 'SX', 238 | '30631': 'YQ', 239 | '30655': 'QJ', 240 | '30684': 'QJG', 241 | '30707': 'SD', 242 | '30729': 'XH', 243 | '30796': 'LG', 244 | '30917': 'PB', 245 | '31074': 'NM', 246 | '31085': 'JZ', 247 | '31109': 'SC', 248 | '31181': 'ZC', 249 | '31192': 'MLB', 250 | '31293': 'JQ', 251 | '31400': 'YX', 252 | '31584': 'YJ', 253 | '31896': 'ZN', 254 | '31909': 'ZY', 255 | '31995': 'XJ', 256 | '32321': 'PF', 257 | '32327': 'ZY', 258 | '32418': 'HG', 259 | '32420': 'XQ', 260 | '32421': 'HG', 261 | '32438': 'LG', 262 | '32473': 'GJ', 263 | '32488': 'TD', 264 | '32521': 'QJ', 265 | '32527': 'PB', 266 | '32562': 'ZSQ', 267 | '32564': 'JZ', 268 | '32735': 'ZD', 269 | '32793': 'PB', 270 | '33071': 'PF', 271 | '33098': 'XL', 272 | '33100': 'YA', 273 | '33152': 'PB', 274 | '33261': 'CX', 275 | '33324': 'BP', 276 | '33333': 'TD', 277 | '33406': 'YA', 278 | '33426': 'WM', 279 | '33432': 'PB', 280 | '33445': 'JG', 281 | '33486': 'ZN', 282 | '33493': 'TS', 283 | '33507': 'QJ', 284 | '33540': 'QJ', 285 | '33544': 'ZC', 286 | '33564': 'XQ', 287 | '33617': 'YT', 288 | '33632': 'QJ', 289 | '33636': 'XH', 290 | '33637': 'YX', 291 | '33694': 'WG', 292 | '33705': 'PF', 293 | '33728': 'YW', 294 | '33882': 'SR', 295 | '34067': 'WM', 296 | '34074': 'YW', 297 | '34121': 'QJ', 298 | '34255': 'ZC', 299 | '34259': 'XL', 300 | '34425': 'JH', 301 | '34430': 'XH', 302 | '34485': 'KH', 303 | '34503': 'YS', 304 | '34532': 'HG', 305 | '34552': 'XS', 306 | '34558': 'YE', 307 | '34593': 'ZL', 308 | '34660': 'YQ', 309 | '34892': 'XH', 310 | '34928': 'SC', 311 | '34999': 'QJ', 312 | '35048': 'PB', 313 | '35059': 'SC', 314 | '35098': 'ZC', 315 | '35203': 'TQ', 316 | '35265': 'JX', 317 | '35299': 'JX', 318 | '35782': 'SZ', 319 | '35828': 'YS', 320 | '35830': 'E', 321 | '35843': 'TD', 322 | '35895': 'YG', 323 | '35977': 'MH', 324 | '36158': 'JG', 325 | '36228': 'QJ', 326 | '36426': 'XQ', 327 | '36466': 'DC', 328 | '36710': 'JC', 329 | '36711': 'ZYG', 330 | '36767': 'PB', 331 | '36866': 'SK', 332 | '36951': 'YW', 333 | '37034': 'YX', 334 | '37063': 'XH', 335 | '37218': 'ZC', 336 | '37325': 'ZC', 337 | '38063': 'PB', 338 | '38079': 'TD', 339 | '38085': 'QY', 340 | '38107': 'DC', 341 | '38116': 'TD', 342 | '38123': 'YD', 343 | '38224': 'HG', 344 | '38241': 'XTC', 345 | '38271': 'ZC', 346 | '38415': 'YE', 347 | '38426': 'KH', 348 | '38461': 'YD', 349 | '38463': 'AE', 350 | '38466': 'PB', 351 | '38477': 'XJ', 352 | '38518': 'YT', 353 | '38551': 'WK', 354 | '38585': 'ZC', 355 | '38704': 'XS', 356 | '38739': 'LJ', 357 | '38761': 'GJ', 358 | '38808': 'SQ', 359 | '39048': 'JG', 360 | '39049': 'XJ', 361 | '39052': 'HG', 362 | '39076': 'CZ', 363 | '39271': 'XT', 364 | '39534': 'TD', 365 | '39552': 'TD', 366 | '39584': 'PB', 367 | '39647': 'SB', 368 | '39730': 'LG', 369 | '39748': 'TPB', 370 | '40109': 'ZQ', 371 | '40479': 'ND', 372 | '40516': 'HG', 373 | '40536': 'HG', 374 | '40583': 'QJ', 375 | '40765': 'YQ', 376 | '40784': 'QJ', 377 | '40840': 'YK', 378 | '40863': 'QJG' 379 | }; 380 | 381 | // 参数,中文字符串 382 | // 返回值:拼音首字母串数组 383 | function makePy(str) { 384 | if (typeof (str) !== 'string') { 385 | throw new Error(-1, '函数makePy需要字符串类型参数!'); 386 | } 387 | let arrResult = []; // 保存中间结果的数组 388 | for (let i = 0, len = str.length; i < len; i++) { 389 | // 获得unicode码 390 | let ch = str.charAt(i); 391 | // 检查该unicode码是否在处理范围之内,在则返回该码对映汉字的拼音首字母,不在则调用其它函数处理 392 | arrResult.push(checkCh(ch)); 393 | } 394 | // 处理arrResult,返回所有可能的拼音首字母串数组 395 | return mkRslt(arrResult); 396 | } 397 | 398 | function checkCh(ch) { 399 | let uni = ch.charCodeAt(0); 400 | // 如果不在汉字处理范围之内,返回原字符,也可以调用自己的处理函数 401 | if (uni > 40869 || uni < 19968) { 402 | return ch; // dealWithOthers(ch); 403 | } 404 | // 检查是否是多音字,是按多音字处理,不是就直接在strChineseFirstPY字符串中找对应的首字母 405 | return (oMultiDiff[uni] ? oMultiDiff[uni] : (strChineseFirstPY.charAt(uni - 19968))); 406 | } 407 | 408 | function mkRslt(arr) { 409 | let arrRslt = ['']; 410 | for (let i = 0, len = arr.length; i < len; i++) { 411 | let str = arr[i]; 412 | let strlen = str.length; 413 | if (strlen === 1) { 414 | for (let k = 0; k < arrRslt.length; k++) { 415 | arrRslt[k] += str; 416 | } 417 | } else { 418 | let tmpArr = arrRslt.slice(0); 419 | arrRslt = []; 420 | for (let k = 0; k < strlen; k++) { 421 | // 复制一个相同的arrRslt 422 | let tmp = tmpArr.slice(0); 423 | // 把当前字符str[k]添加到每个元素末尾 424 | for (let j = 0; j < tmp.length; j++) { 425 | tmp[j] += str.charAt(k); 426 | } 427 | // 把复制并修改后的数组连接到arrRslt上 428 | arrRslt = arrRslt.concat(tmp); 429 | } 430 | } 431 | } 432 | return arrRslt; 433 | } 434 | 435 | export default makePy; 436 | --------------------------------------------------------------------------------