├── .babelrc
├── .gitignore
├── README.md
├── build
├── auto_make
│ ├── index.js
│ ├── prompt.js
│ └── tools.js
├── build.js
├── check-server.js
├── check-versions.js
├── config
│ ├── dev.env.js
│ ├── index.js
│ ├── prod.env.js
│ └── test.env.js
├── develop.js
├── open-browser.js
├── rem.conf.js
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
├── webpack.prod.conf.js
└── webpack.test.conf.js
├── controller
├── ajax.js
├── defaultCtrl.js
├── mobile
│ └── home.js
└── pc
│ └── index.js
├── deploy
└── build.sh
├── mock
└── demo.json
├── package.json
├── static
├── css
│ └── reset.scss
├── fonts
│ ├── iconfont.css
│ ├── iconfont.eot
│ ├── iconfont.svg
│ ├── iconfont.ttf
│ └── iconfont.woff
├── image
│ ├── favicon.ico
│ └── logo-grace.png
└── js
│ └── common
│ └── flexible.js
├── test
├── e2e
│ ├── custom-assertions
│ │ └── elementCount.js
│ ├── nightwatch.conf.js
│ ├── runner.js
│ └── specs
│ │ └── test.js
└── unit
│ ├── .eslintrc
│ ├── index.js
│ ├── karma.conf.js
│ └── specs
│ └── Hello.spec.js
├── views
└── _common
│ ├── _template.ejs
│ ├── footer.html
│ ├── foothook.html
│ ├── head.html
│ ├── headhook.html
│ ├── hook
│ └── baidu.html
│ └── layout.html
├── vues
├── _components
│ ├── grace.vue
│ └── start.vue
├── mobile
│ └── home
│ │ ├── components
│ │ └── home.vue
│ │ ├── index.js
│ │ ├── index.vue
│ │ └── router.js
└── pc
│ └── index
│ ├── components
│ └── index.vue
│ ├── index.js
│ ├── index.vue
│ └── router.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-2"],
3 | "plugins": ["transform-runtime"],
4 | "comments": false
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_store
2 |
3 | npm-debug.log
4 |
5 | /node_modules
6 |
7 | /static/**/rev-manifest.json
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-webpack-boilerplate
2 |
3 | - 在使用之前请确保已阅读 [koa-grace](https://github.com/xiongwilee/koa-grace)的相关文档;
4 | - 基于[vuejs-templates](https://github.com/vuejs-templates/webpack)和[grace-vue-webpack-boilerplate](https://github.com/Thunf/grace-vue-webpack-boilerplate),根据项目实际需求做了部分定制。
5 |
6 | ## 使用方法
7 |
8 | ``` bash
9 | $ cd ~/fe/app/
10 | $ git clone git@github.com:haoranw/vue-template.git grace_boilerplate
11 | $
12 | $ cd grace_boilerplate
13 | $ yarn install(recommended,or use npm install)
14 | $
15 | $ npm run dev
16 |
17 | ```
18 | 新建页面:
19 | ```
20 | $ npm run auto
21 |
22 | 依次输入项目名称和页面名称
23 | ```
24 |
25 | ## Feature
26 |
27 | - 随着项目内容的扩展,单个仓储内的vues文件越来越多。
28 | 在以往的多入口构建方案中,不支持页面的二级目录。在单个仓储内包含了多个项目的vues文件,并不能很好的区分它们属于哪一个项目模块。
29 | - 在本构建方案中,相关路径如下:
30 | ```
31 | └── vues
32 | ├── _components
33 | ├── project1 // project1
34 | └── project2 // project2
35 | ├── page1 // page1入口
36 | ├── page2
37 | └── page3
38 | ├── components
39 | ├── index.js
40 | ├── index.vue
41 | └── router.js
42 | ```
43 | - 为了配合此路径,在通过`npm run auto`来新建页面时需要输入项目名称。例如新建`project1/page1`,需要在运行命令之后依次输入`project1`、`page1`。
44 |
45 | ## Config
46 | - 模板内集成了手淘的flexible适配方案和px2rem的webpack插件
47 | 在使用中,需要将`/build/rem.conf.js`中的`remUnit`设置为"设计稿的宽度/10",列如设计稿为750px,该值应为75。
48 |
49 | - 页面中元素的宽高等数值,按照设计稿中的像素值编写即可。
50 | 例如设计稿中宽150px,高75px的元素:
51 | ```
52 | .selector {
53 | width: 150px;
54 | height: 75px;
55 | font-size: 28px; /*px*/
56 | border: 1px solid #ddd; /*no*/
57 | }
58 | ```
59 | 经过编译后会变成:
60 | ```
61 | .selector {
62 | width: 2rem;
63 | height: 1rem;
64 | border: 1px solid #ddd;
65 | }
66 | [data-dpr="1"] .selector {
67 | font-size: 14px;
68 | }
69 | [data-dpr="2"] .selector {
70 | font-size: 28px;
71 | }
72 | [data-dpr="3"] .selector {
73 | font-size: 42px;
74 | }
75 | ```
76 | 对于字体大小,建议使用px作为单位。
77 |
78 | ## Todo
79 | - [x] 新建页面时判断是否存在同名文件
80 | - [x] 新建页面时生成对应路径的controller
81 | - [x] 修改prefix配置,提高兼容性
82 | - [x] 集成flexible && pxtorem的配置
83 | - [ ] 集成vuex
84 | - [ ] 检测项目名称,必须输入已存在的项目名。独立新建项目的指令
85 | - [ ] 不同项目内的同名页面共存
86 |
--------------------------------------------------------------------------------
/build/auto_make/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const prompt = require('./prompt.js');
4 | const tools = require('./tools.js');
5 | let isExist = false,
6 | project = '',
7 | step2Text = '请给新建的业务页面命名:';
8 |
9 | function _step1() {
10 | prompt.readLine('输入项目名称:', function(data) {
11 | if (tools.illegal(data)) {
12 | step2Text = '项目名只能包含小写字母、数字和下划线,并不能以数字开头,请重新输入新建的业务页面名:'
13 | return false
14 | }
15 | project = data + '/';
16 | return true;
17 | });
18 | }
19 |
20 | function _step2() {
21 | prompt.readLine(step2Text, function(data) {
22 | if (tools.illegal(data)) {
23 | step2Text = '业务页面名只能包含小写字母、数字和下划线,并不能以数字开头,请重新输入新建的业务页面名:'
24 | } else if (tools.exist(data)) {
25 | step2Text = data + '已存在,请重新输入新建的业务页面名:'
26 | isExist = true;
27 | return false;
28 | } else {
29 | step2Text = '';
30 | tools.autoMake(project, data);
31 | return true;
32 | }
33 | })
34 | }
35 |
36 | prompt.startStepByStep({step1: _step1, step2: _step2})
37 |
--------------------------------------------------------------------------------
/build/auto_make/prompt.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 读取node.js命令行输入内容的小工具
3 | *
4 | * @api
5 | * exports.startStepByStep;
6 | * exports.readLine;
7 | *
8 | * @example
9 | * var prompt = require('prompt.js');
10 | * prompt.startStepByStep({
11 | * step1 : function(){
12 | * prompt.readLine('请输入账号:',function(username){
13 | * });
14 | * },
15 | * step2 : function(){
16 | * prompt.readLine('请输入密码:',function(password){
17 | * },true);
18 | * }
19 | * },0);
20 | *
21 | * @author zhaoxianlie
22 | */
23 |
24 | var stepMap , cursor = 0, secure, stepCallback;
25 | var userInput = '';
26 |
27 | /**
28 | * 启动prompt,且按照stepMap逐个执行 (之后再考虑升级成Promise模式)
29 | * @param _stepMap 等待执行的step队列,JSON格式,格式如:
30 | * @param firstStep 从stepMap中的第几个开始执行,默认是第一个
31 | *
32 | * @example
33 | * var prompt = require('prompt.js');
34 | * prompt.startStepByStep({
35 | * step1 : function(){},
36 | * step2 : function(){}
37 | * },0);
38 | */
39 | var startStepByStep = function (_stepMap, firstStep) {
40 | stepMap = _stepMap;
41 | dataInputting();
42 | next(firstStep);
43 | };
44 |
45 | /**
46 | * 读取命令行的输入
47 | * @param tips 提示文字
48 | * @param callback 输入结束后的回调,格式为:function(data){}
49 | * @param secure 是否为安全码输入模式,默认:false
50 | *
51 | * @example
52 | * var prompt = require('prompt.js');
53 | * prompt.readLine('请输入密码:',function(data){
54 | * var password = data;
55 | * },true);
56 | */
57 | var readLine = function (tips, callback, secure) {
58 | process.stdout.write(tips);
59 | setSecure(secure);
60 | stepCallback = callback;
61 | };
62 |
63 | /**
64 | * 获取当前处于执行中的Step
65 | * @return {*}
66 | */
67 | var getCurrentStep = function () {
68 | var step_keys = Object.keys(stepMap);
69 | return stepMap[step_keys[cursor]];
70 | };
71 |
72 | /**
73 | * 执行下一个Step;可执行stepMap中指定位置的step
74 | * @param _cursor
75 | */
76 | var next = function (_cursor) {
77 | cursor = +_cursor || cursor;
78 | var step = getCurrentStep();
79 | if (step) {
80 | step();
81 | } else {
82 | process.stdin.resume();
83 | process.stdin.end();
84 | }
85 | };
86 |
87 | /**
88 | * 设置是否为安全码输入模式,如果是,则在输入过程中,回显为“*”
89 | * @param _secure
90 | */
91 | var setSecure = function (_secure) {
92 | secure = !!_secure;
93 | process.stdin.setRawMode(secure);
94 | };
95 |
96 | /**
97 | * 输入结束时的操作
98 | */
99 | var dataFinished = function () {
100 | setSecure(false);
101 | userInput = userInput.toString().trim();
102 | var step = getCurrentStep();
103 | if (typeof step == 'function') {
104 | // 如果callback中返回true,则表示输入是合法的,可以进入下一步
105 | if (typeof stepCallback == 'function' && stepCallback(userInput)) {
106 | next(++cursor);
107 | } else {
108 | // 否则重复本步
109 | step();
110 | }
111 | userInput = '';
112 | } else {
113 | process.stdin.end();
114 | }
115 | };
116 |
117 | /**
118 | * 数据输入过程中的处理
119 | */
120 | var dataInputting = function () {
121 | process.stdin.resume();
122 | process.stdin.setEncoding('utf8');
123 | process.stdin.setRawMode(false);
124 |
125 | /**
126 | * 监听数据输入,每次 Enter 则表示输入完毕
127 | */
128 | process.stdin.on("data", function (data) {
129 | // 如果是非安全码模式,直接回显,Enter后,结束操作
130 | if (!secure) {
131 | userInput = data;
132 | dataFinished();
133 | return;
134 | }
135 |
136 | // 安全码输入模式,回显为 *
137 | switch (data) {
138 | case "\n": // 监听 Enter 键
139 | case "\r":
140 | case "\u0004":
141 | process.stdout.write('\n');
142 | dataFinished();
143 | break;
144 | case "\u0003": // Ctrl C
145 | process.exit();
146 | break;
147 | default: // 其他字符回显为 *
148 | process.stdout.write('*');
149 | userInput += data;
150 | break;
151 | }
152 | });
153 | };
154 |
155 | exports.startStepByStep = startStepByStep;
156 | exports.readLine = readLine;
157 |
--------------------------------------------------------------------------------
/build/auto_make/tools.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const glob = require('glob');
3 | const path = require('path');
4 | const fs = require('fs');
5 | require('shelljs/global');
6 |
7 | const controllerPath = path.resolve(__dirname, '../../controller/') + '/';
8 | const srcPath = path.resolve(__dirname, '../../vues/') + '/';
9 | function exec(cmd) {
10 | return require('child_process').execSync(cmd).toString().trim()
11 | }
12 |
13 | let illegal = function(name) {
14 | return !/^[a-z0-9_]+?$/.test(name) || /^\d/.test(name)
15 | }
16 |
17 | let exist = function(name) {
18 | var dirList = exec(`cd ${srcPath} && ls`).split('\n');
19 | let existNames = [];
20 | dirList.map(function(i) {
21 | let fileList = exec(`cd ${srcPath}${i} && ls`).split('\n');
22 | fileList.map(function(files) {
23 | existNames.push(files)
24 | })
25 | });
26 | return existNames.indexOf(name) > -1;
27 | }
28 |
29 | let _autoMakeController = function(project, name) {
30 | try {
31 | fs.mkdirSync(controllerPath + project)
32 | } catch (e) {}
33 | fs.writeFileSync(controllerPath + project + name + '.js', `'use strict';
34 |
35 | exports.index = function*() {
36 | yield this.bindDefault();
37 |
38 | yield this.render('${name}', {
39 | siteInfo: this.siteInfo
40 | });
41 | }
42 |
43 | `);
44 | }
45 |
46 | let _autoMakeEntryJs = function(project, name) {
47 | let Name = name[0].toUpperCase() + name.slice(1);
48 |
49 | fs.writeFileSync(srcPath + project + name + '/index.js', `// The Vue build version to load with the \`import\` command
50 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
51 | import Vue from 'vue'
52 | import VueRouter from 'vue-router'
53 | import VueResource from 'vue-resource'
54 | import routes from './router.js'
55 | import App from './index.vue'
56 |
57 | // 初始化路由
58 | /* eslint-disable no-new */
59 | const router = new VueRouter(routes)
60 |
61 | // 置入组件
62 | Vue.use(VueRouter)
63 | Vue.use(VueResource)
64 |
65 | new Vue({
66 | /**
67 | * 提供的元素只能作为挂载点。
68 | * 不同于 Vue 1.x,所有的挂载元素会被 Vue 生成的 DOM 替换。
69 | * 因此不推荐挂载root实例到 或者
上。
70 | */
71 | el: '#app',
72 | template: '',
73 | components: { App },
74 | /**
75 | * 置入路由
76 | */
77 | router
78 | })
79 | /* eslint-enable no-new */
80 | `);
81 | }
82 |
83 | let _autoMakeEntryVue = function(project, name) {
84 | fs.mkdirSync(srcPath + project + name + '/components');
85 | fs.writeFileSync(srcPath + project + name + '/index.vue', `
86 |
87 |
88 |
89 |
90 |
91 |
98 |
99 |
101 | `);
102 |
103 | fs.writeFileSync(srcPath + project + name + '/components/' + name + '.vue', `
104 |
105 |
106 |
107 |
108 |
114 |
115 |
117 | `);
118 | }
119 |
120 | let _autoMakeRouter = function(project, name) {
121 | fs.writeFileSync(srcPath + project + name + '/router.js', `module.exports = {
122 | routes: [{
123 | path: '/',
124 | component: require('./components/${name}.vue')
125 | }, {
126 | path: '*',
127 | redirect: '/'
128 | }]
129 | }
130 | `);
131 | }
132 |
133 | let autoMake = function(project, name) {
134 | _autoMakeController(project, name);
135 | mkdir('-p', srcPath + project + name)
136 | _autoMakeEntryJs(project, name);
137 | _autoMakeEntryVue(project, name);
138 | _autoMakeRouter(project, name);
139 | }
140 |
141 | exports.illegal = illegal;
142 | exports.exist = exist;
143 | exports.autoMake = autoMake;
144 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | // https://github.com/shelljs/shelljs
2 | require('./check-versions')()
3 |
4 | process.env.NODE_ENV = 'production'
5 |
6 | var ora = require('ora')
7 | var path = require('path')
8 | var chalk = require('chalk')
9 | var shell = require('shelljs')
10 | var webpack = require('webpack')
11 | var config = require('./config')
12 | var webpackConfig = require('./webpack.prod.conf')
13 |
14 | var spinner = ora('building for production...')
15 | spinner.start()
16 |
17 | var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
18 | shell.rm('-rf', assetsPath)
19 | shell.mkdir('-p', assetsPath)
20 | shell.config.silent = true
21 | shell.cp('-R', 'static/*', assetsPath)
22 | shell.config.silent = false
23 |
24 | webpack(webpackConfig, function (err, stats) {
25 | spinner.stop()
26 | if (err) throw err
27 | process.stdout.write(stats.toString({
28 | colors: true,
29 | modules: false,
30 | children: false,
31 | chunks: false,
32 | chunkModules: false
33 | }) + '\n\n')
34 |
35 | console.log(chalk.cyan(' Build complete.\n'))
36 | console.log(chalk.yellow(
37 | ' Tip: built files are meant to be served over an HTTP server.\n' +
38 | ' Opening index.html over file:// won\'t work.\n'
39 | ))
40 | })
41 |
--------------------------------------------------------------------------------
/build/check-server.js:
--------------------------------------------------------------------------------
1 | var glob = require('glob')
2 | var path = require('path')
3 | var chalk = require('chalk')
4 |
5 | function exec (cmd) {
6 | return require('child_process').execSync(cmd).toString().trim()
7 | }
8 |
9 | function callback(cb) {
10 | return 'function' === typeof cb ? cb : function(){;}
11 | }
12 |
13 | // 匹配grace下配置目录
14 | function matchServerJson(graceRoot) {
15 | return glob.sync(path.join(graceRoot, '*/config/main.development.js')) || []
16 | }
17 |
18 | // 匹配grace目录
19 | function findServerFolder(graceRoot, scb, ecb) {
20 | var confMatch = matchServerJson(graceRoot)
21 | ;(1 === confMatch.length) ? callback(scb)({
22 | serverRoot: path.resolve(confMatch[0], '../..'),
23 | serverConf: confMatch[0]
24 | }) : callback(ecb)(confMatch.length)
25 | }
26 |
27 | // 匹配中文系统
28 | function matchSysLang_zh() {
29 | var langConf = exec('command -v env && env | grep LANG').toString().toLowerCase()
30 | return /(zh|cn)/.test(langConf)
31 | }
32 |
33 | module.exports = function (graceRoot) {
34 | var warnings = [],
35 | result = {};
36 |
37 | // 匹配server
38 | findServerFolder(graceRoot, function(data) {
39 |
40 | Object.assign(result, data)
41 |
42 | }, function(err) {
43 |
44 | var message = {
45 | en: [
46 | "Fail to match koa-grace server folder, " + chalk.red("normalize folder structure") + " and retry ",
47 | "Refer to: ",
48 | chalk.green("https://github.com/xiongwilee/koa-grace/tree/v2.x#目录结构-1 ")
49 | ],
50 | zh: [
51 | "未匹配到Koa-grace正确的目录结构,请" + chalk.red("调整目录结构") + "后重试!",
52 | "请参考:",
53 | chalk.green("https://github.com/xiongwilee/koa-grace/tree/v2.x#目录结构-1 ")
54 | ]
55 | }
56 |
57 | warnings.push.call(warnings, message[matchSysLang_zh() ? 'zh' : 'en'].join('\n '))
58 |
59 | })
60 |
61 |
62 | if (warnings.length) {
63 | console.log('')
64 | console.log(chalk.yellow('To use this template, you must check following:'))
65 | console.log()
66 | for (var i = 0, len = warnings.length; i < len; i++) {
67 | var warning = warnings[i]
68 | console.log(' ' + warning)
69 | }
70 | console.log()
71 | process.exit(1)
72 | } else {
73 | return result;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | var chalk = require('chalk')
2 | var semver = require('semver')
3 | var packageConfig = require('../package.json')
4 |
5 | function exec (cmd) {
6 | return require('child_process').execSync(cmd).toString().trim()
7 | }
8 |
9 | var versionRequirements = [
10 | {
11 | name: 'node',
12 | currentVersion: semver.clean(process.version),
13 | versionRequirement: packageConfig.engines.node
14 | },
15 | {
16 | name: 'npm',
17 | currentVersion: exec('npm --version'),
18 | versionRequirement: packageConfig.engines.npm
19 | }
20 | ]
21 |
22 | module.exports = function () {
23 | var warnings = []
24 | for (var i = 0; i < versionRequirements.length; i++) {
25 | var mod = versionRequirements[i]
26 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
27 | warnings.push(mod.name + ': ' +
28 | chalk.red(mod.currentVersion) + ' should be ' +
29 | chalk.green(mod.versionRequirement)
30 | )
31 | }
32 | }
33 |
34 | if (warnings.length) {
35 | console.log('')
36 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
37 | console.log()
38 | for (var i = 0; i < warnings.length; i++) {
39 | var warning = warnings[i]
40 | console.log(' ' + warning)
41 | }
42 | console.log()
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/build/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 |
--------------------------------------------------------------------------------
/build/config/index.js:
--------------------------------------------------------------------------------
1 | // see http://vuejs-templates.github.io/webpack for documentation.
2 | var path = require('path')
3 | var projectRoot = path.resolve(__dirname, '../..'),
4 | graceRoot = path.resolve(projectRoot, '../..');
5 |
6 | /**
7 | * Check for server folder & get its path
8 | * @value graceServer
9 | * @params serverRoot
10 | * @params serverConf
11 | */
12 | var graceServer = require('../check-server')(graceRoot)
13 |
14 | var baseConf = {
15 | // Get module name automatically OR set manually
16 | moduleName: path.basename(projectRoot),
17 | projectRoot: projectRoot,
18 | serverRoot: graceServer.serverRoot,
19 | serverConf: graceServer.serverConf,
20 | outputRoot: path.resolve(graceServer.serverRoot, path.relative(graceRoot, projectRoot)),
21 | entryTemplate: path.resolve(projectRoot, 'views/_common/_template.ejs')
22 | },
23 | buildConf = {
24 | env: require('./prod.env'),
25 | assetsRoot: baseConf.outputRoot,
26 | assetsPublicPath: path.resolve('/', baseConf.moduleName) + '/',
27 | assetsSubDirectory: 'static',
28 | productionSourceMap: false || !!process.env.npm_config_map
29 | },
30 | devConf = {
31 | env: require('./dev.env'),
32 | assetsRoot: baseConf.outputRoot,
33 | assetsPublicPath: path.resolve('/', baseConf.moduleName) + '/',
34 | assetsSubDirectory: 'static',
35 | autoOpenBrowser: false,
36 | autoOpenDelay: 2000,
37 | autoOpenPage: 'home'
38 | };
39 |
40 | module.exports = {
41 | build: buildConf,
42 | base: baseConf,
43 | dev: devConf
44 | }
45 |
--------------------------------------------------------------------------------
/build/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"'
3 | }
4 |
--------------------------------------------------------------------------------
/build/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 |
--------------------------------------------------------------------------------
/build/develop.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 | var config = require('./config')
3 | var pristine = true
4 |
5 | if (!process.env.NODE_ENV) {
6 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
7 | }
8 |
9 | var ora = require('ora')
10 | var path = require('path')
11 | var chalk = require('chalk')
12 | var shell = require('shelljs')
13 | var webpack = require('webpack')
14 | var webpackConfig = process.env.NODE_ENV === 'testing'
15 | ? require('./webpack.prod.conf')
16 | : require('./webpack.dev.conf')
17 |
18 | var spinner = ora('building for development...')
19 | spinner.start()
20 |
21 | var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
22 | shell.rm('-rf', assetsPath)
23 | shell.mkdir('-p', assetsPath)
24 | shell.config.silent = true
25 | shell.cp('-R', 'static/*', assetsPath)
26 | shell.config.silent = false
27 |
28 | // automatically open browser, if not set will be false
29 | var autoOpenBrowser = !!config.dev.autoOpenBrowser
30 | var autoOpenDelay = config.dev.autoOpenDelay
31 |
32 | webpack(webpackConfig, function (err, stats) {
33 |
34 | // when env is testing, don't need open it
35 | if (pristine && process.env.NODE_ENV !== 'testing') {
36 | require('./open-browser')(autoOpenBrowser, autoOpenDelay)
37 | }
38 |
39 | spinner.stop()
40 | if (err) throw err
41 | process.stdout.write(stats.toString({
42 | colors: true,
43 | modules: false,
44 | children: false,
45 | chunks: false,
46 | chunkModules: false
47 | }) + '\n\n')
48 |
49 | console.log(chalk.cyan('\n Build complete.\n'))
50 | pristine && console.log(chalk.yellow(
51 | ' Tip: built files are meant to be served over Koa-grace server.\n' +
52 | ' Opening index.html over file:// won\'t work.',
53 | (function(){pristine = false})() || ''
54 | ))
55 | })
56 |
57 |
--------------------------------------------------------------------------------
/build/open-browser.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var opn = require('opn')
3 | var path = require('path')
4 | var chalk = require('chalk')
5 | var config = require('./config')
6 | var merge = require('webpack-merge')
7 |
8 | // lsof -i:3000
9 | var port = 3000
10 | var serverConf = {
11 | vhost: {
12 | // some host looks like:
13 | // '192.168.1.123': '< module name >'
14 | },
15 | site: {
16 | port: port
17 | }
18 | }
19 |
20 | function loopKey(obj, keys) {
21 | if (keys && keys.length === 0) return obj;
22 | var loop = (keys && keys[0] && obj && obj.hasOwnProperty && obj.hasOwnProperty(keys[0]));
23 | return loop ? loopKey(obj[keys[0]], keys.slice(1)) : undefined
24 | }
25 |
26 | function lookForHost(hosts, autoOpenBrowser) {
27 | return Object.keys(hosts).filter(function(key, value) {
28 | // if vhost-matched, use the match one
29 | return hosts[key] === config.base.moduleName
30 | })[0] || (autoOpenBrowser && writeHostConf({
31 | // if auto-open & no-vhost-matched, auto set
32 | "127.0.0.1": config.base.moduleName
33 | }) || [
34 | // if no-auto-open & no-vhost-matched, to tip
35 | '> Maybe you have not set vhost to this app: ' + chalk.cyan(config.base.moduleName),
36 | '> Please set ' + chalk.magenta('vhost') + ' in ' + chalk.magenta('/server/config/server.json') + ' like this:',
37 | ' ' + chalk.green( JSON.stringify({
38 | merge: {vhost: {"127.0.0.1": config.base.moduleName } }
39 | }, null, 2).replace(/\n/g, '\n ') ), '', ''
40 | ])
41 | }
42 |
43 | function writeHostConf(conf) {
44 |
45 | var serverJsonPath = path.resolve(config.base.serverConf, '../server.json'),
46 | serverJson = {},
47 | defaultJson = {merge: {vhost: conf } };
48 |
49 | try {
50 | serverJson = require(serverJsonPath)
51 | serverJson = merge(serverJson, defaultJson)
52 | } catch (e) {
53 | serverJson = defaultJson
54 | }
55 |
56 | setTimeout(function(){
57 | console.log(chalk.cyan('\n No vhost matched & Write vhost ' + chalk.magenta(JSON.stringify(conf)) + ' in server.json 🚀'));
58 | })
59 |
60 | fs.writeFileSync(serverJsonPath, JSON.stringify(serverJson, null, 2));
61 |
62 | return Object.keys(conf)[0]
63 | }
64 |
65 | module.exports = function(autoOpenBrowser, autoOpenDelay) {
66 |
67 | try {
68 | serverConf = require(config.base.serverConf) || serverConf
69 | } catch (e) {}
70 |
71 | port = loopKey(serverConf, ['site', 'port']) || port
72 | host = lookForHost(loopKey(serverConf, ['vhost']), autoOpenBrowser)
73 | autoOpenDelay = +autoOpenDelay || 2000
74 |
75 | var uri = ['http://', host, ':', port ].join('')
76 | var uri2show = chalk.green(chalk.underline(uri))
77 | var tip = autoOpenBrowser ? [
78 | '> Browser will open ' + uri2show + ' in ' + autoOpenDelay/1000 + 's 🔍 \n'
79 | ] : ('string' === typeof host) ? [
80 | '> Page is running at ' + uri2show + ' 🔍 \n'
81 | ] : host;
82 |
83 | setTimeout(function(){
84 | console.log('\n ' + tip.join('\n '))
85 | })
86 |
87 | autoOpenBrowser && setTimeout(function(){
88 | opn( path.join(uri, config.dev.autoOpenPage || '') )
89 | }, autoOpenDelay)
90 | }
--------------------------------------------------------------------------------
/build/rem.conf.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | baseDpr: 1, // base device pixel ratio (default: 2)
4 | threeVersion: false, // whether to generate @1x, @2x and @3x version (default: false)
5 | remVersion: true, // whether to generate rem version (default: true)
6 | remUnit: 37.5, // rem unit value (default: 75)
7 | remPrecision: 6 // rem precision (default: 6)
8 | }
9 |
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('./config')
3 | var HtmlWebpackPlugin = require('html-webpack-plugin')
4 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
5 |
6 | exports.assetsPath = function (_path) {
7 | var assetsSubDirectory = process.env.NODE_ENV === 'production'
8 | ? config.build.assetsSubDirectory
9 | : config.dev.assetsSubDirectory
10 | return path.posix.join(assetsSubDirectory, _path)
11 | }
12 |
13 | exports.cssLoaders = function (options) {
14 | options = options || {}
15 | // generate loader string to be used with extract text plugin
16 | function generateLoaders (loaders) {
17 | var sourceLoader = loaders.map(function (loader) {
18 | var extraParamChar
19 | if (/\?/.test(loader)) {
20 | loader = loader.replace(/\?/, '-loader?')
21 | extraParamChar = '&'
22 | } else {
23 | loader = loader + '-loader'
24 | extraParamChar = '?'
25 | }
26 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '')
27 | }).join('!')
28 |
29 | // Extract CSS when that option is specified
30 | // (which is the case during production build)
31 | if (options.extract) {
32 | return ExtractTextPlugin.extract({
33 | use: sourceLoader,
34 | fallback: 'vue-style-loader'
35 | })
36 | } else {
37 | return ['vue-style-loader', sourceLoader].join('!')
38 | }
39 | }
40 |
41 | // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
42 | return {
43 | css: generateLoaders(['css']),
44 | postcss: generateLoaders(['css']),
45 | less: generateLoaders(['css', 'less']),
46 | sass: generateLoaders(['css', 'sass?indentedSyntax']),
47 | scss: generateLoaders(['css', 'sass']),
48 | stylus: generateLoaders(['css', 'stylus']),
49 | styl: generateLoaders(['css', 'stylus'])
50 | }
51 | }
52 |
53 | // Generate loaders for standalone style files (outside of .vue)
54 | exports.styleLoaders = function (options) {
55 | var output = []
56 | var loaders = exports.cssLoaders(options)
57 | for (var extension in loaders) {
58 | var loader = loaders[extension]
59 | output.push({
60 | test: new RegExp('\\.' + extension + '$'),
61 | loader: loader
62 | })
63 | }
64 | return output
65 | }
66 |
67 | // Multiple entry for html
68 | exports.setEntrys = function(conf) {
69 |
70 | conf.entry = conf.entry || {}
71 | conf.plugins = conf.plugins || []
72 |
73 | var isNotDev = 'development' !== process.env.NODE_ENV
74 | var htmlConfig = {
75 | minify: {
76 | removeComments: isNotDev,
77 | collapseWhitespace: isNotDev,
78 | removeAttributeQuotes: isNotDev
79 | }
80 | }
81 |
82 | var entries = Object.keys(conf.entry)
83 | entries.map(function(ent) {
84 | if ('common' === ent) return;
85 | // generate dist template.html with correct asset hash for caching.
86 | // you can customize output by editing template.html
87 | // see https://github.com/ampedandwired/html-webpack-plugin
88 | conf.plugins.push(new HtmlWebpackPlugin(Object.assign({
89 | filename: `${path.resolve(config.base.outputRoot, 'views')}/${ent}/index.html`,
90 | chunks: [ent, 'vendor', 'manifest', 'common'],
91 | template: config.base.entryTemplate,
92 | inject: false,
93 | chunksSortMode: 'dependency',
94 | module: config.base.moduleName
95 | }, htmlConfig)))
96 | });
97 |
98 | return conf;
99 | }
100 |
--------------------------------------------------------------------------------
/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils')
2 | var config = require('./config')
3 | var isProduction = process.env.NODE_ENV === 'production'
4 | var px2remConfig = require('./rem.conf')
5 |
6 | module.exports = {
7 | loaders: utils.cssLoaders({
8 | sourceMap: isProduction
9 | ? config.build.productionSourceMap
10 | : config.dev.cssSourceMap,
11 | extract: isProduction
12 | }),
13 | postcss: [
14 | require('postcss-px2rem')(px2remConfig),
15 | require('autoprefixer')
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var config = require('./config')
4 | var vueLoaderConfig = require('./vue-loader.conf')
5 | var CopyWebpackPlugin = require('copy-webpack-plugin')
6 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
7 | var px2remConfig = require('./rem.conf')
8 | function resolve(dir) {
9 | return path.join(__dirname, '..', dir)
10 | }
11 |
12 | function exec(cmd) {
13 | return require('child_process').execSync(cmd).toString().trim()
14 | }
15 |
16 | function entries(opt) {
17 | var fileList = exec('cd ./vues && ls').split('\n');
18 | return Object.assign.apply(null, [].concat(fileList.map(function(item) {
19 | var obj = {};
20 | if (!/^_[\w-]+$/.test(item)) {
21 | exec(`cd ./vues/${item} && ls`).split('\n').map(function(dir) {
22 | obj[dir] = `./vues/${item}/` + dir + '/'
23 | })
24 | }
25 | return obj
26 | }), opt))
27 | }
28 |
29 | const webpackConfig = {
30 | entry: entries({
31 | // folderName: './vues/folderName'
32 | common: [
33 | './static/css/reset.scss',
34 | ]
35 | }),
36 | output: {
37 | path: config.build.assetsRoot,
38 | filename: utils.assetsPath('js/[name]/build.js?[hash:7]'),
39 | publicPath: process.env.NODE_ENV === 'production'
40 | ? config.build.assetsPublicPath
41 | : config.dev.assetsPublicPath
42 | },
43 | resolve: {
44 | extensions: [
45 | '.js', '.vue', '.json'
46 | ],
47 | modules: [
48 | resolve('vues'), resolve('static'), resolve('node_modules')
49 | ],
50 | alias: {
51 | 'vue$': 'vue/dist/vue.esm.js', // 'vue/dist/vue.common.js' for webpack 1
52 | 'vues': resolve('vues'),
53 | 'static': resolve('static'),
54 | 'image': resolve('static/image'),
55 | 'components': resolve('vues/_components')
56 | }
57 | },
58 | module: {
59 | loaders: [
60 | {
61 | test: /\.css$/,
62 | loader: "style-loader!css-loader!postcss-loader",
63 | options: {
64 | plugins: function() {
65 | return [
66 | require('autoprefixer'),
67 | require('postcss-px2rem')(px2remConfig)
68 | ];
69 | }
70 | }
71 | }
72 | ],
73 | rules: [
74 | {
75 | test: /\.vue$/,
76 | loader: 'vue-loader',
77 | options: vueLoaderConfig
78 | }, {
79 | test: /\.js$/,
80 | loader: 'babel-loader',
81 | include: [
82 | resolve('vues'), resolve('test')
83 | ],
84 | exclude: [resolve('node_modules')]
85 | }, {
86 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
87 | loader: 'url-loader',
88 | query: {
89 | limit: 1024 * 8,
90 | name: utils.assetsPath('image/[name].[ext]?[hash:10]')
91 | }
92 | }, {
93 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
94 | loader: 'url-loader',
95 | query: {
96 | limit: 1024 * 8,
97 | name: utils.assetsPath('fonts/[name].[ext]?[hash:10]')
98 | }
99 | }
100 | ]
101 | },
102 | plugins: [
103 | new CopyWebpackPlugin([
104 | {
105 | from: {
106 | glob: './controller/**/*'
107 | },
108 | to: `${config.base.outputRoot}`
109 | }, {
110 | from: {
111 | glob: './deploy/**/*'
112 | },
113 | to: `${config.base.outputRoot}`
114 | }, {
115 | from: {
116 | glob: './mock/**/*'
117 | },
118 | to: `${config.base.outputRoot}`
119 | }, {
120 | from: {
121 | glob: './views/**/!(_*)'
122 | },
123 | to: `${config.base.outputRoot}`
124 | }, {
125 | from: {
126 | glob: './static/**/!(_*)'
127 | },
128 | to: `${config.base.outputRoot}`
129 | }
130 | ]),
131 | new ExtractTextPlugin({filename: utils.assetsPath('css/[name]/build.css?[contenthash:10]')})
132 | ]
133 | }
134 | const vuxLoader = require('vux-loader')
135 |
136 | module.exports = vuxLoader.merge(webpackConfig, {
137 | options: {},
138 | plugins: [
139 | {
140 | name: 'vux-ui'
141 | }
142 | ]
143 | })
144 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var config = require('./config')
4 | var webpack = require('webpack')
5 | var merge = require('webpack-merge')
6 | var baseWebpackConfig = require('./webpack.base.conf')
7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
8 |
9 | module.exports = merge(utils.setEntrys(baseWebpackConfig), {
10 | watch: true,
11 | module: {
12 | rules: utils.styleLoaders({
13 | sourceMap: config.dev.cssSourceMap
14 | })
15 | },
16 | output: {
17 | path: config.build.assetsRoot,
18 | filename: utils.assetsPath('js/[name]/build.js?[chunkhash:10]'),
19 | chunkFilename: utils.assetsPath('js/[id]/chunk.js?[chunkhash:10]')
20 | },
21 | // cheap-module-eval-source-map is faster for development
22 | devtool: '#cheap-module-eval-source-map',
23 | plugins: [
24 | new webpack.DefinePlugin({
25 | 'process.env': config.dev.env
26 | }),
27 | new webpack.NoEmitOnErrorsPlugin(),
28 | new FriendlyErrorsPlugin()
29 | ]
30 | })
31 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var utils = require('./utils')
3 | var config = require('./config')
4 | var webpack = require('webpack')
5 | var merge = require('webpack-merge')
6 | var baseWebpackConfig = require('./webpack.base.conf')
7 | var HtmlWebpackPlugin = require('html-webpack-plugin')
8 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
9 | var env = process.env.NODE_ENV === 'testing'
10 | ? require('./config/test.env')
11 | : config.build.env
12 |
13 | var webpackConfig = merge(utils.setEntrys(baseWebpackConfig), {
14 | module: {
15 | rules: utils.styleLoaders({
16 | sourceMap: config.build.productionSourceMap,
17 | extract: true
18 | })
19 | },
20 | devtool: config.build.productionSourceMap ? '#source-map' : false,
21 | output: {
22 | path: config.build.assetsRoot,
23 | filename: utils.assetsPath('js/[name]/build.js?[chunkhash:10]'),
24 | chunkFilename: utils.assetsPath('js/[id]/chunk.js?[chunkhash:10]')
25 | },
26 | plugins: [
27 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
28 | new webpack.DefinePlugin({
29 | 'process.env': env
30 | }),
31 | new webpack.optimize.UglifyJsPlugin({
32 | compress: {
33 | warnings: false
34 | },
35 | sourceMap: true
36 | }),
37 | // https://github.com/vuejs-templates/webpack/pull/514/commits/1b535cf38cae6a07e07cd10f26dcb433e7301e52
38 | new webpack.LoaderOptionsPlugin({
39 | minimize: true
40 | }),
41 | // extract css into its own file
42 | new ExtractTextPlugin({
43 | filename: utils.assetsPath('css/[name]/build.css?[contenthash:10]')
44 | }),
45 | // split vendor js into its own file
46 | new webpack.optimize.CommonsChunkPlugin({
47 | name: 'vendor',
48 | minChunks: function (module, count) {
49 | // any required modules inside node_modules are extracted to vendor
50 | return (
51 | module.resource &&
52 | /\.js$/.test(module.resource) &&
53 | module.resource.indexOf(
54 | path.join(__dirname, '../node_modules')
55 | ) === 0
56 | )
57 | }
58 | }),
59 | // extract webpack runtime and module manifest to its own file in order to
60 | // prevent vendor hash from being updated whenever app bundle is updated
61 | new webpack.optimize.CommonsChunkPlugin({
62 | name: 'manifest',
63 | chunks: ['vendor']
64 | })
65 | ]
66 | })
67 |
68 | module.exports = webpackConfig
69 |
--------------------------------------------------------------------------------
/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 | plugins: [
15 | new webpack.DefinePlugin({
16 | 'process.env': require('./config/test.env')
17 | })
18 | ]
19 | })
20 |
21 | // no need for app entry during tests
22 | delete webpackConfig.entry
23 |
24 | module.exports = webpackConfig
25 |
--------------------------------------------------------------------------------
/controller/ajax.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | // 接口示例:proxy转发
4 | exports.demo = function*() {
5 | // 此处示例:通过node,并发请求其他API
6 | // 假设我们访问koa-grace服务自己的其他API,接口见下文
7 | yield this.proxy({
8 | test1: `http://${this.host}/ajax/test/1`,
9 | test2: `http://${this.host}/ajax/test/2`,
10 | test3: `http://${this.host}/ajax/test/3`,
11 | // 顺便请求一下作者的github信息
12 | thunf: `github_api:users/thunf`
13 | })
14 |
15 | // 假装处理数据
16 | this.backData.grace = Object.assign({
17 | grace: 'http://7xrhcw.com1.z0.glb.clouddn.com/grace-qq.png'
18 | }, this.backData.thunf)
19 |
20 | this.body = this.backData
21 | }
22 |
23 | // 接口示例:静态数据
24 | exports.test = function*() {
25 | // 配置header, 此处示例如何允许跨域
26 | this.set('Access-Control-Allow-Origin', this.headers.origin)
27 | this.set('Access-Control-Allow-Methods', 'POST, GET')
28 | this.set('Access-Control-Allow-Headers', 'content-type')
29 |
30 | // 接收参数
31 | let _text = this.params.text
32 |
33 | // 返回数据
34 | this.body = {
35 | code: 0,
36 | data: {
37 | text: `test data: ${_text}`
38 | },
39 | message: 'success'
40 | }
41 | }
42 | exports.test.__regular__ = '/:text'
43 |
--------------------------------------------------------------------------------
/controller/defaultCtrl.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function*() {
4 |
5 | // 网站信息
6 | this.siteInfo = {
7 | title: 'demo',
8 | path: this.path,
9 | url: this.request.url,
10 | href: this.request.href,
11 | year: new Date().getFullYear(),
12 | }
13 |
14 | }
15 |
16 | // 设置为非路由
17 | module.exports.__controller__ = false;
--------------------------------------------------------------------------------
/controller/mobile/home.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.index = function*() {
4 | yield this.bindDefault();
5 |
6 | yield this.render('home', {
7 | siteInfo: this.siteInfo
8 | });
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/controller/pc/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.index = function*() {
4 | yield this.bindDefault();
5 |
6 | yield this.render('index', {
7 | siteInfo: this.siteInfo
8 | });
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/deploy/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # ====================================================
4 | # pushtest时的操作:
5 | # 1、进入cli目录
6 | # 2、执行编译
7 | # 3、编译最终的目的是在server/app产出编译之后的文件
8 | # 完成编译
9 | # ====================================================
10 | mod="grace_boilerplate"
11 |
12 | # 编译当前代码
13 | npm run build
14 |
15 | # 加戳
16 | cd ../../build/
17 | gulp build --env=production --mod=${mod}
18 |
19 | exit $?
20 |
--------------------------------------------------------------------------------
/mock/demo.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": "0",
3 | "data": [],
4 | "message": "success"
5 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grace_boilerplate",
3 | "version": "2.0.0",
4 | "description": "a boilerplate for vue2-project run on koa-grace",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "node build/develop.js",
8 | "build": "node build/build.js",
9 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
10 | "e2e": "node test/e2e/runner.js",
11 | "test": "echo \"Test will be coming soon\"",
12 | "lint": "eslint --ext .js,.vue controller vues test/unit/specs test/e2e/specs",
13 | "auto": "node build/auto_make/index.js"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/haoranw/grace-vue2-webpack-boilerplate.git"
18 | },
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/haoranw/grace-vue2-webpack-boilerplate/issues"
22 | },
23 | "homepage": "https://github.com/haoranw/grace-vue2-webpack-boilerplate#readme",
24 | "dependencies": {
25 | "vue": "^2.2.6"
26 | },
27 | "devDependencies": {
28 | "ansi-html": "^0.0.7",
29 | "ansi-regex": "^2.1.1",
30 | "autoprefixer": "^6.7.7",
31 | "babel-core": "^6.22.1",
32 | "babel-eslint": "^7.1.1",
33 | "babel-loader": "^6.2.10",
34 | "babel-plugin-istanbul": "^3.1.2",
35 | "babel-plugin-transform-runtime": "^6.22.0",
36 | "babel-preset-es2015": "^6.22.0",
37 | "babel-preset-stage-2": "^6.22.0",
38 | "babel-register": "^6.22.0",
39 | "chai": "^3.5.0",
40 | "chalk": "^1.1.3",
41 | "copy-webpack-plugin": "^4.0.1",
42 | "cross-env": "^3.1.4",
43 | "cross-spawn": "^5.0.1",
44 | "css-loader": "^0.26.1",
45 | "extract-text-webpack-plugin": "^2.0.0-rc.2",
46 | "file-loader": "^0.10.0",
47 | "friendly-errors-webpack-plugin": "^1.1.3",
48 | "function-bind": "^1.1.0",
49 | "glob": "^7.1.1",
50 | "html-entities": "^1.2.0",
51 | "html-webpack-plugin": "^2.28.0",
52 | "inject-loader": "^2.0.1",
53 | "less": "^2.7.2",
54 | "less-loader": "^2.2.3",
55 | "lodash": "^4.17.4",
56 | "lolex": "^1.5.2",
57 | "node-sass": "^4.5.0",
58 | "opn": "^4.0.2",
59 | "optimize-css-assets-webpack-plugin": "^1.3.0",
60 | "ora": "^1.1.0",
61 | "postcss-loader": "^1.3.1",
62 | "sass-loader": "^6.0.2",
63 | "semver": "^5.3.0",
64 | "shelljs": "^0.7.6",
65 | "strip-ansi": "^3.0.1",
66 | "url-loader": "^0.5.7",
67 | "vue-directive-touch": "^1.0.3",
68 | "vue-infinite-scroll": "^2.0.0",
69 | "vue-loader": "^10.3.0",
70 | "vue-resource": "^1.2.0",
71 | "vue-router": "^2.2.0",
72 | "vue-style-loader": "^2.0.0",
73 | "vue-template-compiler": "^2.2.6",
74 | "vuex-i18n": "^1.3.2",
75 | "vux": "^2.1.0",
76 | "vux-loader": "^1.0.46",
77 | "webpack": "^2.2.1",
78 | "webpack-bundle-analyzer": "^2.2.1",
79 | "webpack-dev-middleware": "^1.10.0",
80 | "webpack-hot-middleware": "^2.16.1",
81 | "webpack-merge": "^2.6.1"
82 | },
83 | "engines": {
84 | "node": ">= 4.0.0",
85 | "npm": ">= 2.1.5"
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/static/css/reset.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * ===================================
3 | * RESET CSS BY U SELF ~
4 | * ===================================
5 | */
6 | /*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
7 | /**
8 | * 1. Change the default font family in all browsers (opinionated).
9 | * 2. Correct the line height in all browsers.
10 | * 3. Prevent adjustments of font size after orientation changes in
11 | * IE on Windows Phone and in iOS.
12 | */
13 | /* Document
14 | ========================================================================== */
15 | html {
16 | font-family: sans-serif;
17 | /* 1 */
18 | line-height: 1.15;
19 | /* 2 */
20 | -ms-text-size-adjust: 100%;
21 | /* 3 */
22 | -webkit-text-size-adjust: 100%;
23 | /* 3 */
24 | }
25 | /* Sections
26 | ========================================================================== */
27 | /**
28 | * Remove the margin in all browsers (opinionated).
29 | */
30 | body {
31 | margin: 0;
32 | }
33 |
34 | i {
35 | font-style: normal;
36 | }
37 |
38 | li,
39 | ul {
40 | list-style: none;
41 | margin: 0;
42 | padding: 0;
43 | }
44 |
45 | button,
46 | p {
47 | margin: 0;
48 | padding: 0;
49 | }
50 | /**
51 | * Add the correct display in IE 9-.
52 | */
53 | article,
54 | aside,
55 | footer,
56 | header,
57 | nav,
58 | section {
59 | display: block;
60 | }
61 | /**
62 | * Correct the font size and margin on `h1` elements within `section` and
63 | * `article` contexts in Chrome, Firefox, and Safari.
64 | */
65 | h1,
66 | h2 {
67 | margin: 0;
68 | }
69 |
70 | h1 {
71 | font-size: 2em;
72 | margin: 0.67em 0;
73 | }
74 | /* Grouping content
75 | ========================================================================== */
76 | /**
77 | * Add the correct display in IE 9-.
78 | * 1. Add the correct display in IE.
79 | */
80 | figcaption,
81 | figure,
82 | main {
83 | /* 1 */
84 | display: block;
85 | }
86 | /**
87 | * Add the correct margin in IE 8.
88 | */
89 | figure {
90 | margin: 1em 40px;
91 | }
92 | /**
93 | * 1. Add the correct box sizing in Firefox.
94 | * 2. Show the overflow in Edge and IE.
95 | */
96 | hr {
97 | box-sizing: content-box;
98 | /* 1 */
99 | height: 0;
100 | /* 1 */
101 | overflow: visible;
102 | /* 2 */
103 | }
104 | /**
105 | * 1. Correct the inheritance and scaling of font size in all browsers.
106 | * 2. Correct the odd `em` font sizing in all browsers.
107 | */
108 | pre {
109 | font-family: monospace, monospace;
110 | /* 1 */
111 | font-size: 1em;
112 | /* 2 */
113 | }
114 | /* Text-level semantics
115 | ========================================================================== */
116 | /**
117 | * 1. Remove the gray background on active links in IE 10.
118 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
119 | */
120 | a {
121 | background-color: transparent;
122 | /* 1 */
123 | -webkit-text-decoration-skip: objects;
124 | /* 2 */
125 | text-decoration: none;
126 | -webkit-tap-highlight-color: rgba(0,0,0,0);
127 | }
128 | /**
129 | * Remove the outline on focused links when they are also active or hovered
130 | * in all browsers (opinionated).
131 | */
132 | a:active,
133 | a:hover {
134 | outline-width: 0;
135 | }
136 | /**
137 | * 1. Remove the bottom border in Firefox 39-.
138 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
139 | */
140 | abbr[title] {
141 | border-bottom: none;
142 | /* 1 */
143 | text-decoration: underline;
144 | /* 2 */
145 | text-decoration: underline dotted;
146 | /* 2 */
147 | }
148 | /**
149 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6.
150 | */
151 | b,
152 | strong {
153 | font-weight: inherit;
154 | }
155 | /**
156 | * Add the correct font weight in Chrome, Edge, and Safari.
157 | */
158 | b,
159 | strong {
160 | font-weight: bolder;
161 | }
162 | /**
163 | * 1. Correct the inheritance and scaling of font size in all browsers.
164 | * 2. Correct the odd `em` font sizing in all browsers.
165 | */
166 | code,
167 | kbd,
168 | samp {
169 | font-family: monospace, monospace;
170 | /* 1 */
171 | font-size: 1em;
172 | /* 2 */
173 | }
174 | /**
175 | * Add the correct font style in Android 4.3-.
176 | */
177 | dfn {
178 | font-style: italic;
179 | }
180 | /**
181 | * Add the correct background and color in IE 9-.
182 | */
183 | mark {
184 | background-color: #ff0;
185 | color: #000;
186 | }
187 | /**
188 | * Add the correct font size in all browsers.
189 | */
190 | small {
191 | font-size: 80%;
192 | }
193 | /**
194 | * Prevent `sub` and `sup` elements from affecting the line height in
195 | * all browsers.
196 | */
197 | sub,
198 | sup {
199 | font-size: 75%;
200 | line-height: 0;
201 | position: relative;
202 | vertical-align: baseline;
203 | }
204 |
205 | sub {
206 | bottom: -0.25em;
207 | }
208 |
209 | sup {
210 | top: -0.5em;
211 | }
212 | /* Embedded content
213 | ========================================================================== */
214 | /**
215 | * Add the correct display in IE 9-.
216 | */
217 | audio,
218 | video {
219 | display: inline-block;
220 | }
221 | /**
222 | * Add the correct display in iOS 4-7.
223 | */
224 | audio:not([controls]) {
225 | display: none;
226 | height: 0;
227 | }
228 | /**
229 | * Remove the border on images inside links in IE 10-.
230 | */
231 | img {
232 | border-style: none;
233 | }
234 | /**
235 | * Hide the overflow in IE.
236 | */
237 | svg:not(:root) {
238 | overflow: hidden;
239 | }
240 | /* Forms
241 | ========================================================================== */
242 | /**
243 | * 1. Change the font styles in all browsers (opinionated).
244 | * 2. Remove the margin in Firefox and Safari.
245 | */
246 | button,
247 | input,
248 | optgroup,
249 | select,
250 | textarea {
251 | font-family: sans-serif;
252 | /* 1 */
253 | font-size: 100%;
254 | /* 1 */
255 | line-height: 1.15;
256 | /* 1 */
257 | margin: 0;
258 | /* 2 */
259 | outline: none;
260 | -webkit-tap-highlight-color: rgba(0,0,0,0);
261 | }
262 | /**
263 | * Show the overflow in IE.
264 | * 1. Show the overflow in Edge.
265 | */
266 | button,
267 | input {
268 | /* 1 */
269 | overflow: visible;
270 | }
271 | /**
272 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
273 | * 1. Remove the inheritance of text transform in Firefox.
274 | */
275 | button,
276 | select {
277 | /* 1 */
278 | text-transform: none;
279 | }
280 | /**
281 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
282 | * controls in Android 4.
283 | * 2. Correct the inability to style clickable types in iOS and Safari.
284 | */
285 | button, html [type="button"],
286 | /* 1 */
287 | [type="reset"],
288 | [type="submit"] {
289 | -webkit-appearance: button;
290 | /* 2 */
291 | }
292 | /**
293 | * Remove the inner border and padding in Firefox.
294 | */
295 | [type="button"]::-moz-focus-inner,
296 | [type="reset"]::-moz-focus-inner,
297 | [type="submit"]::-moz-focus-inner,
298 | button::-moz-focus-inner {
299 | border-style: none;
300 | padding: 0;
301 | }
302 | /**
303 | * Restore the focus styles unset by the previous rule.
304 | */
305 | [type="button"]:-moz-focusring,
306 | [type="reset"]:-moz-focusring,
307 | [type="submit"]:-moz-focusring,
308 | button:-moz-focusring {
309 | outline: 1px dotted ButtonText;
310 | }
311 | /**
312 | * Change the border, margin, and padding in all browsers (opinionated).
313 | */
314 | fieldset {
315 | border: 1px solid #c0c0c0;
316 | margin: 0 2px;
317 | padding: 0.35em 0.625em 0.75em;
318 | }
319 | /**
320 | * 1. Correct the text wrapping in Edge and IE.
321 | * 2. Correct the color inheritance from `fieldset` elements in IE.
322 | * 3. Remove the padding so developers are not caught out when they zero out
323 | * `fieldset` elements in all browsers.
324 | */
325 | legend {
326 | box-sizing: border-box;
327 | /* 1 */
328 | color: inherit;
329 | /* 2 */
330 | display: table;
331 | /* 1 */
332 | max-width: 100%;
333 | /* 1 */
334 | padding: 0;
335 | /* 3 */
336 | white-space: normal;
337 | /* 1 */
338 | }
339 | /**
340 | * 1. Add the correct display in IE 9-.
341 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
342 | */
343 | progress {
344 | display: inline-block;
345 | /* 1 */
346 | vertical-align: baseline;
347 | /* 2 */
348 | }
349 | /**
350 | * Remove the default vertical scrollbar in IE.
351 | */
352 | textarea {
353 | overflow: auto;
354 | }
355 | /**
356 | * 1. Add the correct box sizing in IE 10-.
357 | * 2. Remove the padding in IE 10-.
358 | */
359 | [type="checkbox"],
360 | [type="radio"] {
361 | box-sizing: border-box;
362 | /* 1 */
363 | padding: 0;
364 | /* 2 */
365 | }
366 | /**
367 | * Correct the cursor style of increment and decrement buttons in Chrome.
368 | */
369 | [type="number"]::-webkit-inner-spin-button,
370 | [type="number"]::-webkit-outer-spin-button {
371 | height: auto;
372 | }
373 | /**
374 | * 1. Correct the odd appearance in Chrome and Safari.
375 | * 2. Correct the outline style in Safari.
376 | */
377 | [type="search"] {
378 | -webkit-appearance: textfield;
379 | /* 1 */
380 | outline-offset: -2px;
381 | /* 2 */
382 | }
383 | /**
384 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
385 | */
386 | [type="search"]::-webkit-search-cancel-button,
387 | [type="search"]::-webkit-search-decoration {
388 | -webkit-appearance: none;
389 | }
390 | /**
391 | * 1. Correct the inability to style clickable types in iOS and Safari.
392 | * 2. Change font properties to `inherit` in Safari.
393 | */
394 | ::-webkit-file-upload-button {
395 | -webkit-appearance: button;
396 | /* 1 */
397 | font: inherit;
398 | /* 2 */
399 | }
400 | /* Interactive
401 | ========================================================================== */
402 | /*
403 | * Add the correct display in IE 9-.
404 | * 1. Add the correct display in Edge, IE, and Firefox.
405 | */
406 | details,
407 | /* 1 */
408 | menu {
409 | display: block;
410 | }
411 | /*
412 | * Add the correct display in all browsers.
413 | */
414 | summary {
415 | display: list-item;
416 | }
417 | /* Scripting
418 | ========================================================================== */
419 | /**
420 | * Add the correct display in IE 9-.
421 | */
422 | canvas {
423 | display: inline-block;
424 | }
425 | /**
426 | * Add the correct display in IE.
427 | */
428 | template {
429 | display: none;
430 | }
431 | /* Hidden
432 | ========================================================================== */
433 | /**
434 | * Add the correct display in IE 10-.
435 | */
436 | [hidden] {
437 | display: none;
438 | }
439 |
--------------------------------------------------------------------------------
/static/fonts/iconfont.css:
--------------------------------------------------------------------------------
1 |
2 | @font-face {font-family: "iconfont";
3 | src: url('iconfont.eot?t=1487148725145'); /* IE9*/
4 | src: url('iconfont.eot?t=1487148725145#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url('iconfont.woff?t=1487148725145') format('woff'), /* chrome, firefox */
6 | url('iconfont.ttf?t=1487148725145') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7 | url('iconfont.svg?t=1487148725145#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family:"iconfont" !important;
12 | font-size:16px;
13 | font-style:normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-Expression_:before { content: "\e613"; }
19 |
20 | .icon-Expression_4:before { content: "\e617"; }
21 |
22 | .icon-Expression_9:before { content: "\e61c"; }
23 |
24 | .icon-Expression_10:before { content: "\e61d"; }
25 |
26 | .icon-Expression_14:before { content: "\e621"; }
27 |
28 | .icon-Expression_15:before { content: "\e622"; }
29 |
30 |
--------------------------------------------------------------------------------
/static/fonts/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etheranl/vue-template/0b9438d16b00cc48bfbbaf2ffc51f4df2fc10124/static/fonts/iconfont.eot
--------------------------------------------------------------------------------
/static/fonts/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
89 |
--------------------------------------------------------------------------------
/static/fonts/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etheranl/vue-template/0b9438d16b00cc48bfbbaf2ffc51f4df2fc10124/static/fonts/iconfont.ttf
--------------------------------------------------------------------------------
/static/fonts/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etheranl/vue-template/0b9438d16b00cc48bfbbaf2ffc51f4df2fc10124/static/fonts/iconfont.woff
--------------------------------------------------------------------------------
/static/image/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etheranl/vue-template/0b9438d16b00cc48bfbbaf2ffc51f4df2fc10124/static/image/favicon.ico
--------------------------------------------------------------------------------
/static/image/logo-grace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etheranl/vue-template/0b9438d16b00cc48bfbbaf2ffc51f4df2fc10124/static/image/logo-grace.png
--------------------------------------------------------------------------------
/static/js/common/flexible.js:
--------------------------------------------------------------------------------
1 | ;(function(win, lib) {
2 | var doc = win.document;
3 | var docEl = doc.documentElement;
4 | var metaEl = doc.querySelector('meta[name="viewport"]');
5 | var flexibleEl = doc.querySelector('meta[name="flexible"]');
6 | var dpr = 0;
7 | var scale = 0;
8 | var tid;
9 | var flexible = lib.flexible || (lib.flexible = {});
10 |
11 | if (metaEl) {
12 | console.warn('将根据已有的meta标签来设置缩放比例');
13 | var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
14 | if (match) {
15 | scale = parseFloat(match[1]);
16 | dpr = parseInt(1 / scale);
17 | }
18 | } else if (flexibleEl) {
19 | var content = flexibleEl.getAttribute('content');
20 | if (content) {
21 | var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
22 | var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
23 | if (initialDpr) {
24 | dpr = parseFloat(initialDpr[1]);
25 | scale = parseFloat((1 / dpr).toFixed(2));
26 | }
27 | if (maximumDpr) {
28 | dpr = parseFloat(maximumDpr[1]);
29 | scale = parseFloat((1 / dpr).toFixed(2));
30 | }
31 | }
32 | }
33 |
34 | if (!dpr && !scale) {
35 | var isAndroid = win.navigator.appVersion.match(/android/gi);
36 | var isIPhone = win.navigator.appVersion.match(/iphone/gi);
37 | var devicePixelRatio = win.devicePixelRatio;
38 | if (isIPhone) {
39 | // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
40 | if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
41 | dpr = 3;
42 | } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
43 | dpr = 2;
44 | } else {
45 | dpr = 1;
46 | }
47 | } else {
48 | // 其他设备下,仍旧使用1倍的方案
49 | dpr = 1;
50 | }
51 | scale = 1 / dpr;
52 | }
53 |
54 | docEl.setAttribute('data-dpr', dpr);
55 | if (!metaEl) {
56 | metaEl = doc.createElement('meta');
57 | metaEl.setAttribute('name', 'viewport');
58 | metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
59 | if (docEl.firstElementChild) {
60 | docEl.firstElementChild.appendChild(metaEl);
61 | } else {
62 | var wrap = doc.createElement('div');
63 | wrap.appendChild(metaEl);
64 | doc.write(wrap.innerHTML);
65 | }
66 | }
67 |
68 | function refreshRem(){
69 | var width = docEl.getBoundingClientRect().width;
70 | if (width / dpr > 540) {
71 | width = 540 * dpr;
72 | }
73 | var rem = width / 10;
74 | docEl.style.fontSize = rem + 'px';
75 | flexible.rem = win.rem = rem;
76 | }
77 |
78 | win.addEventListener('resize', function() {
79 | clearTimeout(tid);
80 | tid = setTimeout(refreshRem, 300);
81 | }, false);
82 | win.addEventListener('pageshow', function(e) {
83 | if (e.persisted) {
84 | clearTimeout(tid);
85 | tid = setTimeout(refreshRem, 300);
86 | }
87 | }, false);
88 |
89 | if (doc.readyState === 'complete') {
90 | doc.body.style.fontSize = 12 * dpr + 'px';
91 | } else {
92 | doc.addEventListener('DOMContentLoaded', function(e) {
93 | doc.body.style.fontSize = 12 * dpr + 'px';
94 | }, false);
95 | }
96 |
97 |
98 | refreshRem();
99 |
100 | flexible.dpr = win.dpr = dpr;
101 | flexible.refreshRem = refreshRem;
102 | flexible.rem2px = function(d) {
103 | var val = parseFloat(d) * this.rem;
104 | if (typeof d === 'string' && d.match(/rem$/)) {
105 | val += 'px';
106 | }
107 | return val;
108 | }
109 | flexible.px2rem = function(d) {
110 | var val = parseFloat(d) / this.rem;
111 | if (typeof d === 'string' && d.match(/px$/)) {
112 | val += 'rem';
113 | }
114 | return val;
115 | }
116 |
117 | })(window, window['lib'] || (window['lib'] = {}));
--------------------------------------------------------------------------------
/test/e2e/custom-assertions/elementCount.js:
--------------------------------------------------------------------------------
1 | // A custom Nightwatch assertion.
2 | // the name of the method is the filename.
3 | // can be used in tests like this:
4 | //
5 | // browser.assert.elementCount(selector, count)
6 | //
7 | // for how to write custom assertions see
8 | // http://nightwatchjs.org/guide#writing-custom-assertions
9 | exports.assertion = function (selector, count) {
10 | this.message = 'Testing if element <' + selector + '> has count: ' + count
11 | this.expected = count
12 | this.pass = function (val) {
13 | return val === this.expected
14 | }
15 | this.value = function (res) {
16 | return res.value
17 | }
18 | this.command = function (cb) {
19 | var self = this
20 | return this.api.execute(function (selector) {
21 | return document.querySelectorAll(selector).length
22 | }, [selector], function (res) {
23 | cb.call(self, res)
24 | })
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/e2e/nightwatch.conf.js:
--------------------------------------------------------------------------------
1 | require('babel-register')
2 | var config = require('../../build/config')
3 |
4 | // http://nightwatchjs.org/guide#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 |
--------------------------------------------------------------------------------
/test/e2e/runner.js:
--------------------------------------------------------------------------------
1 | // 1. start the dev server using production config
2 | process.env.NODE_ENV = 'testing'
3 | var server = require('../../build/dev-server.js')
4 |
5 | // 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)
13 | if (opts.indexOf('--config') === -1) {
14 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
15 | }
16 | if (opts.indexOf('--env') === -1) {
17 | opts = opts.concat(['--env', 'chrome'])
18 | }
19 |
20 | var spawn = require('cross-spawn')
21 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
22 |
23 | runner.on('exit', function (code) {
24 | server.close()
25 | process.exit(code)
26 | })
27 |
28 | runner.on('error', function (err) {
29 | server.close()
30 | throw err
31 | })
32 |
--------------------------------------------------------------------------------
/test/e2e/specs/test.js:
--------------------------------------------------------------------------------
1 | // For authoring Nightwatch tests, see
2 | // http://nightwatchjs.org/guide#usage
3 |
4 | module.exports = {
5 | 'default e2e tests': function (browser) {
6 | // automatically uses dev Server port from /config.index.js
7 | // default: http://localhost:8080
8 | // see nightwatch.conf.js
9 | const devServer = browser.globals.devServerURL
10 |
11 | browser
12 | .url(devServer)
13 | .waitForElementVisible('#app', 5000)
14 | .assert.elementPresent('.hello')
15 | .assert.containsText('h1', 'Welcome to Your Vue.js App')
16 | .assert.elementCount('img', 1)
17 | .end()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/unit/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true,
7 | "sinon": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/test/unit/index.js:
--------------------------------------------------------------------------------
1 | // Polyfill fn.bind() for PhantomJS
2 | /* eslint-disable no-extend-native */
3 | Function.prototype.bind = require('function-bind')
4 |
5 | // require all test files (files that ends with .spec.js)
6 | const testsContext = require.context('./specs', true, /\.spec$/)
7 | testsContext.keys().forEach(testsContext)
8 |
9 | // require all src files except main.js for coverage.
10 | // you can also change this to match only the subset of files that
11 | // you want coverage for.
12 | const srcContext = require.context('src', true, /^\.\/(?!main(\.js)?$)/)
13 | srcContext.keys().forEach(srcContext)
14 |
--------------------------------------------------------------------------------
/test/unit/karma.conf.js:
--------------------------------------------------------------------------------
1 | // This is a karma config file. For more details see
2 | // http://karma-runner.github.io/0.13/config/configuration-file.html
3 | // we are also using it with karma-webpack
4 | // https://github.com/webpack/karma-webpack
5 |
6 | var webpackConfig = require('../../build/webpack.test.conf')
7 |
8 | module.exports = function (config) {
9 | config.set({
10 | // to run in additional browsers:
11 | // 1. install corresponding karma launcher
12 | // http://karma-runner.github.io/0.13/config/browsers.html
13 | // 2. add it to the `browsers` array below.
14 | browsers: ['PhantomJS'],
15 | frameworks: ['mocha', 'sinon-chai'],
16 | reporters: ['spec', 'coverage'],
17 | files: ['./index.js'],
18 | preprocessors: {
19 | './index.js': ['webpack', 'sourcemap']
20 | },
21 | webpack: webpackConfig,
22 | webpackMiddleware: {
23 | noInfo: true
24 | },
25 | coverageReporter: {
26 | dir: './coverage',
27 | reporters: [
28 | { type: 'lcov', subdir: '.' },
29 | { type: 'text-summary' }
30 | ]
31 | }
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/test/unit/specs/Hello.spec.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Hello from 'src/components/Hello'
3 |
4 | describe('Hello.vue', () => {
5 | it('should render correct contents', () => {
6 | const Constructor = Vue.extend(Hello)
7 | const vm = new Constructor().$mount()
8 | expect(vm.$el.querySelector('.hello h1').textContent)
9 | .to.equal('Welcome to Your Vue.js App')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/views/_common/_template.ejs:
--------------------------------------------------------------------------------
1 | {% extends "../_common/layout.html" %}
2 |
3 | {% block "block_custom_css" %}
4 |
5 |
6 | <% for (var css in htmlWebpackPlugin.files.css) { %>
7 |
8 | <% } %>
9 | {% endblock %}
10 |
11 | {% block "block_custom_js" %}
12 | <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
13 |
14 | <% } %>
15 |
16 | {% endblock %}
17 |
18 | {% block "content" %}
19 |
20 |
21 |
22 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/views/_common/footer.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/views/_common/foothook.html:
--------------------------------------------------------------------------------
1 |
2 | {% include 'hook/baidu.html' %}
3 |
4 |
--------------------------------------------------------------------------------
/views/_common/head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{ pageData.title || siteInfo.title }}
16 |
--------------------------------------------------------------------------------
/views/_common/headhook.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etheranl/vue-template/0b9438d16b00cc48bfbbaf2ffc51f4df2fc10124/views/_common/headhook.html
--------------------------------------------------------------------------------
/views/_common/hook/baidu.html:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/views/_common/layout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {% spaceless %}
4 |
5 | {% include "head.html" %}
6 | {% include "headhook.html" %}
7 | {% block "block_custom_css" %}{% endblock %}
8 |
9 |
10 |
11 | {% block "content" %}{% endblock %}
12 |
13 |
14 | {% include "footer.html" %}
15 |
16 |
19 |
20 | {% block "block_custom_js" %}{% endblock %}
21 |
22 | {% include "foothook.html" %}
23 |
24 |
25 | {% endspaceless %}
26 |
27 |
--------------------------------------------------------------------------------
/vues/_components/grace.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Gracejs 交流群
7 |
8 |
9 |
10 |
11 |
12 |
13 |
43 |
44 |
77 |
--------------------------------------------------------------------------------
/vues/_components/start.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 | Welcome to start your Vue2 app
with Koa-grace!
6 |
7 |
8 |
9 | To get a better understanding of how this boilerplate works,
10 |
check out
11 | its documentation.
12 |
13 |
14 | It is also recommended to go through the docs
15 |
for
16 | Webpack and
17 | vue-loader.
18 |
19 |
20 | If you have any issues with the setup,
21 |
please file an issue at this boilerplate's
22 | repository.
23 |
24 |
25 |
26 |
27 |
Essential Links
28 |
32 |
Ecosystem
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
55 |
56 |
96 |
--------------------------------------------------------------------------------
/vues/mobile/home/components/home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
18 |
--------------------------------------------------------------------------------
/vues/mobile/home/index.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import VueRouter from 'vue-router'
5 | import VueResource from 'vue-resource'
6 | import routes from './router.js'
7 | import App from './index.vue'
8 |
9 | // 初始化路由
10 | /* eslint-disable no-new */
11 | const router = new VueRouter(routes)
12 |
13 | // 置入组件
14 | Vue.use(VueRouter)
15 | Vue.use(VueResource)
16 |
17 | new Vue({
18 | /**
19 | * 提供的元素只能作为挂载点。
20 | * 不同于 Vue 1.x,所有的挂载元素会被 Vue 生成的 DOM 替换。
21 | * 因此不推荐挂载root实例到 或者 上。
22 | */
23 | el: '#app',
24 | template: '',
25 | components: { App },
26 | /**
27 | * 置入路由
28 | */
29 | router
30 | })
31 | /* eslint-enable no-new */
32 |
--------------------------------------------------------------------------------
/vues/mobile/home/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 |
15 |
17 |
--------------------------------------------------------------------------------
/vues/mobile/home/router.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | routes: [{
3 | path: '/',
4 | component: require('./components/home.vue')
5 | }, {
6 | path: '*',
7 | redirect: '/'
8 | }]
9 | }
10 |
--------------------------------------------------------------------------------
/vues/pc/index/components/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
18 |
--------------------------------------------------------------------------------
/vues/pc/index/index.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import VueRouter from 'vue-router'
5 | import VueResource from 'vue-resource'
6 | import routes from './router.js'
7 | import App from './index.vue'
8 |
9 | // 初始化路由
10 | /* eslint-disable no-new */
11 | const router = new VueRouter(routes)
12 |
13 | // 置入组件
14 | Vue.use(VueRouter)
15 | Vue.use(VueResource)
16 |
17 | new Vue({
18 | el: '#app',
19 | template: '',
20 | components: { App },
21 | router
22 | })
23 |
--------------------------------------------------------------------------------
/vues/pc/index/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 |
15 |
17 |
--------------------------------------------------------------------------------
/vues/pc/index/router.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | routes: [{
3 | path: '/',
4 | component: require('./components/index.vue')
5 | }, {
6 | path: '*',
7 | redirect: '/'
8 | }]
9 | }
10 |
--------------------------------------------------------------------------------