diff --git a/README.md b/README.md index 79dbddc..32b23af 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,86 @@ # vue-cli-plugin-element -Quickly build a backend system with vue-cli and element-ui in seconds. \ No newline at end of file +[![Version](https://img.shields.io/npm/v/@codetrial/vue-cli-plugin-element.svg)](https://www.npmjs.com/package/@codetrial/vue-cli-plugin-element) +[![License](https://img.shields.io/npm/l/@codetrial/vue-cli-plugin-element.svg)](https://www.npmjs.com/package/@codetrial/vue-cli-plugin-element) +[![Dependencies](https://img.shields.io/david/codetrial/vue-cli-plugin-element.svg)](https://www.npmjs.com/package/@codetrial/vue-cli-plugin-element) + +Quickly build a backend system with vue-cli and element-ui in seconds. + +This project is not only a vue-cli plugin but also a vue-cli preset. Have fun! + +:us: English | [:cn: 简体中文](README.zh-CN.md) + +## Getting Started + +### Prerequisites + +You must install Vue CLI 3 before you start: + +```bash +npm install -g @vue/cli +# OR +yarn global add @vue/cli +``` + +### Install + +You can create your project directly via preset, which already includes configurations such as ESLint and other plugins. + +```bash +vue create --preset codetrial/vue-cli-plugin-element your-awesome-project +``` + +If you don't want to use the preset , you can manually create an empty project via vue-cli. + +```bash +# make sure the following features are selected: +# - Babel +# - PWA +# - Router +# - Vuex +# - CSS Pre-processors +# - Linter - Formatter +vue create your-awesome-project +``` + +Then add the plugin to your project via vue. + +```bash +vue add @codetrial/element +``` + +## Docs + +The full documentation: [:book: codetrial.github.io/element-admin](https://codetrial.github.io/element-admin) + +## Example + +A complete example project: [:zap: @codetrial/element-admin](https://github.com/codetrial/element-admin) + +Live Preview: [:telescope: element-admin.now.sh](https://element-admin.now.sh) + +## Core Features + +- :camera: Minimal dependencies +- :tv: Project Structure +- :telephone_receiver: View Layout +- :pager: Data Processing Layer +- :watch: Authorization +- :radio: Error Pages +- :mag_right: List Example +- :ghost: Form Example + +## Contributing + +Looking forward to your pull requests. + +## Built With + +- [Vue.js](https://github.com/vuejs/vue) +- [ElementUI](https://github.com/ElemeFE/element) + +## License + +[MIT](http://opensource.org/licenses/MIT) + +Copyright (c) 2018 - present, Felix Yang diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..8fd92f5 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,93 @@ +# vue-cli-plugin-element + +[![Version](https://img.shields.io/npm/v/@codetrial/vue-cli-plugin-element.svg)](https://www.npmjs.com/package/@codetrial/vue-cli-plugin-element) +[![License](https://img.shields.io/npm/l/@codetrial/vue-cli-plugin-element.svg)](https://www.npmjs.com/package/@codetrial/vue-cli-plugin-element) +[![Dependencies](https://img.shields.io/david/codetrial/vue-cli-plugin-element.svg)](https://www.npmjs.com/package/@codetrial/vue-cli-plugin-element) + +使用 vue-cli 及 element-ui 闪电般构建一个后台管理系统。 + +这个项目不仅仅是一个 vue-cli 插件,也是一个 vue-cli preset,希望能够帮到你! + +:cn: 简体中文 | [:us: English](README.md) + +## 入门指南 + +### 前置依赖 + +在开始前,你需要先安装 Vue CLI 3: + +```bash +npm install -g @vue/cli +# OR +yarn global add @vue/cli +``` + +### 安装 + +你可以通过 preset 的方式直接创建你的项目,它已经包含了 ESLint 等插件的配置。 + +```bash +vue create --preset codetrial/vue-cli-plugin-element your-awesome-project +``` + +如果你不想使用 preset 的方式,你也可以先通过 vue-cli 手动创建一个空的项目。 + +```bash +# 确保你创建的项目选择了以下功能: +# - Babel +# - PWA +# - Router +# - Vuex +# - CSS Pre-processors +# - Linter - Formatter +vue create your-awesome-project +``` + +然后通过 vue 将插件添加到你的项目中。 + +```bash +vue add @codetrial/element +``` + +## 文档 + +完整的参考文档:[:book: codetrial.github.io/element-admin](https://codetrial.github.io/element-admin) + +## 示例 + +一个使用该插件的完整示例:[:zap: @codetrial/element-admin](https://github.com/codetrial/element-admin) + +在线预览:[:telescope: element-admin.now.sh](https://element-admin.now.sh) + +## 核心功能 + +:camera: **最小依赖**:仅依赖 Vue 官方库及 ElementUI 组件库,未额外引入其它第三方库,为你提供自由发挥的空间。 + +:tv: **目录结构**:根据项目实战经验,设计了合理、清晰的目录结构。 + +:telephone_receiver: **页面布局**:使用 Vue Router 嵌套路由及 ElementUI 内置组件进行布局。 + +:pager: **数据处理**:添加独立的 api 及 service 层,将业务逻辑从组件中抽离。 + +:watch: **权限控制**:为路由添加配置式拦截器,默认支持用户登录鉴权及角色鉴权。 + +:radio: **列表示例**:一个相对比较完整的列表页示例,包含字段查询(过滤),字段排序,页码跳转,批量操作等等。 + +:mag_right: **表单示例**:一个相对比较完整的表单页示例,包含表单校验、提交等操作。 + +:ghost: **错误页面**:内置简单的(其实是偷懒) 403、404 及 500 错误页。 + +## 贡献 + +期待你的 `pull requests`。如果你觉得有帮助,还请多多反馈! + +## 技术栈 + +- [Vue.js](https://github.com/vuejs/vue) +- [ElementUI](https://github.com/ElemeFE/element) + +## 许可 + +[MIT](http://opensource.org/licenses/MIT) + +Copyright (c) 2018 - present, Felix Yang diff --git a/generator.js b/generator.js index ad59ef7..31e9bea 100644 --- a/generator.js +++ b/generator.js @@ -3,25 +3,49 @@ const fs = require('fs') const removeFiles = (files = []) => { files.forEach(path => { if (fs.existsSync(path)) { - fs.unlinkSync(path); + fs.unlinkSync(path) } }) } +const filesToRemove = [ + 'src/components/HelloWorld.vue', + 'src/router.js', + 'src/store.js' +] + +const importsToRemove = [ + `import store from './store'`, + `import router from './router'` +] + module.exports = (api, options, rootOptions) => { + const { + entryFile, + generator: { imports } + } = api + api.extendPackage({ dependencies: { - 'element-ui': '^2.4.11' - }, + 'element-ui': '^2.4.11', + 'vuex-router-sync': '^5.0.0' + } }) - api.injectImports(api.entryFile, `import './registerElementUI'`) + // api.injectImports(entryFile, `import './registerElementUI'`) + + // Remove the default imports generated by vue-cli + if (imports[entryFile]) { + importsToRemove.forEach(itr => imports[entryFile].delete(itr)) + } + // Remove the default files generated by vue-cli + api.render(function(files) { + filesToRemove.forEach(ftr => delete files[ftr]) + }) api.render('./template/structure') api.onCreateComplete(() => { - removeFiles([ - api.resolve('src/components/HelloWorld.vue') - ]) + removeFiles(filesToRemove.map(ftr => api.resolve(ftr))) }) } diff --git a/package.json b/package.json index 3b2490f..e1395e6 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "vue-cli-plugin-element", - "version": "0.1.0", + "version": "1.0.0", "description": "Quickly build a backend system with vue-cli and element-ui in seconds.", "main": "index.js", "scripts": { - "test": "yarn test" + "test": "jest" }, "repository": { "type": "git", @@ -12,7 +12,11 @@ }, "keywords": [ "vue", - "cli", + "vue-cli", + "vue-cli-plugin", + "vue-cli-preset", + "preset", + "plugin", "element", "layout", "structure" diff --git a/preset.json b/preset.json index 5633d40..66b7f6c 100644 --- a/preset.json +++ b/preset.json @@ -4,10 +4,8 @@ "@vue/cli-plugin-babel": {}, "@vue/cli-plugin-pwa": {}, "@vue/cli-plugin-eslint": { - "config": "airbnb", - "lintOn": [ - "save" - ] + "config": "standard", + "lintOn": ["save"] }, "@vue/cli-plugin-unit-jest": {} }, diff --git a/template/structure/src/App.vue b/template/structure/src/App.vue new file mode 100644 index 0000000..3214fb0 --- /dev/null +++ b/template/structure/src/App.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/template/structure/src/api/index.js b/template/structure/src/api/index.js new file mode 100644 index 0000000..187831d --- /dev/null +++ b/template/structure/src/api/index.js @@ -0,0 +1,104 @@ +import { delay, checkCode } from '@/utils/request' + +/* start of mocking user */ +const users = { + admin: { + id: 100001, + name: 'Administrator', + roles: ['USER', 'ADMIN'] + }, + codetrial: { + id: 100001, + name: 'Codetrial', + roles: ['USER'] + } +} + +export async function getUser(username) { + await delay(200) + + const user = users[username] + const response = user + ? { + status: '1', + data: user + } + : { + status: '1100', + message: 'Wrong user or password' + } + + return Promise.resolve(response).then(checkCode) +} +/* end of mocking user */ + +/* start of mocking example list */ +const exampleSource = Array(235) + .fill(1) + .map((element, index) => { + const personal = index % 2 === 0 + return { + id: index + 10000, + name: `Example - ${index} - ${personal ? 'felixpy' : 'codetrial'}`, + type: personal ? 1 : 2, + status: index % 5 === 0 ? 0 : 1, + url: personal ? 'https://felixpy.com' : 'https://codetrial.github.io', + createUser: 100001, + createUserName: 'Felix Yang', + updateUser: 100001, + updateUserName: 'Felix Yang', + createTime: '2018-12-22 11:00:00', + updateTime: '2018-12-22 11:00:00' + } + }) + +export async function getExampleList({ filter = {}, page = {} }) { + const { pageNo = 1, pageSize = 20 } = page + const offset = (pageNo - 1) * pageSize + let list = exampleSource + let total = exampleSource.length + + // filter + Object.keys(filter).forEach(key => { + const filterValue = filter[key] + if (filterValue != null && filterValue.length) { + list = list.filter(item => { + if (Array.isArray(filterValue)) { + return filterValue.map(String).indexOf(String(item[key])) > -1 + } + return String(item[key]) === String(filterValue) + }) + } + }) + + total = list.length + + // sort + list = list.sort((a, b) => { + const { order, orderBy } = page + + if (!order || !orderBy) { + return + } + + const diff = (order === 'descending' ? -1 : 1) * (a[orderBy] - b[orderBy]) + return diff > 0 ? 1 : -1 + }) + + // pagination + list = list.slice(offset, offset + pageSize) + + await delay(1000) + + return Promise.resolve({ + status: '1', + data: { + list, + page: { + ...page, + total + } + } + }).then(checkCode) +} +/* end of mocking example list */ diff --git a/template/structure/src/components/errors/403.vue b/template/structure/src/components/errors/403.vue new file mode 100644 index 0000000..d11088b --- /dev/null +++ b/template/structure/src/components/errors/403.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/template/structure/src/components/errors/404.vue b/template/structure/src/components/errors/404.vue new file mode 100644 index 0000000..f621b40 --- /dev/null +++ b/template/structure/src/components/errors/404.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/template/structure/src/components/errors/500.vue b/template/structure/src/components/errors/500.vue new file mode 100644 index 0000000..e20e086 --- /dev/null +++ b/template/structure/src/components/errors/500.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/template/structure/src/components/errors/index.js b/template/structure/src/components/errors/index.js new file mode 100644 index 0000000..544d48d --- /dev/null +++ b/template/structure/src/components/errors/index.js @@ -0,0 +1,5 @@ +import Forbidden from './403.vue' +import NotFound from './404.vue' +import InternalServerError from './500.vue' + +export { Forbidden, NotFound, InternalServerError } diff --git a/template/structure/src/components/errors/index.scss b/template/structure/src/components/errors/index.scss new file mode 100644 index 0000000..f5a3409 --- /dev/null +++ b/template/structure/src/components/errors/index.scss @@ -0,0 +1,9 @@ +.error-page { + padding: 100px 0; + + .error-page__tip { + font-size: 48px; + font-weight: bold; + text-align: center; + } +} diff --git a/template/structure/src/components/layout/AppBreadcrumb.vue b/template/structure/src/components/layout/AppBreadcrumb.vue new file mode 100644 index 0000000..8a0f68f --- /dev/null +++ b/template/structure/src/components/layout/AppBreadcrumb.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/template/structure/src/components/layout/AppFooter.vue b/template/structure/src/components/layout/AppFooter.vue new file mode 100644 index 0000000..296cc03 --- /dev/null +++ b/template/structure/src/components/layout/AppFooter.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/template/structure/src/components/layout/AppHeader.vue b/template/structure/src/components/layout/AppHeader.vue new file mode 100644 index 0000000..836d382 --- /dev/null +++ b/template/structure/src/components/layout/AppHeader.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/template/structure/src/components/layout/AppLayout.vue b/template/structure/src/components/layout/AppLayout.vue index a930e75..6416377 100644 --- a/template/structure/src/components/layout/AppLayout.vue +++ b/template/structure/src/components/layout/AppLayout.vue @@ -1,3 +1,55 @@ + + + + diff --git a/template/structure/src/components/layout/BlankLayout.vue b/template/structure/src/components/layout/BlankLayout.vue new file mode 100644 index 0000000..673e3b8 --- /dev/null +++ b/template/structure/src/components/layout/BlankLayout.vue @@ -0,0 +1,17 @@ + + + + + diff --git a/template/structure/src/constants/index.js b/template/structure/src/constants/index.js new file mode 100644 index 0000000..e8617c6 --- /dev/null +++ b/template/structure/src/constants/index.js @@ -0,0 +1,5 @@ +export const RESPONSE_STATUS = { + SUCCESS: '1', + SERVER_ERROR: '1000', + NETWORK_ERROR: '1001' +} diff --git a/template/structure/src/main.js b/template/structure/src/main.js new file mode 100644 index 0000000..35c7b1c --- /dev/null +++ b/template/structure/src/main.js @@ -0,0 +1,20 @@ +import Vue from 'vue' +import { sync } from 'vuex-router-sync' +import App from './App.vue' +import router from './router/' +import store from './store/' +import './registerServiceWorker' + +import './registerElementUI' + +import './styles/app/index.scss' + +Vue.config.productionTip = false + +sync(store, router) + +new Vue({ + router, + store, + render: h => h(App) +}).$mount('#app') diff --git a/template/structure/src/router/index.js b/template/structure/src/router/index.js new file mode 100644 index 0000000..c59f25e --- /dev/null +++ b/template/structure/src/router/index.js @@ -0,0 +1,95 @@ +import Vue from 'vue' +import Router from 'vue-router' +import AppLayout from '@/components/layout/AppLayout.vue' +import BlankLayout from '@/components/layout/BlankLayout.vue' +import Home from '@/views/Home.vue' +import Login from '@/views/Login.vue' +import { Forbidden, NotFound, InternalServerError } from '@/components/errors' + +import UserInterceptor from './interceptors/user' +import RoleInterceptor from './interceptors/role' + +Vue.use(Router) + +const router = new Router({ + mode: 'history', + base: process.env.BASE_URL, + routes: [ + { + path: '/login', + component: BlankLayout, + children: [{ path: '', name: 'login', component: Login }] + }, + { + path: '/', + component: AppLayout, + children: [ + { + path: '', + name: 'home', + meta: { + requiresUser: true + }, + component: Home + }, + { + path: '/about', + name: 'about', + meta: { + requiresUser: true + }, + component: () => + import(/* webpackChunkName: "about" */ '@/views/About.vue') + }, + { + path: '/example', + name: 'example-list', + meta: { + requiresUser: true, + + breadcrumb: [ + { name: '示例模块', path: '/example' }, + { name: '列表页' } + ] + // breadcrumb: ['示例模块 /example', '列表页'] + }, + component: () => + import(/* webpackChunkName: "example" */ '@/views/example/Table.vue') + }, + { + path: '/example/new', + name: 'example-form', + meta: { + requiresUser: true, + + breadcrumb: ['示例模块 /example', '表单页'] + }, + component: () => + import(/* webpackChunkName: "example" */ '@/views/example/Form.vue') + }, + { + path: '/example/admin-authorized', + name: 'example-admin-authorized', + meta: { + requiresUser: true, + requiresRole: 'ADMIN', + + breadcrumb: ['示例模块 /example', '管理员权限'] + }, + component: () => + import(/* webpackChunkName: "example" */ '@/views/example/AdminAuthorized.vue') + }, + + { path: '/403', component: Forbidden }, + { path: '/404', component: NotFound }, + { path: '/500', component: InternalServerError }, + { path: '*', component: NotFound } + ] + } + ] +}) + +router.beforeEach(UserInterceptor) +router.beforeEach(RoleInterceptor) + +export default router diff --git a/template/structure/src/router/interceptors/role.js b/template/structure/src/router/interceptors/role.js new file mode 100644 index 0000000..18ce518 --- /dev/null +++ b/template/structure/src/router/interceptors/role.js @@ -0,0 +1,21 @@ +import store from '@/store/' + +export default function checkRoles(to, from, next) { + const { state } = store + const { requiresRole } = to.meta + + if (!requiresRole) { + next() + } else { + if (!state.user.id) { + next('/login') + } else { + const { roles = [] } = state.user + if (roles.indexOf(requiresRole) > -1) { + next() + } else { + next('/403') + } + } + } +} diff --git a/template/structure/src/router/interceptors/user.js b/template/structure/src/router/interceptors/user.js new file mode 100644 index 0000000..f750313 --- /dev/null +++ b/template/structure/src/router/interceptors/user.js @@ -0,0 +1,15 @@ +import store from '@/store/' + +export default function checkUser(to, from, next) { + const { state } = store + + if (to.matched.some(record => record.meta.requiresUser)) { + if (state.user.id) { + next() + } else { + next('/login') + } + } else { + next() + } +} diff --git a/template/structure/src/services/index.js b/template/structure/src/services/index.js new file mode 100644 index 0000000..882a71c --- /dev/null +++ b/template/structure/src/services/index.js @@ -0,0 +1,30 @@ +import * as api from '@/api' + +export async function getUser(username) { + return api.getUser(username) +} + +export async function getExampleList(param) { + const TYPES = { + '1': '个人', + '2': '组织' + } + + const STATUS = { + '0': '离线', + '1': '在线' + } + + return api.getExampleList(param).then(data => { + return { + ...data, + list: data.list.map(item => { + return { + ...item, + typeText: TYPES[item.type], + statusText: STATUS[item.status] + } + }) + } + }) +} diff --git a/template/structure/src/store/index.js b/template/structure/src/store/index.js new file mode 100644 index 0000000..f8e5a59 --- /dev/null +++ b/template/structure/src/store/index.js @@ -0,0 +1,49 @@ +import Vue from 'vue' +import Vuex from 'vuex' + +import * as service from '@/services' + +import example from './modules/example' + +Vue.use(Vuex) + +function initialState() { + return { + user: { + id: 0, + name: '', + roles: [] + } + } +} + +export default new Vuex.Store({ + state: initialState(), + mutations: { + get(state, payload) { + state.user = payload.data || {} + } + }, + actions: { + async getUser({ commit }, username) { + const data = await service.getUser(username) + + commit({ + type: 'get', + data + }) + }, + + resetUser({ commit }) { + const { user } = initialState() + commit({ + type: 'get', + data: user + }) + } + }, + + modules: { + example + } +}) diff --git a/template/structure/src/store/modules/example.js b/template/structure/src/store/modules/example.js new file mode 100644 index 0000000..f8da839 --- /dev/null +++ b/template/structure/src/store/modules/example.js @@ -0,0 +1,47 @@ +import * as service from '@/services' + +export const example = { + namespaced: true, + state: { + loading: true, + exampleList: { + page: { + order: 'descending', + orderBy: 'id', + pageNo: 1, + pageSize: 20, + total: 0 + }, + list: [] + } + }, + mutations: { + loading(state, loading) { + state.loading = loading + }, + + search(state, payload) { + state.exampleList = payload.data + } + }, + actions: { + async searchExampleList({ commit }, param) { + commit('loading', true) + + try { + const data = await service.getExampleList(param) + + commit({ + type: 'search', + data + }) + } catch (err) { + throw err + } finally { + commit('loading', false) + } + } + } +} + +export default example diff --git a/template/structure/src/styles/app/base/index.scss b/template/structure/src/styles/app/base/index.scss new file mode 100644 index 0000000..8a3b1cf --- /dev/null +++ b/template/structure/src/styles/app/base/index.scss @@ -0,0 +1,9 @@ +html, +body { + margin: 0; + height: 100%; + + #app { + height: 100%; + } +} diff --git a/template/structure/src/styles/app/form/index.scss b/template/structure/src/styles/app/form/index.scss new file mode 100644 index 0000000..50fee9e --- /dev/null +++ b/template/structure/src/styles/app/form/index.scss @@ -0,0 +1,19 @@ +.standard-form-container { + .standard-form { + width: 600px; + + .el-radio-group .el-radio, + .el-checkbox-group .el-checkbox { + float: left; + width: 160px; + padding-right: 20px; + margin: 0; + padding: 0; + } + + .el-radio-group .el-radio + .el-radio, + .el-checkbox-group .el-checkbox + .el-checkbox { + margin-left: 0; + } + } +} diff --git a/template/structure/src/styles/app/helper/index.scss b/template/structure/src/styles/app/helper/index.scss new file mode 100644 index 0000000..e0cea79 --- /dev/null +++ b/template/structure/src/styles/app/helper/index.scss @@ -0,0 +1,24 @@ +.pull-left { + float: left; +} + +.pull-right { + float: right; +} + +.text-center { + text-align: center; +} + +.bottom-gutter { + margin-bottom: 20px; +} + +.clearfix:before, +.clearfix:after { + display: table; + content: ''; +} +.clearfix:after { + clear: both; +} diff --git a/template/structure/src/styles/app/index.scss b/template/structure/src/styles/app/index.scss new file mode 100644 index 0000000..177b18d --- /dev/null +++ b/template/structure/src/styles/app/index.scss @@ -0,0 +1,4 @@ +@import './base/index'; +@import './form/index'; +@import './table/index'; +@import './helper/index'; diff --git a/template/structure/src/styles/app/table/index.scss b/template/structure/src/styles/app/table/index.scss new file mode 100644 index 0000000..f9ddff8 --- /dev/null +++ b/template/structure/src/styles/app/table/index.scss @@ -0,0 +1,37 @@ +.standard-table { + .standard-table-filter { + margin-bottom: 20px; + } + + .standard-table-toolbar { + margin-bottom: 20px; + + .standard-table-toolbar__row { + display: flex; + + .standard-table-toolbar__buttons { + flex-grow: 1; + } + + .standard-table-toolbar__pagination { + font-weight: bold; + } + } + } + + .standard-table-list { + .standard-table-list__expand { + font-size: 0; + + label { + width: 120px; + color: #99a9bf; + } + .el-form-item { + margin-right: 0; + margin-bottom: 0; + width: 33%; + } + } + } +} diff --git a/template/structure/src/styles/theme/element-variables.scss b/template/structure/src/styles/theme/element-variables.scss index 819f795..a7c568a 100644 --- a/template/structure/src/styles/theme/element-variables.scss +++ b/template/structure/src/styles/theme/element-variables.scss @@ -2,4 +2,4 @@ $--color-primary: #409eff; $--font-path: '~element-ui/lib/theme-chalk/fonts'; -@import "~element-ui/packages/theme-chalk/src/index"; +@import '~element-ui/packages/theme-chalk/src/index'; diff --git a/template/structure/src/utils/request.js b/template/structure/src/utils/request.js new file mode 100644 index 0000000..fe569a1 --- /dev/null +++ b/template/structure/src/utils/request.js @@ -0,0 +1,18 @@ +import { RESPONSE_STATUS } from '@/constants' + +export function delay(t = 500, resolved = true) { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolved ? resolve() : reject(new Error('delay error')) + }, t) + }) +} + +export function checkCode(response) { + if (RESPONSE_STATUS.SUCCESS === response.status) { + return response.data + } else { + const { status = 0, message = 'request failed' } = response + throw new Error(`Error ${status}: ${message}`) + } +} diff --git a/template/structure/src/views/About.vue b/template/structure/src/views/About.vue new file mode 100644 index 0000000..6e4df83 --- /dev/null +++ b/template/structure/src/views/About.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/template/structure/src/views/Home.vue b/template/structure/src/views/Home.vue index 33e2961..e85b771 100644 --- a/template/structure/src/views/Home.vue +++ b/template/structure/src/views/Home.vue @@ -1,11 +1,122 @@ + + diff --git a/template/structure/src/views/Login.vue b/template/structure/src/views/Login.vue new file mode 100644 index 0000000..c0f51d8 --- /dev/null +++ b/template/structure/src/views/Login.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/template/structure/src/views/example/AdminAuthorized.vue b/template/structure/src/views/example/AdminAuthorized.vue new file mode 100644 index 0000000..256d07b --- /dev/null +++ b/template/structure/src/views/example/AdminAuthorized.vue @@ -0,0 +1,17 @@ + + + diff --git a/template/structure/src/views/example/Form.vue b/template/structure/src/views/example/Form.vue new file mode 100644 index 0000000..b848777 --- /dev/null +++ b/template/structure/src/views/example/Form.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/template/structure/src/views/example/Table.vue b/template/structure/src/views/example/Table.vue new file mode 100644 index 0000000..5f9917c --- /dev/null +++ b/template/structure/src/views/example/Table.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/template/structure/src/views/example/config.js b/template/structure/src/views/example/config.js new file mode 100644 index 0000000..4fcbf97 --- /dev/null +++ b/template/structure/src/views/example/config.js @@ -0,0 +1,33 @@ +export const rules = { + name: [ + { required: true, message: '请输入名称', trigger: 'blur' }, + { min: 5, max: 20, message: '长度在 5 到 20 个字符', trigger: 'blur' } + ], + region: [{ required: true, message: '请选择地区', trigger: 'change' }], + date: [ + { + type: 'date', + required: true, + message: '请选择日期', + trigger: 'change' + } + ], + time: [ + { + type: 'date', + required: true, + message: '请选择时间', + trigger: 'change' + } + ], + type: [ + { + type: 'array', + required: true, + message: '请至少选择一个类型', + trigger: 'change' + } + ], + status: [{ required: true, message: '请选择状态', trigger: 'change' }], + desc: [{ required: true, message: '请填写详细描述', trigger: 'blur' }] +} diff --git a/template/structure/tests/unit/example.spec.js b/template/structure/tests/unit/example.spec.js new file mode 100644 index 0000000..7673c17 --- /dev/null +++ b/template/structure/tests/unit/example.spec.js @@ -0,0 +1,9 @@ +import { shallowMount } from '@vue/test-utils' +import AppFooter from '@/components/layout/AppFooter.vue' + +describe('AppFooter.vue', () => { + it('render app footer', () => { + const wrapper = shallowMount(AppFooter, {}) + expect(wrapper.text()).toMatch('Copyright') + }) +})