├── src ├── libs │ └── .gitkeep ├── less │ ├── icons.less │ ├── app.less │ ├── variables.less │ ├── utils.less │ ├── base.less │ ├── mixins.less │ └── reset.less ├── plugins │ └── .gitkeep ├── assets │ ├── fonts │ │ └── .gitkeep │ └── images │ │ └── .gitkeep ├── store │ ├── modules │ │ └── .gitkeep │ ├── config.js │ ├── types.js │ ├── mutations.js │ ├── plugins │ │ └── cache.js │ ├── index.js │ └── actions.js ├── view │ ├── modules │ │ └── .gitkeep │ ├── Hello.vue │ └── Tree.vue ├── router │ ├── map │ │ ├── hello.js │ │ └── tree.js │ └── index.js ├── components │ └── tree │ │ ├── index.js │ │ ├── tree.vue │ │ └── treeItem.vue ├── main.js ├── api │ ├── city.js │ └── ajax.js ├── App.vue ├── index.html └── util │ └── cache.js ├── upload └── .gitkeep ├── .eslintignore ├── dic.yml ├── .editorconfig ├── .gitignore ├── LICENSE ├── .eslintrc.js ├── mock └── city.js ├── package.json └── README.md /src/libs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /upload/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/less/icons.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/fonts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/store/modules/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/view/modules/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | public/*.js 2 | build/*.js 3 | -------------------------------------------------------------------------------- /src/store/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'city': 'CACHE_CITY' 3 | } 4 | -------------------------------------------------------------------------------- /dic.yml: -------------------------------------------------------------------------------- 1 | action: 2 | add: 添加 3 | get: 获取 4 | 5 | model: 6 | project: 项目 7 | user: 用户 8 | -------------------------------------------------------------------------------- /src/router/map/hello.js: -------------------------------------------------------------------------------- 1 | import Hello from 'view/Hello' 2 | export default [ 3 | { path: '/', component: Hello } 4 | ] 5 | -------------------------------------------------------------------------------- /src/router/map/tree.js: -------------------------------------------------------------------------------- 1 | import Tree from 'view/Tree' 2 | 3 | export default [ 4 | { path: '/tree', component: Tree } 5 | ] 6 | -------------------------------------------------------------------------------- /src/components/tree/index.js: -------------------------------------------------------------------------------- 1 | import tree from './tree.vue' 2 | import treeItem from './treeItem.vue' 3 | 4 | export default tree 5 | export { treeItem } 6 | -------------------------------------------------------------------------------- /src/less/app.less: -------------------------------------------------------------------------------- 1 | @import "variables.less"; 2 | @import "mixins.less"; 3 | @import "reset.less"; 4 | @import "base.less"; 5 | @import "icons.less"; 6 | @import "utils.less"; 7 | -------------------------------------------------------------------------------- /src/store/types.js: -------------------------------------------------------------------------------- 1 | export const city = { 2 | SYNC_DATA: 'city/SYNC_DATA', 3 | REMOVE_ONE: 'city/REMOVE_ONE', 4 | } 5 | export const COMMIT_ACTION = 'COMMIT_ACTION' 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{less,json,yml}] 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | import router from './router' 4 | import store from './store' 5 | 6 | export default new Vue({ 7 | el: '#app', 8 | router, 9 | store, 10 | render: h => h(App) 11 | }) 12 | -------------------------------------------------------------------------------- /src/api/city.js: -------------------------------------------------------------------------------- 1 | import Ajax, { GET } from './ajax' 2 | 3 | // 通过 Ajax 类实例化拥有基础的 QRUD 能力 4 | const CityApi = new Ajax('/city') 5 | 6 | // 如果需要额外的个性化请求,借助 GET、POST、PUT、DELETE 工具方法添加 7 | CityApi.paging = () => GET('/city') 8 | 9 | export default CityApi 10 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 12 | 15 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import hello from './map/hello' 4 | import tree from './map/tree' 5 | 6 | Vue.use(VueRouter) 7 | 8 | export default new VueRouter({ 9 | routes: [ 10 | ...hello, 11 | ...tree 12 | ] 13 | }) 14 | -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | // import _ from 'lodash' 2 | import { city as cityType } from './types' 3 | 4 | export default { 5 | [cityType.SYNC_DATA](state, data) { 6 | state.citys = data 7 | }, 8 | // [cityType.REMOVE_ONE](state, id) { 9 | // state.citys = _.omit(state.citys, [id]) 10 | // } 11 | } 12 | -------------------------------------------------------------------------------- /src/less/variables.less: -------------------------------------------------------------------------------- 1 | @font-family-base: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif; 2 | @font-size-base: 14px; 3 | @line-height-base: 1.5; 4 | @text-color: #333; 5 | @body-bg: #fff; 6 | 7 | // link 8 | @link-color: #20a0ff; 9 | @link-hover-color: darken(#20a0ff, 10%); 10 | @link-hover-decoration: underline; 11 | -------------------------------------------------------------------------------- /src/store/plugins/cache.js: -------------------------------------------------------------------------------- 1 | import Cache from 'src/util/cache' 2 | import Config from '../config' 3 | 4 | export default store => { 5 | store.subscribe(({ type, payload }, state) => { 6 | const module = type.split('/')[0] 7 | const cacheKey = Config[module] 8 | if (cacheKey) { 9 | Cache.save(cacheKey, payload) 10 | } 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import mutations from './mutations' 4 | import actions from './actions' 5 | import Cache from './plugins/cache' 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | state: { 11 | citys: {} 12 | }, 13 | mutations, 14 | actions, 15 | getters: { 16 | citysCount(state) { 17 | return Object.keys(state.citys).length 18 | } 19 | }, 20 | plugins: [ 21 | Cache 22 | ] 23 | }) 24 | -------------------------------------------------------------------------------- /src/components/tree/tree.vue: -------------------------------------------------------------------------------- 1 | 11 | 26 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | <% if (htmlWebpackPlugin.options.NODE_ENV === 'dev') { %> 10 | 11 | <% } %> 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/less/utils.less: -------------------------------------------------------------------------------- 1 | .clearfix { 2 | .clearfix() 3 | } 4 | .center-block { 5 | .center-block() 6 | } 7 | .text-overflow { 8 | .text-overflow() 9 | } 10 | .pull-right { 11 | float: right !important 12 | } 13 | .pull-left { 14 | float: left !important 15 | } 16 | .abs-left, 17 | .abs-right { 18 | position: absolute; 19 | top: 0; 20 | } 21 | .abs-left { 22 | left: 0 23 | } 24 | .abs-right { 25 | right: 0 26 | } 27 | 28 | .hide { 29 | display: none !important 30 | } 31 | .show { 32 | display: block !important 33 | } 34 | .invisible { 35 | visibility: hidden 36 | } 37 | 38 | .hidden { 39 | display: none !important; 40 | visibility: hidden !important; 41 | } 42 | 43 | .affix { 44 | position: fixed 45 | } 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # build dir 7 | public 8 | 9 | # e2e test reports 10 | test/e2e/reports 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules 37 | jspm_packages 38 | 39 | # Optional npm cache directory 40 | .npm 41 | 42 | # Optional REPL history 43 | .node_repl_history 44 | -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | import CityApi from 'api/city' 2 | import Config from './config' 3 | import Cache from 'src/util/cache' 4 | import { city as cityType, COMMIT_ACTION } from './types' 5 | import _ from 'lodash' 6 | 7 | export default { 8 | getCitys({ dispatch }) { 9 | dispatch(COMMIT_ACTION, { 10 | type: cityType.SYNC_DATA, 11 | promise: () => CityApi.all().then(data => data.citys) 12 | }) 13 | }, 14 | deleteCity({ commit, state }, id) { 15 | commit(cityType.SYNC_DATA, _.omit(state.citys, [id])) 16 | }, 17 | [COMMIT_ACTION]({ commit }, { type, promise }) { 18 | const module = type.split('/')[0] 19 | const cacheKey = Config[module] || '' 20 | const cacheData = Cache.get(cacheKey) 21 | if(!cacheData) { 22 | promise().then(data => commit(type, data)) 23 | } else { 24 | commit(type, cacheData) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/less/base.less: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: border-box 5 | } 6 | 7 | html { 8 | font-size: 10px; 9 | -webkit-tap-highlight-color: rgba(0,0,0,0); 10 | } 11 | 12 | body { 13 | font-family: @font-family-base; 14 | font-size: @font-size-base; 15 | line-height: @line-height-base; 16 | color: @text-color; 17 | background-color: @body-bg; 18 | } 19 | 20 | input, 21 | button, 22 | select, 23 | textarea { 24 | font-family: inherit; 25 | font-size: inherit; 26 | line-height: inherit; 27 | } 28 | 29 | a { 30 | color: @link-color; 31 | text-decoration: none; 32 | 33 | &:hover, 34 | &:focus { 35 | color: @link-hover-color; 36 | text-decoration: @link-hover-decoration; 37 | } 38 | 39 | &:focus { 40 | .tab-focus(); 41 | } 42 | } 43 | 44 | ul.default, 45 | ol.default { 46 | padding-left: 30px; 47 | } 48 | ul.default { 49 | list-style: disc 50 | } 51 | ol.default { 52 | list-style: decimal 53 | } 54 | -------------------------------------------------------------------------------- /src/view/Hello.vue: -------------------------------------------------------------------------------- 1 | 14 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | ecmaVersion: 6, 6 | sourceType: 'module' 7 | }, 8 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 9 | extends: 'standard', 10 | // required to lint *.vue files 11 | plugins: [ 12 | 'html' 13 | ], 14 | env: { 15 | 'browser': true, 16 | 'node': true 17 | }, 18 | globals: { 19 | '_': true 20 | }, 21 | // add your custom rules here 22 | rules: { 23 | // allow paren-less arrow functions 24 | 'arrow-parens': 0, 25 | // allow async-await 26 | 'generator-star-spacing': 0, 27 | // 28 | 'space-before-function-paren': [0, "always"], 29 | 'comma-dangle': ['error', 'ignore'], 30 | "keyword-spacing": 0, 31 | // 4 space indent 32 | 'indent': [2, 4, { "SwitchCase": 1 }], 33 | // disable no-new 34 | 'no-new': 0, 35 | // allow debugger during development 36 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/view/Tree.vue: -------------------------------------------------------------------------------- 1 | 4 | 47 | -------------------------------------------------------------------------------- /src/util/cache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * localStorage 3 | */ 4 | const __localStorage = window.localStorage 5 | 6 | /** 7 | * 检测是否支持 localStorage 8 | */ 9 | const isSupported = () => { 10 | const key = '__EPM_TEST_SUPPORTED__' 11 | try { 12 | __localStorage.setItem(key, Date.now()) 13 | __localStorage.removeItem(key) 14 | return true 15 | } catch (error) { 16 | return false 17 | } 18 | } 19 | 20 | /** 21 | * browserStorage 22 | */ 23 | const browserStorage = () => { 24 | const storage = __localStorage 25 | return { 26 | get: key => JSON.parse(storage.getItem(key)), 27 | save: (key, data) => storage.setItem(key, JSON.stringify(data)), 28 | remove: key => storage.removeItem(key), 29 | clear: () => storage.clear() 30 | } 31 | } 32 | 33 | /** 34 | * ramStorage 35 | */ 36 | const ramStorage = () => { 37 | const storage = new Map() 38 | return { 39 | get: key => storage.get(key), 40 | save: (key, data) => storage.set(key, data), 41 | remove: key => storage.delete(key), 42 | clear: () => storage.clear() 43 | } 44 | } 45 | 46 | /** 47 | * cacheStore fallback 48 | * 不支持 localStorage 时,回退 ramStorage 49 | */ 50 | const cacheStore = isSupported() ? browserStorage() : ramStorage() 51 | export default cacheStore 52 | -------------------------------------------------------------------------------- /src/less/mixins.less: -------------------------------------------------------------------------------- 1 | .size(@width; @height) { 2 | width: @width; 3 | height: @height; 4 | } 5 | 6 | .circle(@size) { 7 | .size(@size, @size); 8 | border-radius: 50%; 9 | } 10 | 11 | .clearfix() { 12 | &:before, 13 | &:after { 14 | content: " "; 15 | display: table; 16 | } 17 | &:after { 18 | clear: both; 19 | } 20 | } 21 | 22 | .center-block() { 23 | display: block; 24 | margin-left: auto; 25 | margin-right: auto; 26 | } 27 | 28 | .text-overflow() { 29 | overflow: hidden; 30 | text-overflow: ellipsis; 31 | white-space: nowrap; 32 | } 33 | 34 | .img-responsive(@display: block) { 35 | display: @display; 36 | max-width: 100%; 37 | height: auto; 38 | } 39 | 40 | .bg-variant(@color) { 41 | background-color: @color; 42 | a&:hover { 43 | background-color: darken(@color, 10%); 44 | } 45 | } 46 | .text-emphasis-variant(@color) { 47 | color: @color; 48 | a&:hover { 49 | color: darken(@color, 10%); 50 | } 51 | } 52 | 53 | .placeholder(@color) { 54 | &::-moz-placeholder { 55 | color: @color; 56 | opacity: 1; 57 | } 58 | &:-ms-input-placeholder { color: @color; } 59 | &::-webkit-input-placeholder { color: @color; } 60 | } 61 | 62 | .tab-focus() { 63 | outline: thin dotted; 64 | outline: 5px auto -webkit-focus-ring-color; 65 | outline-offset: -2px; 66 | } 67 | -------------------------------------------------------------------------------- /mock/city.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | // template example 3 | { 4 | // url 5 | url: '/', 6 | // Mock template 7 | data: { 8 | 'citys|1-10': { 9 | '310000': '上海市', 10 | '320000': '江苏省', 11 | '330000': '浙江省', 12 | '340000': '安徽省', 13 | '350000': '河南省' 14 | } 15 | } 16 | }, 17 | // function example 18 | // 适用于需要动态切换数据的场景 19 | { 20 | url: '/:type', 21 | // Mock function 22 | // 根据参数动态切换数据 23 | // @req: Express request 24 | data(req) { 25 | const type = req.params.type 26 | if (type === 'array') { 27 | return { 28 | 'citys|1-10': [ 29 | 'Mock.js' 30 | ] 31 | } 32 | } 33 | return { 34 | 'name': 'Mock.js' 35 | } 36 | } 37 | }, 38 | // method example 39 | // 请求协议:get、post、put、delete,小写 40 | // 省略协议时为 get 请求 41 | { 42 | url: '/aaa', 43 | // default method is get 44 | method: 'post', 45 | data: { 46 | 'citys|2': { 47 | '310000': '上海市', 48 | '320000': '江苏省', 49 | '330000': '浙江省', 50 | '340000': '安徽省' 51 | } 52 | } 53 | } 54 | ] 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-scaffold", 3 | "version": "0.2.0", 4 | "private": true, 5 | "author": { 6 | "name": "yusen", 7 | "email": "634206017@qq.com" 8 | }, 9 | "scripts": { 10 | "dev": "cross-env NODE_ENV=dev supervisor -w mock -i src build/dev-server.js", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.15.3", 15 | "lodash": "^4.17.2", 16 | "vue": "^2.2.4", 17 | "vue-router": "^2.3.0", 18 | "vuex": "^2.2.1" 19 | }, 20 | "devDependencies": { 21 | "autoprefixer": "^6.5.2", 22 | "babel-core": "^6.24.0", 23 | "babel-eslint": "^7.1.0", 24 | "babel-loader": "^6.2.4", 25 | "babel-plugin-lodash": "^3.2.10", 26 | "babel-plugin-transform-runtime": "^6.23.0", 27 | "babel-preset-es2015": "^6.24.0", 28 | "babel-preset-stage-2": "^6.22.0", 29 | "babel-runtime": "^6.23.0", 30 | "body-parser": "^1.17.1", 31 | "connect-history-api-fallback": "^1.2.0", 32 | "cross-env": "^4.0.0", 33 | "css-loader": "^0.27.3", 34 | "ejs-loader": "^0.3.0", 35 | "eslint": "^3.9.1", 36 | "eslint-config-standard": "^7.1.0", 37 | "eslint-friendly-formatter": "^2.0.6", 38 | "eslint-loader": "^1.6.1", 39 | "eslint-plugin-html": "^1.6.0", 40 | "eslint-plugin-promise": "^3.3.1", 41 | "eslint-plugin-standard": "^2.0.1", 42 | "eventsource-polyfill": "^0.9.6", 43 | "express": "^4.14.0", 44 | "extract-text-webpack-plugin": "^1.0.1", 45 | "file-loader": "^0.9.0", 46 | "html-webpack-plugin": "^2.14.0", 47 | "http-proxy-middleware": "^0.17.2", 48 | "json-loader": "^0.5.4", 49 | "less": "^2.7.1", 50 | "less-loader": "^2.2.3", 51 | "lodash-webpack-plugin": "^0.10.6", 52 | "mockjs": "^1.0.1-beta3", 53 | "multiparty": "^4.1.3", 54 | "opn": "^4.0.2", 55 | "ora": "^1.2.0", 56 | "postcss-loader": "^1.3.3", 57 | "reload": "^1.1.0", 58 | "shelljs": "^0.7.4", 59 | "url-loader": "^0.5.7", 60 | "vue-hot-reload-api": "^2.0.11", 61 | "vue-html-loader": "^1.2.2", 62 | "vue-loader": "^11.3.2", 63 | "vue-style-loader": "^2.0.4", 64 | "vue-template-compiler": "^2.2.4", 65 | "webpack": "^1.14.0", 66 | "webpack-bundle-analyzer": "^2.3.1", 67 | "webpack-dev-middleware": "^1.8.3", 68 | "webpack-hot-middleware": "^2.12.2", 69 | "webpack-merge": "^4.1.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-scaffold 2 | 3 | ## 介绍 4 | 5 | 基于 Webpack 的 Vue2 SPA 开发环境,支持 ES6、Less、ESlint、e2e test,种子项目已集成 vuex、vue-router、axios、mockjs 等。 6 | 7 | ## 使用 8 | 9 | ```bash 10 | # 获取脚手架到 vue-project 目录或自定义目录 11 | $ git clone git@github.com:HYFE/vue-scaffold.git vue-project 12 | 13 | # 进入工作目录或你的自定义目录 14 | $ cd vue-project 15 | 16 | # 安装依赖 17 | $ npm install 18 | 19 | # 全局安装 supervisor,监视开发服务器和 webpack 配置等代码更改 20 | $ npm install supervisor -g 21 | 22 | # 启动开发服务 23 | $ npm run dev 24 | 25 | # e2e test 26 | $ npm run e2e 27 | 28 | # 打包 29 | $ npm run build 30 | ``` 31 | 32 | ## 目录结构 33 | 34 | ```bash 35 | ├── build/ # 环境配置文件 36 | │ ├── build.js 37 | │ ├── config.js # 目录及端口等常用配置项 38 | │ ├── dev-client.js 39 | │ ├── dev-server.js 40 | │ ├── mock.routes.js 41 | │ ├── webpack.base.conf.js 42 | │ ├── webpack.dev.conf.js 43 | │ ├── webpack.prod.conf.js 44 | ├── mock/ 45 | │ └── ... # mock路由和数据配置 46 | ├── src/ 47 | │ ├── main.js # 项目入口文件 48 | │ ├── router/ # 路由配置 49 | │ │ └── ... 50 | │ ├── App.vue # main app component 51 | │ ├── index.html # index.html 52 | │ ├── components/ # 可复用组件 53 | │ │ └── ... 54 | │ ├── less/ # less 55 | │ │ └── ... 56 | │ ├── view/ # 视图组件 57 | │ │ └── ... 58 | │ ├── libs/ # 第三方库文件 59 | │ │ └── ... 60 | │ ├── plugins/ # Vue插件 61 | │ │ └── ... 62 | │ ├── util/ # 常用工具库 63 | │ │ └── ... 64 | │ ├── assets/ # 静态资源文件 65 | │ │ └── fonts 66 | │ │ └── images 67 | │ ├── store/ # vuex 68 | │ │ ├── index.js # 组合 vuex 模块 69 | │ │ ├── actions.js # 根 actions 70 | │ │ ├── mutations.js # 根 mutations 71 | │ │ ├── modules/ # 根据业务逻辑划分子模块 72 | │ │ │ └── ... 73 | │ ├── api/ 74 | │ │ └── index.js # 最终请求后端的地方 75 | ├── upload/ # 模拟文件上传目录 76 | │ └── ... 77 | ├── test/ 78 | │ ├── e2e/ # e2e test 79 | │ │ ├── custom-assertions/ # 自定义断言 80 | │ │ │ └── ... 81 | │ │ ├── specs/ # 测试用例 82 | │ │ │ └── ... 83 | │ │ ├── nightwatch.conf.js # nightwatch 配置 84 | │ │ ├── runner.js # e2e start 85 | ├── dic.yml # 项目内关键字命名字典,供所有人查阅 86 | ├── .editorconfig # 编辑器配置 87 | ├── .eslintignore # eslint ignore conf 88 | ├── .eslintrc.js # eslint conf 89 | └── package.json # build scripts and dependencies 90 | ``` 91 | -------------------------------------------------------------------------------- /src/api/ajax.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { ROOT_PATH } from './config' 3 | 4 | const FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded' 5 | axios.defaults.headers.post['Content-Type'] = FORM_CONTENT_TYPE 6 | axios.defaults.headers.put['Content-Type'] = FORM_CONTENT_TYPE 7 | 8 | const Ajax = axios.create({ 9 | // 公共 HTTP 配置 10 | baseURL: ROOT_PATH, 11 | // timeout: 1000, 12 | // headers: { 'X-Custom-Header': 'foobar' }, 13 | responseType: 'json' 14 | }) 15 | 16 | /** 17 | * 格式化参数 18 | * @param {*} params Object 19 | */ 20 | const QS = (params = {}) => { 21 | let url = '' 22 | Object.keys(params).forEach((k, i) => { 23 | url += `${i === 0 ? '' : '&'}${k}=${params[k]}` 24 | }) 25 | return url 26 | } 27 | 28 | const HTTP_HANDLE = req => req.then(res => res.data).catch(err => console.error('[API]:', err)) 29 | 30 | /** 31 | * http GET 32 | * @param {*} url String 33 | * @param {*} params Object 34 | */ 35 | export const GET = (url, params) => HTTP_HANDLE(Ajax.get(url, params ? { 36 | params 37 | } : params)) 38 | 39 | /** 40 | * http POST 41 | * @param {*} url String 42 | * @param {*} body Object 43 | */ 44 | export const POST = (url, body) => HTTP_HANDLE(Ajax.post(url, QS(body))) 45 | 46 | /** 47 | * http PUT 48 | * @param {*} url String 49 | * @param {*} body Object 50 | */ 51 | export const PUT = (url, body) => HTTP_HANDLE(Ajax.put(url, QS(body))) 52 | 53 | /** 54 | * http DELETE 55 | * @param {*} url String 56 | * @param {*} params Object 57 | */ 58 | export const DELETE = (url, params) => HTTP_HANDLE(Ajax.delete(url, params ? { 59 | params 60 | } : params)) 61 | 62 | const fixUrl = url => (url.lastIndexOf('/') === url.length ? url : `${url}/`) 63 | const urlPattern = (url, arg) => ({ 64 | url: url.replace(/:[a-zA-Z]+/g, m => arg.shift()), 65 | params: arg.length ? arg[0] : null 66 | }) 67 | 68 | /** 69 | * 对常用 RESTful 请求格式的封装,支持多级资源请求 70 | * 以一个实体 User 为例,基础 URL 为 `/api/user`,则: 71 | * 查询 GET /api/user/:id 72 | * 添加 POST /api/user 73 | * 修改 PUT /api/user/:id 74 | * 删除 DELETE /api/user/:id 75 | * 76 | * @export 77 | * @class Api 78 | */ 79 | export default class Api { 80 | constructor(url) { 81 | this.url = fixUrl(url) 82 | } 83 | 84 | add(...args) { 85 | const { url, params } = urlPattern(this.url, args) 86 | return POST(url, params) 87 | } 88 | 89 | update(...args) { 90 | const { url, params } = urlPattern(`${this.url}:id`, args) 91 | return PUT(url, params) 92 | } 93 | 94 | delete(...args) { 95 | const { url, params } = urlPattern(`${this.url}:id`, args) 96 | return DELETE(url, params) 97 | } 98 | 99 | one(...args) { 100 | const { url, params } = urlPattern(`${this.url}:id`, args) 101 | return GET(url, params) 102 | } 103 | 104 | all(...args) { 105 | const { url, params } = urlPattern(this.url, args) 106 | return GET(url, params) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/less/reset.less: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | -ms-text-size-adjust: 100%; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | 7 | body, h1, h2, h3, h4, h5, h6, p, figure, pre, dl, dd, blockquote { 8 | margin: 0; 9 | } 10 | 11 | input, legend, input, textarea, button { 12 | padding: 0; 13 | } 14 | 15 | ul, ol, form, fieldset, th, td { 16 | margin: 0; 17 | padding: 0; 18 | } 19 | 20 | article, 21 | aside, 22 | details, 23 | figcaption, 24 | figure, 25 | footer, 26 | header, 27 | hgroup, 28 | main, 29 | menu, 30 | nav, 31 | section, 32 | summary { 33 | display: block; 34 | } 35 | 36 | audio, 37 | canvas, 38 | progress, 39 | video { 40 | display: inline-block; 41 | vertical-align: baseline; 42 | } 43 | 44 | audio:not([controls]) { 45 | display: none; 46 | height: 0; 47 | } 48 | 49 | [hidden], 50 | template { 51 | display: none; 52 | } 53 | 54 | a { 55 | background-color: transparent; 56 | } 57 | a:active, 58 | a:hover { 59 | outline: 0; 60 | } 61 | 62 | abbr[title] { 63 | border-bottom: 1px dotted; 64 | } 65 | 66 | ul, ol { 67 | list-style: none; 68 | } 69 | 70 | b, 71 | strong { 72 | font-weight: bold; 73 | } 74 | 75 | mark { 76 | background: #ff0; 77 | color: #000; 78 | } 79 | 80 | small { 81 | font-size: 80%; 82 | } 83 | 84 | sub, 85 | sup { 86 | font-size: 75%; 87 | line-height: 0; 88 | position: relative; 89 | vertical-align: baseline; 90 | } 91 | 92 | sup { 93 | top: -0.5em; 94 | } 95 | 96 | sub { 97 | bottom: -0.25em; 98 | } 99 | 100 | img, 101 | legend { 102 | border: 0; 103 | } 104 | 105 | img, input, button, label { 106 | vertical-align: middle; 107 | } 108 | 109 | svg:not(:root) { 110 | overflow: hidden; 111 | } 112 | 113 | hr { 114 | box-sizing: content-box; 115 | height: 0; 116 | } 117 | 118 | pre { 119 | overflow: auto; 120 | } 121 | 122 | code, 123 | kbd, 124 | pre, 125 | samp { 126 | font-family: monospace, monospace; 127 | font-size: 1em; 128 | } 129 | 130 | button, 131 | input, 132 | optgroup, 133 | select, 134 | textarea { 135 | color: inherit; 136 | font: inherit; 137 | margin: 0; 138 | } 139 | 140 | button { 141 | overflow: visible; 142 | } 143 | 144 | button, 145 | select { 146 | text-transform: none; 147 | } 148 | 149 | button, 150 | html input[type="button"], 151 | input[type="reset"], 152 | input[type="submit"] { 153 | -webkit-appearance: button; 154 | cursor: pointer; 155 | } 156 | 157 | button[disabled], 158 | html input[disabled] { 159 | cursor: default; 160 | } 161 | 162 | button::-moz-focus-inner, 163 | input::-moz-focus-inner { 164 | border: 0; 165 | padding: 0; 166 | } 167 | 168 | input { 169 | line-height: normal; 170 | } 171 | 172 | input[type="number"]::-webkit-inner-spin-button, 173 | input[type="number"]::-webkit-outer-spin-button { 174 | height: auto; 175 | } 176 | 177 | input[type="search"] { 178 | -webkit-appearance: textfield; 179 | box-sizing: content-box; 180 | } 181 | input[type="search"]::-webkit-search-cancel-button, 182 | input[type="search"]::-webkit-search-decoration { 183 | -webkit-appearance: none; 184 | } 185 | 186 | fieldset { 187 | border: 1px solid #c0c0c0; 188 | margin: 0 2px; 189 | padding: 0.35em 0.625em 0.75em; 190 | } 191 | 192 | textarea { 193 | overflow: auto; 194 | } 195 | 196 | table { 197 | border-collapse: collapse; 198 | border-spacing: 0; 199 | } 200 | -------------------------------------------------------------------------------- /src/components/tree/treeItem.vue: -------------------------------------------------------------------------------- 1 | 26 | 95 | 208 | --------------------------------------------------------------------------------