├── server ├── .eslintignore ├── .eslintrc ├── app │ ├── common │ │ ├── role.js │ │ ├── responseCode.js │ │ └── serverResponse.js │ ├── extend │ │ └── response.js │ ├── controller │ │ ├── home.js │ │ └── api │ │ │ └── user.js │ ├── router.js │ ├── service │ │ ├── post.js │ │ ├── token.js │ │ └── user.js │ ├── router │ │ └── user.js │ ├── middleware │ │ ├── responseTime.js │ │ └── checkLogin.js │ ├── model │ │ ├── post.js │ │ ├── token.js │ │ ├── index.js │ │ └── user.js │ └── migrate │ │ └── 20180315-create-posts.js ├── server.js ├── .gitignore ├── .travis.yml ├── config │ ├── sequelize.js │ ├── config.local.js │ ├── config.prod.js │ ├── plugin.js │ └── config.default.js ├── .sequelizerc ├── appveyor.yml ├── .autod.conf.js ├── test │ └── app │ │ ├── controller │ │ └── home.test.js │ │ └── service │ │ └── token.test.js ├── README.md └── package.json ├── client ├── src │ ├── views │ │ ├── system │ │ │ ├── user │ │ │ │ └── list.less │ │ │ └── role │ │ │ │ ├── list.less │ │ │ │ └── list.vue │ │ ├── main-components │ │ │ ├── shrinkable-menu │ │ │ │ ├── styles │ │ │ │ │ └── menu.less │ │ │ │ ├── shrinkable-menu.vue │ │ │ │ └── components │ │ │ │ │ ├── sidebarMenuShrink.vue │ │ │ │ │ └── sidebarMenu.vue │ │ │ ├── breadcrumb-nav.vue │ │ │ ├── message-tip.vue │ │ │ ├── lockscreen │ │ │ │ ├── components │ │ │ │ │ ├── locking-page.vue │ │ │ │ │ └── unlock.vue │ │ │ │ ├── lockscreen.vue │ │ │ │ └── styles │ │ │ │ │ └── unlock.less │ │ │ └── fullscreen.vue │ │ ├── advanced-router │ │ │ ├── advanced-router.less │ │ │ ├── component │ │ │ │ ├── expandRow.vue │ │ │ │ └── shopping-info.vue │ │ │ ├── mutative-router.vue │ │ │ └── argument-page.vue │ │ ├── example │ │ │ └── table │ │ │ │ ├── components │ │ │ │ ├── editable-table.less │ │ │ │ ├── ExportExcel.vue │ │ │ │ ├── dragableTable.vue │ │ │ │ ├── table.less │ │ │ │ ├── multiPageTable.vue │ │ │ │ └── table_data.js │ │ │ │ ├── index.vue │ │ │ │ ├── data │ │ │ │ ├── search.js │ │ │ │ ├── table2csv.js │ │ │ │ └── table2excel.js │ │ │ │ ├── searchable-table.vue │ │ │ │ └── exportable-table.vue │ │ ├── home │ │ │ ├── components │ │ │ │ ├── styles │ │ │ │ │ ├── infor-card.less │ │ │ │ │ └── to-do-list-item.less │ │ │ │ ├── mapDataTable.vue │ │ │ │ ├── toDoListItem.vue │ │ │ │ ├── inforCard.vue │ │ │ │ ├── dataSourcePie.vue │ │ │ │ ├── userFlow.vue │ │ │ │ ├── map.vue │ │ │ │ ├── visiteVolume.vue │ │ │ │ ├── countUp.vue │ │ │ │ └── serviceRequests.vue │ │ │ ├── map-data │ │ │ │ ├── get-geography-value.js │ │ │ │ ├── get-city-value.js │ │ │ │ └── get-style-json.js │ │ │ └── home.less │ │ ├── error-page │ │ │ ├── error-page.less │ │ │ ├── 404.vue │ │ │ ├── 403.vue │ │ │ ├── 500.vue │ │ │ ├── 404.less │ │ │ ├── 500.less │ │ │ ├── 403.less │ │ │ └── error-page.vue │ │ ├── login.less │ │ ├── own-space │ │ │ └── own-space.less │ │ ├── message │ │ │ └── message.less │ │ └── login.vue │ ├── images │ │ ├── logo.png │ │ ├── logo-min.png │ │ └── cropper-test.png │ ├── styles │ │ ├── login_bg.jpg │ │ ├── fonts │ │ │ ├── ionicons.eot │ │ │ ├── ionicons.ttf │ │ │ └── ionicons.woff │ │ ├── loading.less │ │ └── common.less │ ├── vendors │ │ ├── vendors.base.js │ │ └── vendors.exten.js │ ├── mock │ │ ├── routes.js │ │ ├── index.js │ │ ├── db.js │ │ ├── server.js │ │ ├── user.js │ │ └── login.js │ ├── store │ │ ├── index.js │ │ └── modules │ │ │ └── user.js │ ├── libs │ │ ├── apiMap.js │ │ ├── axios.js │ │ ├── toast │ │ │ └── index.js │ │ ├── table2excel.js │ │ ├── http.js │ │ └── plugin │ │ │ └── index.js │ ├── app.vue │ ├── locale │ │ ├── index.js │ │ └── locale.js │ ├── template │ │ └── index.ejs │ ├── main.js │ └── router │ │ ├── index.js │ │ └── router.js ├── .travis.yml ├── td_icon.ico ├── config │ ├── prod.env.js │ ├── dev.env.js │ └── index.js ├── build │ ├── config.js │ ├── webpack.dev.config.js │ ├── webpack.prod.config.js │ └── webpack.base.config.js ├── .eslintignore ├── .gitignore ├── .babelrc ├── .editorconfig ├── .postcssrc.js ├── index.prod.html ├── .eslintrc.json ├── index.html ├── LICENSE ├── gulpfile.js ├── README.md └── package.json ├── .circleci └── config.yml ├── README.md ├── .vscode └── launch.json ├── LICENSE └── .gitignore /server/.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /client/src/views/system/user/list.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-egg" 3 | } 4 | -------------------------------------------------------------------------------- /client/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | script: 5 | - npm run test 6 | -------------------------------------------------------------------------------- /client/td_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twang281314/egg-vue-admin/HEAD/client/td_icon.ico -------------------------------------------------------------------------------- /client/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /client/src/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twang281314/egg-vue-admin/HEAD/client/src/images/logo.png -------------------------------------------------------------------------------- /client/src/images/logo-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twang281314/egg-vue-admin/HEAD/client/src/images/logo-min.png -------------------------------------------------------------------------------- /client/src/styles/login_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twang281314/egg-vue-admin/HEAD/client/src/styles/login_bg.jpg -------------------------------------------------------------------------------- /client/build/config.js: -------------------------------------------------------------------------------- 1 | import Env from './env'; 2 | 3 | let config = { 4 | env: Env 5 | }; 6 | export default config; 7 | -------------------------------------------------------------------------------- /client/.eslintignore: -------------------------------------------------------------------------------- 1 | src/vendors 2 | src/libs/table2excel.js 3 | build 4 | router.js 5 | src/views/my-components/text-editor/tinymce -------------------------------------------------------------------------------- /client/src/images/cropper-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twang281314/egg-vue-admin/HEAD/client/src/images/cropper-test.png -------------------------------------------------------------------------------- /client/src/styles/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twang281314/egg-vue-admin/HEAD/client/src/styles/fonts/ionicons.eot -------------------------------------------------------------------------------- /client/src/styles/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twang281314/egg-vue-admin/HEAD/client/src/styles/fonts/ionicons.ttf -------------------------------------------------------------------------------- /client/src/styles/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twang281314/egg-vue-admin/HEAD/client/src/styles/fonts/ionicons.woff -------------------------------------------------------------------------------- /client/src/views/main-components/shrinkable-menu/styles/menu.less: -------------------------------------------------------------------------------- 1 | .ivu-shrinkable-menu{ 2 | height: 100%; 3 | width: 100%; 4 | } -------------------------------------------------------------------------------- /client/src/views/system/role/list.less: -------------------------------------------------------------------------------- 1 | .search .ivu-form-item { 2 | margin-bottom: 0px; 3 | vertical-align: top; 4 | zoom: 1; 5 | } -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules 3 | .project 4 | .vscode 5 | .history 6 | .DS_Store 7 | \.settings/ 8 | build/env.js 9 | dist/ 10 | -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["stage-3", "env"], 3 | "plugins": ["transform-runtime", "syntax-dynamic-import"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /client/src/vendors/vendors.base.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import iView from 'iview'; 3 | import VueRouter from 'vue-router'; 4 | import Vuex from 'vuex'; 5 | -------------------------------------------------------------------------------- /server/app/common/role.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | module.exports = { 5 | // 普通用户 6 | ROLE_CUSTOMER: 0, 7 | // 管理员 8 | ROLE_ADMAIN: 1 9 | }; -------------------------------------------------------------------------------- /client/src/views/advanced-router/advanced-router.less: -------------------------------------------------------------------------------- 1 | .advanced-router{ 2 | height: 240px !important; 3 | &-tip-p{ 4 | padding: 10px 0; 5 | } 6 | } -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:9.8.0 6 | steps: 7 | - checkout 8 | - run: echo "A first hello" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egg-vue-admin 2 | EggJS+Mysql+sequelize+Vue后台管理系统 3 | 4 | https://github.com/eggjs/egg 5 | 6 | https://github.com/iview/iview 7 | 8 | https://github.com/vuejs/vue 9 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | charset = utf-8 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const egg = require('egg'); 2 | 3 | const workers = Number(process.argv[2] || require('os').cpus().length); 4 | egg.startCluster({ 5 | workers, 6 | baseDir: __dirname, 7 | }); -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | npm-debug.log 3 | yarn-error.log 4 | node_modules/ 5 | package-lock.json 6 | yarn.lock 7 | coverage/ 8 | .idea/ 9 | run/ 10 | .DS_Store 11 | *.sw* 12 | *.un~ 13 | -------------------------------------------------------------------------------- /client/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /server/app/common/responseCode.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | module.exports = { 5 | SUCCESS: 0, 6 | ERROR: 1, 7 | NEED_LOGIN: 10, 8 | NO_AUTH: 20, 9 | ILLEGAL_ARGUMENT: 2, 10 | }; -------------------------------------------------------------------------------- /client/src/views/example/table/components/editable-table.less: -------------------------------------------------------------------------------- 1 | .show-edit-btn{ 2 | display: none; 3 | margin-left: -10px; 4 | } 5 | .ivu-table-cell:hover .show-edit-btn{ 6 | display: inline-block; 7 | } -------------------------------------------------------------------------------- /client/src/mock/routes.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const routers = { 4 | "/comment/get.action": "/getComment", 5 | "/comment/add.action": "/addComment", 6 | "/api/user/list": "/getUserList" 7 | } 8 | 9 | module.exports = routers; -------------------------------------------------------------------------------- /server/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | install: 6 | - npm i npminstall && npminstall 7 | script: 8 | - npm run ci 9 | after_script: 10 | - npminstall codecov && codecov 11 | -------------------------------------------------------------------------------- /server/app/extend/response.js: -------------------------------------------------------------------------------- 1 | const ServerResponse = require('../common/serverResponse'); 2 | const ResponseCode = require('../common/responseCode'); 3 | 4 | module.exports = { 5 | ResponseCode, 6 | ServerResponse, 7 | }; -------------------------------------------------------------------------------- /client/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /server/config/sequelize.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const local = require('./config.local'); 4 | const prod = require('./config.prod'); 5 | 6 | module.exports = { 7 | development: local.sequelize, 8 | production: prod.sequelize 9 | }; 10 | -------------------------------------------------------------------------------- /client/src/vendors/vendors.exten.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | // import echarts from 'echarts'; 3 | import Cookies from 'js-cookie'; 4 | import clipboard from 'clipboard'; 5 | import html2canvas from 'html2canvas'; 6 | import rasterizehtml from 'rasterizehtml'; 7 | -------------------------------------------------------------------------------- /client/src/styles/loading.less: -------------------------------------------------------------------------------- 1 | .demo-spin-icon-load{ 2 | animation: ani-demo-spin 1s linear infinite; 3 | } 4 | @keyframes ani-demo-spin { 5 | from { transform: rotate(0deg);} 6 | 50% { transform: rotate(180deg);} 7 | to { transform: rotate(360deg);} 8 | } -------------------------------------------------------------------------------- /server/app/controller/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Controller = require('egg').Controller; 4 | 5 | class HomeController extends Controller { 6 | async index() { 7 | this.ctx.body = 'hi, egg'; 8 | } 9 | } 10 | 11 | module.exports = HomeController; 12 | -------------------------------------------------------------------------------- /server/.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | config: path.resolve('config', 'sequelize.js'), 5 | migrationsPath: path.resolve('app', 'migrate'), 6 | seedersPath: path.resolve('app', 'seed'), 7 | modelsPath: path.resolve('app', 'model') 8 | } -------------------------------------------------------------------------------- /server/app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @param {Egg.Application} app - egg application 5 | */ 6 | module.exports = app => { 7 | const { router, controller } = app; 8 | router.get('/', controller.home.index); 9 | 10 | require('./router/user')(app); 11 | }; 12 | -------------------------------------------------------------------------------- /client/src/views/home/components/styles/infor-card.less: -------------------------------------------------------------------------------- 1 | .infor-card-icon-con{ 2 | height: 100%; 3 | } 4 | .height-100{ 5 | height: 100%; 6 | } 7 | .infor-card-con{ 8 | height: 100px; 9 | } 10 | .infor-intro-text{ 11 | font-size:12px; 12 | font-weight:500; 13 | color:#C8C8C8; 14 | } -------------------------------------------------------------------------------- /server/app/service/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Service = require('egg').Service; 4 | 5 | class PostService extends Service { 6 | 7 | async list() { 8 | const posts = await this.ctx.model.User.findAll(); 9 | return posts; 10 | } 11 | } 12 | 13 | module.exports = PostService; -------------------------------------------------------------------------------- /server/config/config.local.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.sequelize = { 4 | dialect: 'mysql', // support: mysql, mariadb, postgres, mssql 5 | dialectOptions: { 6 | charset: 'utf8mb4' 7 | }, 8 | database: 'egg_db', 9 | host: '', 10 | port: '', 11 | username: '', 12 | password: '', 13 | timezone : "+08:00" 14 | }; -------------------------------------------------------------------------------- /server/appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '8' 4 | 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm i npminstall && node_modules\.bin\npminstall 8 | 9 | test_script: 10 | - node --version 11 | - npm --version 12 | - npm run test 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /client/src/views/system/role/list.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /server/config/config.prod.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.sequelize = { 4 | dialect: 'mysql', // support: mysql, mariadb, postgres, mssql 5 | dialectOptions: { 6 | charset: 'utf8mb4' 7 | }, 8 | database: 'blog4', 9 | host: 'anytao.net', 10 | port: '3306', 11 | username: 'root', 12 | password: '', 13 | timezone : "+08:00" 14 | }; -------------------------------------------------------------------------------- /server/app/router/user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = app => { 4 | const checkLogin = app.middleware.checkLogin({}); 5 | const { router, controller } = app; 6 | router.post('/api/user/login', controller.api.user.login); 7 | router.post('/api/user/add', checkLogin,controller.api.user.add); 8 | router.get('/api/user/list', checkLogin,controller.api.user.list); 9 | } -------------------------------------------------------------------------------- /server/config/plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // had enabled by egg 4 | // exports.static = true; 5 | 6 | /** 7 | * 添加egg-sequelize插件配置 8 | */ 9 | exports.sequelize = { 10 | enable: true, 11 | package: 'egg-sequelize' 12 | }; 13 | 14 | exports.cors = { 15 | enable: true, 16 | package: 'egg-cors', 17 | }; 18 | 19 | exports.validate = { 20 | enable: true, 21 | package: 'egg-validate', 22 | }; -------------------------------------------------------------------------------- /client/src/views/example/table/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /client/src/mock/index.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | 3 | import loginAPI from './login.js'; 4 | import userAPI from './user.js'; 5 | 6 | 7 | // 登录相关 8 | Mock.mock(/\/login\/login/, 'post', loginAPI.loginByUsername) 9 | Mock.mock(/\/login\/logout/, 'post', loginAPI.logout) 10 | Mock.mock(/\/api\/user\/info\.*/, 'get', loginAPI.getUserInfo) 11 | Mock.mock(/\/api\/user\/list\.*/, 'get', userAPI.getUserList) 12 | 13 | export default Mock -------------------------------------------------------------------------------- /client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | import app from './modules/app'; 5 | import user from './modules/user'; 6 | 7 | Vue.use(Vuex); 8 | 9 | const store = new Vuex.Store({ 10 | state: { 11 | // 12 | }, 13 | mutations: { 14 | // 15 | }, 16 | actions: { 17 | 18 | }, 19 | modules: { 20 | app, 21 | user 22 | } 23 | }); 24 | 25 | export default store; 26 | -------------------------------------------------------------------------------- /client/src/views/error-page/error-page.less: -------------------------------------------------------------------------------- 1 | .error-page{ 2 | &-show{ 3 | width: 100%; 4 | height: 180px; 5 | transform: scale(0.4); 6 | } 7 | &-cover{ 8 | position: absolute; 9 | width: 100%; 10 | height: 100%; 11 | left: 0; 12 | top: 0; 13 | } 14 | &-intro-con{ 15 | height: 180px; 16 | p{ 17 | display: block; 18 | width: 100%; 19 | text-align: center; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /client/src/views/home/components/styles/to-do-list-item.less: -------------------------------------------------------------------------------- 1 | .to-do-list-item-text{ 2 | word-break:keep-all; 3 | white-space:nowrap; 4 | overflow: hidden; 5 | text-overflow: ellipsis; 6 | font-weight: 500; 7 | cursor: pointer; 8 | height: 36px; 9 | 10 | .height-100{ 11 | height: 100%; 12 | } 13 | .infor-icon-row{ 14 | color: #c8c8c8; 15 | } 16 | } 17 | .hasDid{ 18 | text-decoration: line-through; 19 | color: gray; 20 | font-weight: 100; 21 | } -------------------------------------------------------------------------------- /client/src/mock/db.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var Mock = require('mockjs'); 4 | var userAPI = require('./user.js'); 5 | 6 | 7 | 8 | module.exports = { 9 | getComment: Mock.mock({ 10 | "error": 0, 11 | "message": "success", 12 | "result|40": [{ 13 | "author": "@name", 14 | "comment": "@cparagraph", 15 | "date": "@datetime" 16 | }] 17 | }), 18 | addComment: Mock.mock({ 19 | "error": 0, 20 | "message": "success", 21 | "result": [] 22 | }), 23 | getUserList: userAPI.getUserList 24 | }; -------------------------------------------------------------------------------- /server/.autod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | write: true, 5 | prefix: '^', 6 | plugin: 'autod-egg', 7 | test: [ 8 | 'test', 9 | 'benchmark', 10 | ], 11 | dep: [ 12 | 'egg', 13 | 'egg-scripts', 14 | ], 15 | devdep: [ 16 | 'egg-ci', 17 | 'egg-bin', 18 | 'egg-mock', 19 | 'autod', 20 | 'autod-egg', 21 | 'eslint', 22 | 'eslint-config-egg', 23 | 'webstorm-disable-index', 24 | ], 25 | exclude: [ 26 | './test/fixtures', 27 | './dist', 28 | ], 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /client/index.prod.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台管理系统 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /client/src/views/example/table/components/ExportExcel.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Egg", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}\\server", 9 | "runtimeExecutable": "npm", 10 | "windows": { "runtimeExecutable": "npm.cmd" }, 11 | "runtimeArgs": [ "run", "debug", "--", "--inspect-brk" ], 12 | "console": "integratedTerminal", 13 | "protocol": "auto", 14 | "restart": true, 15 | "port": 9229, 16 | "autoAttachChildProcesses": true 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /server/test/app/controller/home.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { app, assert } = require('egg-mock/bootstrap'); 4 | 5 | describe('test/app/controller/home.test.js', () => { 6 | 7 | it('should assert', function* () { 8 | const pkg = require('../../../package.json'); 9 | assert(app.config.keys.startsWith(pkg.name)); 10 | 11 | // const ctx = app.mockContext({}); 12 | // yield ctx.service.xx(); 13 | }); 14 | 15 | it('should GET /', () => { 16 | return app.httpRequest() 17 | .get('/') 18 | .expect('hi, egg') 19 | .expect(200); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /client/src/libs/apiMap.js: -------------------------------------------------------------------------------- 1 | 2 | const methodMap = { 3 | 4 | /** 管理登陆接口 **/ 5 | AdminLogin: {url: '/user/login', method: 'post'}, 6 | /** 用户登陆解锁 **/ 7 | AdminUnlock: {url: '/passport/unlock', method: 'post'}, 8 | /** 后台用户列表 **/ 9 | UserList: {url: '/user/list', method: 'get'}, 10 | /** 添加后台用户 **/ 11 | UserAdd: {url: '/user/add', method: 'post'}, 12 | /** 编辑后台用户 **/ 13 | AdminEditUser: {url: '/user/edit', method: 'post'}, 14 | /** 重置后台用户密码 **/ 15 | RestPassword: {url: '/user/rest_password', method: 'post'}, 16 | 17 | UserInfo:{ url:'/user/getUserInfo',method:'get'} 18 | 19 | }; 20 | export default methodMap; 21 | -------------------------------------------------------------------------------- /server/app/middleware/responseTime.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: anytao 3 | * @Date: 2018-04-20 14:45:50 4 | * @Last Modified by: anytao 5 | * @Last Modified time: 2018-04-20 14:45:50 6 | */ 7 | 8 | module.exports = responseTime; 9 | 10 | /** 11 | * Add X-Response-Time header field. 12 | * 13 | * @return {Function} 14 | * @api public 15 | */ 16 | 17 | function responseTime() { 18 | return function responseTime(ctx, next){ 19 | var start = Date.now(); 20 | return next().then(function () { 21 | var delta = Math.ceil(Date.now() - start); 22 | ctx.set('X-Response-Time', delta + 'ms'); 23 | }); 24 | } 25 | } -------------------------------------------------------------------------------- /server/app/middleware/checkLogin.js: -------------------------------------------------------------------------------- 1 | const { 2 | ROLE_ADMAIN 3 | } = require('../common/role'); 4 | 5 | module.exports = options => { 6 | 7 | return async function checkLogin(ctx, next) { 8 | const user = ctx.session.currentUser; 9 | if (!user) return ctx.body = ctx.response.ServerResponse.createByErrorCodeMsg(ctx.response.ResponseCode.NEED_LOGIN, '用户未登录'); 10 | 11 | if (options.checkAdmin && user.role !== ROLE_ADMAIN) return ctx.body = ctx.response.ServerResponse.createByErrorCodeMsg(ctx.response.ResponseCode.NO_AUTH, '用户不是管理员无权操作'); 12 | // else await next(); 13 | 14 | await next(); 15 | }; 16 | }; -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "root": true, 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "env": { 9 | "browser": true, 10 | "node": true 11 | }, 12 | "plugins": [ "html", "standard" ], 13 | "rules": { 14 | "indent": ["error", 4, { "SwitchCase": 1 }], 15 | "quotes": ["error", "single"], 16 | "semi": ["error", "always"], 17 | "no-console": ["error"], 18 | "no-empty": 2, 19 | "no-eq-null": 2, 20 | "no-new": 0, 21 | "no-fallthrough": 0, 22 | "no-unreachable": 0 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/src/mock/server.js: -------------------------------------------------------------------------------- 1 | 2 | const jsonServer = require('json-server') 3 | const db = require('./db.js') 4 | const routes = require('./routes.js') 5 | const port = 3000; 6 | 7 | const server = jsonServer.create() 8 | const router = jsonServer.router(db) 9 | const middlewares = jsonServer.defaults() 10 | const rewriter = jsonServer.rewriter(routes) 11 | 12 | server.use(middlewares) 13 | // 将 POST 请求转为 GET 14 | server.use((request, res, next) => { 15 | request.method = 'GET'; 16 | next(); 17 | }) 18 | 19 | server.use(rewriter) // 注意:rewriter 的设置一定要在 router 设置之前 20 | server.use(router) 21 | 22 | server.listen(port, () => { 23 | console.log('open mock server at localhost:' + port) 24 | }) -------------------------------------------------------------------------------- /server/app/model/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = app => { 4 | const { 5 | INTEGER, 6 | STRING, 7 | DATE 8 | } = app.Sequelize; 9 | 10 | return app.model.define('posts', { 11 | id: { 12 | type: INTEGER, 13 | primaryKey: true, 14 | autoIncrement: true, 15 | }, 16 | title: { 17 | type: STRING, 18 | comment: '标题' 19 | }, 20 | body: { 21 | type: STRING, 22 | comment: '内容' 23 | }, 24 | created_at: { 25 | type: DATE 26 | }, 27 | updated_at: { 28 | type: DATE 29 | } 30 | }); 31 | }; -------------------------------------------------------------------------------- /client/src/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 25 | 26 | 38 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 后台管理系统 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /client/src/views/main-components/breadcrumb-nav.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 28 | 29 | -------------------------------------------------------------------------------- /server/app/migrate/20180315-create-posts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up(queryInterface, Sequelize) { 5 | const { INTEGER, STRING, DATE } = Sequelize; 6 | return queryInterface.createTable('posts', { 7 | id: { 8 | type: INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true, 11 | }, 12 | title: { 13 | type: STRING, 14 | comment: '标题' 15 | }, 16 | body: { 17 | type: STRING, 18 | comment: '内容' 19 | }, 20 | created_at: { 21 | type: DATE 22 | }, 23 | updated_at: { 24 | type: DATE 25 | } 26 | }); 27 | }, 28 | 29 | down(queryInterface) { 30 | return queryInterface.dropTable('posts'); 31 | } 32 | }; -------------------------------------------------------------------------------- /client/src/views/home/components/mapDataTable.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 32 | -------------------------------------------------------------------------------- /client/src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie'; 2 | 3 | const user = { 4 | state: {}, 5 | mutations: { 6 | logout (state, vm) { 7 | Cookies.remove('user'); 8 | Cookies.remove('password'); 9 | Cookies.remove('access'); 10 | // 恢复默认样式 11 | let themeLink = document.querySelector('link[name="theme"]'); 12 | themeLink.setAttribute('href', ''); 13 | // 清空打开的页面等数据,但是保存主题数据 14 | let theme = ''; 15 | if (localStorage.theme) { 16 | theme = localStorage.theme; 17 | } 18 | localStorage.clear(); 19 | if (theme) { 20 | localStorage.theme = theme; 21 | } 22 | } 23 | } 24 | }; 25 | 26 | export default user; 27 | -------------------------------------------------------------------------------- /client/src/views/home/map-data/get-geography-value.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '海门': [121.15, 31.89], 3 | '鄂尔多斯': [109.781327, 39.608266], 4 | '招远': [120.38, 37.35], 5 | '舟山': [122.207216, 29.985295], 6 | '齐齐哈尔': [123.97, 47.33], 7 | '广州': [113.23, 23.16], 8 | '盐城': [120.13, 33.38], 9 | '赤峰': [118.87, 42.28], 10 | '深圳': [114.07, 22.62], 11 | '青岛': [120.33, 36.07], 12 | '北京': [116.46, 39.92], 13 | '乳山': [121.52, 36.89], 14 | '金昌': [102.188043, 38.520089], 15 | '泉州': [118.58, 24.93], 16 | '莱西': [120.53, 36.86], 17 | '日照': [119.46, 35.42], 18 | '胶南': [119.97, 35.88], 19 | '南通': [121.05, 32.08], 20 | '拉萨': [91.11, 29.97], 21 | '云浮': [112.02, 22.93], 22 | '梅州': [116.1, 24.55], 23 | '文登': [122.05, 37.2], 24 | '上海': [121.48, 31.22] 25 | }; 26 | -------------------------------------------------------------------------------- /client/src/locale/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Locales from './locale'; 3 | import zhLocale from 'iview/src/locale/lang/zh-CN'; 4 | import enLocale from 'iview/src/locale/lang/en-US'; 5 | import zhTLocale from 'iview/src/locale/lang/zh-TW'; 6 | 7 | // 自动设置语言 8 | const navLang = navigator.language; 9 | const localLang = (navLang === 'zh-CN' || navLang === 'en-US') ? navLang : false; 10 | const lang = window.localStorage.lang || localLang || 'zh-CN'; 11 | 12 | Vue.config.lang = lang; 13 | 14 | // 多语言配置 15 | const locales = Locales; 16 | const mergeZH = Object.assign(zhLocale, locales['zh-CN']); 17 | const mergeEN = Object.assign(enLocale, locales['en-US']); 18 | const mergeTW = Object.assign(zhTLocale, locales['zh-TW']); 19 | Vue.locale('zh-CN', mergeZH); 20 | Vue.locale('en-US', mergeEN); 21 | Vue.locale('zh-TW', mergeTW); 22 | -------------------------------------------------------------------------------- /client/src/template/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= htmlWebpackPlugin.options.title %> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /client/src/views/home/map-data/get-city-value.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | {name: '海门', value: 45}, 3 | {name: '鄂尔多斯', value: 34}, 4 | {name: '招远', value: 47}, 5 | {name: '舟山', value: 22}, 6 | {name: '齐齐哈尔', value: 74}, 7 | {name: '广州', value: 138}, 8 | {name: '盐城', value: 15}, 9 | {name: '北京', value: 250}, 10 | {name: '深圳', value: 141}, 11 | {name: '赤峰', value: 16}, 12 | {name: '青岛', value: 89}, 13 | {name: '乳山', value: 18}, 14 | {name: '金昌', value: 34}, 15 | {name: '泉州', value: 21}, 16 | {name: '莱西', value: 66}, 17 | {name: '日照', value: 45}, 18 | {name: '胶南', value: 23}, 19 | {name: '南通', value: 54}, 20 | {name: '拉萨', value: 22}, 21 | {name: '云浮', value: 78}, 22 | {name: '梅州', value: 23}, 23 | {name: '文登', value: 78}, 24 | {name: '上海', value: 218} 25 | ]; 26 | -------------------------------------------------------------------------------- /client/src/views/login.less: -------------------------------------------------------------------------------- 1 | .login{ 2 | width: 100%; 3 | height: 100%; 4 | background-image: url('https://img.alicdn.com/tfs/TB1zsNhXTtYBeNjy1XdXXXXyVXa-2252-1500.png'); 5 | background-size: cover; 6 | background-position: center; 7 | position: relative; 8 | &-con{ 9 | position: absolute; 10 | right: 160px; 11 | top: 50%; 12 | transform: translateY(-60%); 13 | width: 300px; 14 | &-header{ 15 | font-size: 16px; 16 | font-weight: 300; 17 | text-align: center; 18 | padding: 30px 0; 19 | } 20 | .form-con{ 21 | padding: 10px 0 0; 22 | } 23 | .login-tip{ 24 | font-size: 10px; 25 | text-align: center; 26 | color: #c3c3c3; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /client/src/views/home/home.less: -------------------------------------------------------------------------------- 1 | .user-infor{ 2 | height: 135px; 3 | } 4 | .avator-img{ 5 | display: block; 6 | width: 80%; 7 | max-width: 100px; 8 | height: auto; 9 | } 10 | .card-user-infor-name{ 11 | font-size: 2em; 12 | color: #2d8cf0; 13 | } 14 | .card-title{ 15 | color: #abafbd; 16 | } 17 | .made-child-con-middle{ 18 | height: 100%; 19 | } 20 | .to-do-list-con{ 21 | height: 145px; 22 | overflow: auto; 23 | } 24 | .to-do-item{ 25 | padding: 2px; 26 | } 27 | .infor-card-con{ 28 | height: 100px; 29 | } 30 | .infor-card-icon-con{ 31 | height: 100%; 32 | color: white; 33 | border-radius: 3px 0 0 3px; 34 | } 35 | .map-con{ 36 | height: 305px; 37 | } 38 | .map-incon{ 39 | height: 100%; 40 | } 41 | .data-source-row{ 42 | height: 200px; 43 | } 44 | .line-chart-con{ 45 | height: 150px; 46 | } 47 | -------------------------------------------------------------------------------- /client/src/views/main-components/message-tip.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /server/test/app/service/token.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { 4 | app, 5 | assert 6 | } = require('egg-mock/bootstrap'); 7 | 8 | describe('get()', () => { 9 | 10 | it('should insert token to database', async () => { 11 | const ctx = app.mockContext(); 12 | const token = await ctx.service.token.genToken('zxcvhggghhbnm', '21338880-4971-4428-8258-9b63d4b2'); 13 | assert(token); 14 | }) 15 | 16 | it('should get exists token', async () => { 17 | // 创建 ctx 18 | const ctx = app.mockContext(); 19 | // 通过 ctx 访问到 service.token 20 | const token = await ctx.service.token.fetchOne('21338880-4971-4428-8258-9b63d4b2'); 21 | console.log(JSON.stringify(token)); 22 | assert(token.length==1); 23 | }); 24 | 25 | 26 | 27 | it('should delete token from database', async () => { 28 | const ctx = app.mockContext(); 29 | const token = await ctx.service.token.deleteToken('21338880-4971-4428-8258-9b63d4b2'); 30 | assert(token); 31 | }) 32 | 33 | }); -------------------------------------------------------------------------------- /server/app/model/token.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const moment = require('moment'); 3 | module.exports = app => { 4 | const { 5 | STRING, 6 | DATE, 7 | BIGINT, 8 | BOOLEAN 9 | } = app.Sequelize; 10 | const Token = app.model.define('sys_user_token', { 11 | user_id: { 12 | type: BIGINT(11), 13 | primaryKey: true 14 | }, 15 | token: { 16 | type: STRING(100), 17 | unique: true 18 | }, 19 | expire_time: DATE, 20 | update_time: { 21 | type: DATE, 22 | allowNull: false, 23 | defaultValue: new Date(), 24 | get() { 25 | return moment(this.getDataValue('updateTime')).format('YYYY-MM-DD HH:mm:ss'); 26 | }, 27 | }, 28 | valid: BOOLEAN, 29 | },{ 30 | 'tableName': 'sys_user_token', 31 | 'createdAt': false, 32 | 'updatedAt': false 33 | }); 34 | return Token; 35 | }; -------------------------------------------------------------------------------- /client/src/mock/user.js: -------------------------------------------------------------------------------- 1 | const Mock = require('mockjs'); 2 | 3 | const list = []; 4 | const count = 50; 5 | for (let i = 0; i < count; i++) { 6 | 7 | } 8 | 9 | let usersListData = Mock.mock({ 10 | 'data|25': [{ 11 | id: '@id', 12 | username: '@name', 13 | realname: '@last', 14 | phone: /^1[34578]\d{9}$/, 15 | 'age|11-99': 1, 16 | address: '@county(true)', 17 | isMale: '@boolean', 18 | email: '@email', 19 | createTime: '@datetime', 20 | updateTime: '@datetime', 21 | lastLoginIp: '@ip', 22 | 'loginCount|11-99': 1, 23 | avatar() { 24 | return Mock.Random.image('100x100', Mock.Random.color(), '#757575', 'png', this.realname.substr(0, 1)) 25 | }, 26 | }, ], 27 | }) 28 | 29 | function getUserList(config) { 30 | let result = { 31 | code: 200, 32 | data: null, 33 | msg: '获取成功', 34 | time: new Date() 35 | } 36 | 37 | result.data = usersListData.data; 38 | return result; 39 | } 40 | 41 | module.exports = { 42 | getUserList: getUserList() 43 | } -------------------------------------------------------------------------------- /client/src/views/own-space/own-space.less: -------------------------------------------------------------------------------- 1 | .own-space{ 2 | &-btn-box{ 3 | margin-bottom: 10px; 4 | button{ 5 | padding-left: 0; 6 | span{ 7 | color: #2D8CF0; 8 | transition: all .2s; 9 | } 10 | span:hover{ 11 | color: #0C25F1; 12 | transition: all .2s; 13 | } 14 | } 15 | } 16 | &-tra{ 17 | width:10px; 18 | height:10px; 19 | transform:rotate(45deg); 20 | position:absolute; 21 | top:50%; 22 | margin-top:-6px; 23 | left:-3px; 24 | box-shadow:0 0 2px 3px rgba(0,0,0,.1); 25 | background-color:white;z-index:100; 26 | } 27 | &-input-identifycode-con{ 28 | position:absolute; 29 | width:200px; 30 | height:100px; 31 | right:-220px; 32 | top:50%; 33 | margin-top:-50px; 34 | border-radius:4px; 35 | box-shadow:0 0 2px 3px rgba(0,0,0,.1); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /server/config/config.default.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = appInfo => { 4 | const config = exports = {}; 5 | 6 | // use for cookie sign key, should change to your own and keep security 7 | config.keys = appInfo.name + '_1521088945166_6453'; 8 | 9 | // add your config here 10 | config.middleware = ['responseTime']; 11 | 12 | config.security = { 13 | csrf: { 14 | enable: false, 15 | }, 16 | }; 17 | 18 | config.cors = { 19 | origin: 'http://127.0.0.1:8081', 20 | allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH', 21 | credentials: true, 22 | // allowHeaders :'Authorization,DNT,User-Agent,Keep-Alive,Content-Type,accept,origin,X-Requested-With' 23 | }; 24 | 25 | config.sequelize = { 26 | dialect: 'mysql', // support: mysql, mariadb, postgres, mssql 27 | dialectOptions: { 28 | charset: 'utf8mb4', 29 | }, 30 | database: 'egg_db', 31 | host: '', 32 | port: '3306', 33 | username: 'root', 34 | password: '', 35 | timezone: '+08:00', 36 | } 37 | return config; 38 | }; 39 | -------------------------------------------------------------------------------- /client/src/views/home/components/toDoListItem.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /server/app/model/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const Sequelize = require('sequelize'); 6 | const basename = path.basename(__filename); 7 | const env = process.env.NODE_ENV || 'development'; 8 | const config = require(__dirname + '/..\..\config\sequelize.js')[env]; 9 | const db = {}; 10 | 11 | if (config.use_env_variable) { 12 | const sequelize = new Sequelize(process.env[config.use_env_variable], config); 13 | } else { 14 | const sequelize = new Sequelize(config.database, config.username, config.password, config); 15 | } 16 | 17 | fs 18 | .readdirSync(__dirname) 19 | .filter(file => { 20 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); 21 | }) 22 | .forEach(file => { 23 | const model = sequelize.import(path.join(__dirname, file)); 24 | db[model.name] = model; 25 | }); 26 | 27 | Object.keys(db).forEach(modelName => { 28 | if (db[modelName].associate) { 29 | db[modelName].associate(db); 30 | } 31 | }); 32 | 33 | db.sequelize = sequelize; 34 | db.Sequelize = Sequelize; 35 | 36 | module.exports = db; 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 anytao 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 | -------------------------------------------------------------------------------- /client/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 iView 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 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 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | -------------------------------------------------------------------------------- /client/src/views/error-page/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | 20 | 35 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # server 2 | 3 | 4 | 5 | ## 快速入门 6 | 7 | 8 | 9 | 如需进一步了解,参见 [egg 文档][egg]。 10 | 11 | ### 本地开发 12 | 13 | ```bash 14 | $ npm i 15 | $ npm run dev 16 | $ open http://localhost:7001/ 17 | ``` 18 | 19 | ### 部署 20 | 21 | ```bash 22 | $ npm start 23 | $ npm stop 24 | ``` 25 | 26 | ### 单元测试 27 | 28 | - [egg-bin] 内置了 [mocha], [thunk-mocha], [power-assert], [istanbul] 等框架,让你可以专注于写单元测试,无需理会配套工具。 29 | - 断言库非常推荐使用 [power-assert]。 30 | - 具体参见 [egg 文档 - 单元测试](https://eggjs.org/zh-cn/core/unittest)。 31 | 32 | ### 内置指令 33 | 34 | - 使用 `npm run lint` 来做代码风格检查。 35 | - 使用 `npm test` 来执行单元测试。 36 | - 使用 `npm run autod` 来自动检测依赖更新,详细参见 [autod](https://www.npmjs.com/package/autod) 。 37 | 38 | 39 | [egg]: https://eggjs.org 40 | 41 | ### 目录说明 42 | 43 | - app/router.js 用于配置URL路由规则。 44 | - app/controller/** 45 | 1、 用于解析用户的输入,处理后返回相应的结果。 46 | 2、在 HTML 页面请求中,Controller 根据用户访问不同的 URL,渲染不同的模板得到 HTML 返回给用户。 47 | - app/service/** 用于编写业务逻辑层。 48 | - app/public/** 用于放置静态资源。 49 | - app/model/** 用于放置sequelize相关模型。 50 | 51 | ### 文档 52 | 53 | - Sequelize中文文档:https://itbilu.com/nodejs/npm/VkYIaRPz-.html 54 | - Egg文档:https://eggjs.org/zh-cn/intro/ -------------------------------------------------------------------------------- /client/src/mock/login.js: -------------------------------------------------------------------------------- 1 | 2 | const util = require('../libs/util'); 3 | const Mock = require('mockjs'); 4 | 5 | const userMap = { 6 | admin: { 7 | roles: ['admin'], 8 | token: 'admin', 9 | introduction: '我是超级管理员', 10 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 11 | name: 'Super Admin' 12 | }, 13 | editor: { 14 | roles: ['editor'], 15 | token: 'editor', 16 | introduction: '我是编辑', 17 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 18 | name: 'Normal Editor' 19 | } 20 | } 21 | 22 | module.exports = { 23 | loginByUsername: config => { 24 | const { 25 | username 26 | } = JSON.parse(config.body) 27 | return userMap[username] 28 | }, 29 | getUserInfo: config => { 30 | let result = { 31 | code: 200, 32 | data: null, 33 | msg: '获取成功', 34 | time: new Date() 35 | } 36 | const { 37 | token 38 | } = util.param2Obj(config.url) 39 | if (userMap[token]) { 40 | result.data = userMap[token]; 41 | return result; 42 | } else { 43 | return false 44 | } 45 | }, 46 | logout: () => 'success' 47 | } -------------------------------------------------------------------------------- /client/src/views/error-page/403.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | 20 | 35 | -------------------------------------------------------------------------------- /client/src/views/error-page/500.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 21 | 22 | 37 | -------------------------------------------------------------------------------- /client/src/views/example/table/data/search.js: -------------------------------------------------------------------------------- 1 | export const columns1 = [ 2 | { 3 | key: 'name', 4 | title: '姓名' 5 | }, 6 | { 7 | key: 'tel', 8 | title: '电话号码' 9 | } 10 | ]; 11 | 12 | export const searchTable1 = [ 13 | { 14 | name: 'Aresn', 15 | tel: '17712345678' 16 | }, 17 | { 18 | name: 'Lison', 19 | tel: '17787654321' 20 | }, 21 | { 22 | name: 'Lili', 23 | tel: '12212345678' 24 | }, 25 | { 26 | name: 'Lucy', 27 | tel: '13312345678' 28 | } 29 | ]; 30 | 31 | export const searchTable2 = [ 32 | { 33 | name: 'Aresn', 34 | tel: '17712345678' 35 | }, 36 | { 37 | name: 'Lison', 38 | tel: '17787654321' 39 | }, 40 | { 41 | name: 'Lili', 42 | tel: '12212345678' 43 | }, 44 | { 45 | name: 'Lucy', 46 | tel: '13312345678' 47 | } 48 | ]; 49 | 50 | export const searchTable3 = [ 51 | { 52 | name: 'Aresn', 53 | tel: '17712345678' 54 | }, 55 | { 56 | name: 'Lison', 57 | tel: '17787654321' 58 | }, 59 | { 60 | name: 'Lili', 61 | tel: '12212345678' 62 | }, 63 | { 64 | name: 'Lucy', 65 | tel: '13312345678' 66 | } 67 | ]; 68 | -------------------------------------------------------------------------------- /server/app/service/token.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: anytao 3 | * @Date: 2018-04-18 10:52:11 4 | * @Last Modified by: anytao 5 | * @Last Modified time: 2018-04-19 17:34:14 6 | */ 7 | 8 | const Service = require('egg').Service; 9 | const moment = require('moment'); 10 | 11 | class TokenService extends Service { 12 | constructor(ctx) { 13 | super(ctx); 14 | this.session = ctx.session; 15 | this.ResponseCode = ctx.response.ResponseCode; 16 | this.ServerResponse = ctx.response.ServerResponse; 17 | } 18 | 19 | async fetchOne(userId) { 20 | const token = await this.ctx.model.Token.findAll({ 21 | where:{ 22 | user_id:userId 23 | } 24 | }); 25 | return token; 26 | } 27 | 28 | async genToken(token, userId) { 29 | const expire_time = new Date(moment().add(1, 'days')); 30 | const result = await this.ctx.model.Token.create({ 31 | expire_time, 32 | token, 33 | user_id: userId, 34 | valid: true 35 | }); 36 | return result; 37 | } 38 | async deleteToken(userId) { 39 | const result = await this.ctx.model.Token.destroy({ 40 | where: { 41 | user_id: userId 42 | } 43 | }); 44 | return result; 45 | } 46 | } 47 | 48 | module.exports = TokenService; -------------------------------------------------------------------------------- /client/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var nodemon = require('gulp-nodemon'); 3 | var path = require('path'); 4 | var browserSync = require('browser-sync').create(); 5 | var ROOT = path.resolve(__dirname); 6 | var server = path.resolve(ROOT, './src/mock'); 7 | 8 | // browser-sync配置,配置里启动nodemon任务 9 | gulp.task('browser-sync', ['nodemon'], function() { 10 | browserSync.init(null, { 11 | proxy: "http://localhost:8080", 12 | port: 8081 13 | }); 14 | }); 15 | 16 | // browser-sync 监听文件 17 | gulp.task('mock', ['browser-sync'], function() { 18 | gulp.watch(['./src/mock/db.js', './src/mock/**'], ['bs-delay']); 19 | }); 20 | 21 | // 延时刷新 22 | gulp.task('bs-delay', function() { 23 | setTimeout(function() { 24 | browserSync.reload(); 25 | console.log('restart'); 26 | }, 1000); 27 | }); 28 | 29 | // 服务器重启 30 | gulp.task('nodemon', function(cb) { 31 | // 设个变量来防止重复重启 32 | var started = false; 33 | var stream = nodemon({ 34 | script: './src/mock/server.js', 35 | // 监听文件的后缀 36 | ext: "js", 37 | env: { 38 | 'NODE_ENV': 'development' 39 | }, 40 | // 监听的路径 41 | watch: [ 42 | server 43 | ] 44 | }); 45 | stream.on('start', function() { 46 | if (!started) { 47 | cb(); 48 | started = true; 49 | } 50 | }).on('crash', function() { 51 | console.error('Application has crashed!\n') 52 | stream.emit('restart', 10) 53 | }) 54 | }); 55 | -------------------------------------------------------------------------------- /client/src/views/example/table/components/dragableTable.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 51 | 52 | -------------------------------------------------------------------------------- /server/app/common/serverResponse.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | const { 5 | SUCCESS, 6 | ERROR 7 | } = require('./responseCode'); 8 | 9 | module.exports = class ServerResponse { 10 | constructor(status, msg, data) { 11 | this.status = status; 12 | this.msg = msg; 13 | this.data = data; 14 | } 15 | 16 | isSuccess() { 17 | return this.status === SUCCESS; 18 | } 19 | 20 | getStatus() { 21 | return this.status; 22 | } 23 | 24 | getData() { 25 | return this.data; 26 | } 27 | 28 | getMsg() { 29 | return this.msg; 30 | } 31 | 32 | static createBySuccess() { 33 | return new ServerResponse(SUCCESS); 34 | } 35 | 36 | static createBySuccessMsg(msg) { 37 | return new ServerResponse(SUCCESS, msg, null); 38 | } 39 | 40 | static createBySuccessData(data) { 41 | return new ServerResponse(SUCCESS, null, data); 42 | } 43 | 44 | static createBySuccessMsgAndData(msg, data) { 45 | return new ServerResponse(SUCCESS, msg, data); 46 | } 47 | 48 | static createByError() { 49 | return new ServerResponse(ERROR, 'error', null); 50 | } 51 | 52 | static createByErrorMsg(errorMsg) { 53 | return new ServerResponse(ERROR, errorMsg, null); 54 | } 55 | 56 | static createByErrorCodeMsg(errorCode, errorMsg) { 57 | return new ServerResponse(errorCode, errorMsg, null); 58 | } 59 | }; -------------------------------------------------------------------------------- /client/src/views/advanced-router/component/expandRow.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 37 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import iView from 'iview'; 3 | import {router} from './router/index'; 4 | import {appRouter} from './router/router'; 5 | import store from './store'; 6 | import App from './app.vue'; 7 | import '@/locale'; 8 | import 'iview/dist/styles/iview.css'; 9 | import VueI18n from 'vue-i18n'; 10 | import util from './libs/util'; 11 | 12 | import Http from './libs/http'; 13 | import Plugin from './libs/plugin/index'; 14 | import Toast from './libs/toast/'; 15 | // import './mock' 16 | 17 | Vue.use(VueI18n); 18 | Vue.use(iView); 19 | Vue.use(Toast); 20 | Vue.use(Http); 21 | Vue.use(Plugin); 22 | 23 | new Vue({ 24 | el: '#app', 25 | router: router, 26 | store: store, 27 | render: h => h(App), 28 | data: { 29 | currentPageName: '' 30 | }, 31 | mounted () { 32 | this.currentPageName = this.$route.name; 33 | // 显示打开的页面的列表 34 | this.$store.commit('setOpenedList'); 35 | this.$store.commit('initCachepage'); 36 | // 权限菜单过滤相关 37 | this.$store.commit('updateMenulist'); 38 | // iview-admin检查更新 39 | util.checkUpdate(this); 40 | }, 41 | created () { 42 | let tagsList = []; 43 | appRouter.map((item) => { 44 | if (item.children.length <= 1) { 45 | tagsList.push(item.children[0]); 46 | } else { 47 | tagsList.push(...item.children); 48 | } 49 | }); 50 | this.$store.commit('setTagsList', tagsList); 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "dependencies": { 7 | "egg": "^2.2.1", 8 | "egg-cors": "^2.0.0", 9 | "egg-scripts": "^2.5.0", 10 | "egg-security": "^2.1.0", 11 | "egg-sequelize": "^3.1.2", 12 | "egg-validate": "^1.0.0", 13 | "jsonwebtoken": "^8.2.1", 14 | "lodash": "^4.17.5", 15 | "md5": "^2.2.1", 16 | "moment": "^2.21.0", 17 | "mysql": "^2.15.0", 18 | "mysql2": "^1.5.2", 19 | "sequelize-cli": "^4.0.0" 20 | }, 21 | "devDependencies": { 22 | "autod": "^3.0.1", 23 | "autod-egg": "^1.0.0", 24 | "egg-bin": "^4.3.5", 25 | "egg-ci": "^1.8.0", 26 | "egg-mock": "^3.14.0", 27 | "eslint": "^4.11.0", 28 | "eslint-config-egg": "^6.0.0", 29 | "webstorm-disable-index": "^1.2.0" 30 | }, 31 | "engines": { 32 | "node": ">=8.9.0" 33 | }, 34 | "scripts": { 35 | "start": "egg-scripts start --daemon --title=egg-server-server", 36 | "stop": "egg-scripts stop --title=egg-server-server", 37 | "dev": "egg-bin dev", 38 | "debug": "egg-bin debug", 39 | "test": "npm run lint -- --fix && npm run test-local", 40 | "test-local": "egg-bin test", 41 | "cov": "egg-bin cov", 42 | "lint": "eslint .", 43 | "ci": "npm run lint && npm run cov", 44 | "autod": "autod" 45 | }, 46 | "ci": { 47 | "version": "8" 48 | }, 49 | "repository": { 50 | "type": "git", 51 | "url": "" 52 | }, 53 | "author": "anytao", 54 | "license": "MIT" 55 | } 56 | -------------------------------------------------------------------------------- /client/src/views/error-page/404.less: -------------------------------------------------------------------------------- 1 | @keyframes error404animation { 2 | 0% { 3 | transform: rotateZ(0deg); 4 | } 5 | 20% { 6 | transform: rotateZ(-60deg); 7 | } 8 | 40% { 9 | transform: rotateZ(-10deg); 10 | } 11 | 60% { 12 | transform: rotateZ(50deg); 13 | } 14 | 80% { 15 | transform: rotateZ(-20deg); 16 | } 17 | 100% { 18 | transform: rotateZ(0deg); 19 | } 20 | } 21 | .error404{ 22 | &-body-con{ 23 | width: 700px; 24 | height: 500px; 25 | position: absolute; 26 | left: 50%; 27 | top: 50%; 28 | transform: translate(-50%,-50%); 29 | &-title{ 30 | text-align: center; 31 | font-size: 240px; 32 | font-weight: 700; 33 | color: #2d8cf0; 34 | height: 260px; 35 | line-height: 260px; 36 | margin-top: 40px; 37 | span{ 38 | display: inline-block; 39 | color: #19be6b; 40 | font-size: 230px; 41 | animation: error404animation 3s ease 0s infinite alternate; 42 | } 43 | } 44 | &-message{ 45 | display: block; 46 | text-align: center; 47 | font-size: 30px; 48 | font-weight: 500; 49 | letter-spacing: 12px; 50 | color: #dddde2; 51 | } 52 | } 53 | &-btn-con{ 54 | text-align: center; 55 | padding: 20px 0; 56 | margin-bottom: 40px; 57 | } 58 | } -------------------------------------------------------------------------------- /client/src/views/main-components/lockscreen/components/locking-page.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 45 | -------------------------------------------------------------------------------- /client/src/styles/common.less: -------------------------------------------------------------------------------- 1 | .margin-top-8 { 2 | margin-top: 8px; 3 | } 4 | 5 | .margin-top-10 { 6 | margin-top: 10px; 7 | } 8 | 9 | .margin-top-20 { 10 | margin-top: 20px; 11 | } 12 | 13 | .margin-left-10 { 14 | margin-left: 10px; 15 | } 16 | 17 | .margin-bottom-10 { 18 | margin-bottom: 10px; 19 | } 20 | 21 | .margin-bottom-100 { 22 | margin-bottom: 100px; 23 | } 24 | 25 | .margin-right-10 { 26 | margin-right: 10px; 27 | } 28 | 29 | .padding-left-6 { 30 | padding-left: 6px; 31 | } 32 | 33 | .padding-left-8 { 34 | padding-left: 5px; 35 | } 36 | 37 | .padding-left-10 { 38 | padding-left: 10px; 39 | } 40 | 41 | .padding-left-20 { 42 | padding-left: 20px; 43 | } 44 | 45 | .padding-top-10{ 46 | padding-top:10px; 47 | } 48 | 49 | .padding-right-10{ 50 | padding-right:10px; 51 | } 52 | 53 | .height-100 { 54 | height: 100%; 55 | } 56 | 57 | .height-120px { 58 | height: 100px; 59 | } 60 | 61 | .height-200px { 62 | height: 200px; 63 | } 64 | 65 | .height-492px { 66 | height: 492px; 67 | } 68 | 69 | .height-460px { 70 | height: 460px; 71 | } 72 | 73 | .line-gray { 74 | height: 0; 75 | border-bottom: 2px solid #dcdcdc; 76 | } 77 | 78 | .notwrap { 79 | word-break: keep-all; 80 | white-space: nowrap; 81 | overflow: hidden; 82 | text-overflow: ellipsis; 83 | } 84 | 85 | .padding-left-5 { 86 | padding-left: 10px; 87 | } 88 | 89 | [v-cloak] { 90 | display: none; 91 | } 92 | 93 | .background-color-white { 94 | background-color: white; 95 | } 96 | 97 | .text-align-right{ 98 | text-align: right; 99 | } -------------------------------------------------------------------------------- /client/src/views/example/table/components/table.less: -------------------------------------------------------------------------------- 1 | .dragging-tip-enter-active{ 2 | opacity: 1; 3 | transition: opacity .3s; 4 | } 5 | .dragging-tip-enter, .dragging-tip-leave-to{ 6 | opacity: 0; 7 | transition: opacity .3s 8 | } 9 | .dragging-tip-con{ 10 | display: block; 11 | text-align: center; 12 | width: 100%; 13 | height: 50px; 14 | } 15 | .dragging-tip-con span{ 16 | font-size: 18px; 17 | } 18 | .record-tip-con{ 19 | display: block; 20 | width: 100%; 21 | height: 292px; 22 | overflow: auto; 23 | } 24 | .record-item{ 25 | box-sizing: content-box; 26 | display: block; 27 | overflow: hidden; 28 | height: 24px; 29 | line-height: 24px; 30 | padding: 8px 10px; 31 | border-bottom: 1px dashed gainsboro; 32 | } 33 | .record-tip-con span{ 34 | font-size: 14px; 35 | } 36 | .edittable-test-con{ 37 | height: 160px; 38 | } 39 | .edittable-table-height-con{ 40 | height: 190px; 41 | } 42 | .edittable-con-1{ 43 | box-sizing: content-box; 44 | padding: 15px 0 0; 45 | height: 196px; 46 | } 47 | .edittable-table-get-currentdata-con{ 48 | height: 190px !important; 49 | } 50 | .exportable-table-download-con1{ 51 | padding: 16px 0 16px 20px; 52 | border-bottom: 1px dashed #c3c3c3; 53 | margin-bottom: 16px; 54 | } 55 | .exportable-table-download-con2{ 56 | padding-left: 20px; 57 | } 58 | .show-image{ 59 | padding: 20px 0px; 60 | } 61 | .show-image img{ 62 | display: block; 63 | width: 100%; 64 | height: auto; 65 | } 66 | .searchable-table{ 67 | &-con1{ 68 | height: 230px !important; 69 | } 70 | } -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | > 前端 2 | 3 | # 安装 4 | 5 | 项目地址: (git clone) 6 | 7 | ``` 8 | git clone https://github.com/twang281314/egg-vue-admin 9 | ``` 10 | 11 | 通过npm安装本地服务第三方依赖模块(需要已安装Node.js) 12 | 13 | ``` 14 | npm install 15 | 16 | ``` 17 | 18 | 启动服务 19 | 20 | ``` 21 | cd client 22 | 23 | npm run dev 24 | 25 | ``` 26 | 27 | 发布代码 28 | 29 | ``` 30 | npm run build 31 | ``` 32 | 33 | # 目录结构 34 | 35 | ``` 36 | ├── build // 项目的 Webpack 配置文件 37 | ├── config // 项目配置目录 38 | ├── server // 项目开发的请求数据 39 | ├── src // 生产目录 40 | │ ├── assets // 一些资源文件 41 | │ ├── common // 通用文件、如工具类、状态码 42 | │ ├── components // 各种组件 43 | │ ├── views // 各种页面 44 | │ ├── plugins // 各种插件 45 | │ ├── router // 路由配置及map 46 | │ ├── store // Vuex 状态管理器 47 | │ ├── App.vue // 根组件 48 | │ ├── favicon.ico // ico小图标 49 | │ ├── index.html // 项目入口文件 50 | │ ├── main.js // Webpack 编译入口文件,入口js 51 | ├── static // 静态资源,一般把不需要处理的文件可以放这里 52 | ├── .babelrc // babelrc配置文件 53 | ├── .editorconfig // 代码风格文件,前提是要你的编辑器支持 54 | ├── .gitignore // 用于Git配置不需要加入版本管理的文件 55 | ├── .postcssrc.js // autoprefixer的配置文件 56 | ├── package.json // 项目配置文件 57 | 58 | ``` 59 | 60 | # 使用的技术和库 61 | 62 | - [Vue](https://cn.vuejs.org/) 当下最流行的前端JavaScript框架 63 | - [VueRouter](https://router.vuejs.org/zh-cn/) 基于Vue的路由插件 64 | - [Vuex](https://vuex.vuejs.org/zh-cn/) 管理Vue中多组件共享状态的插件,类似react的redux 65 | - [Axios](https://github.com/mzabriskie/axios) 当前最流行的一个http库 66 | - [iView](https://www.iviewui.com/) 基于Vue的一套UI组件库 67 | - [Mock.js](http://mockjs.com/) 生成随机数据,拦截 Ajax 请求 让前端攻城师独立于后端进行开发 68 | -------------------------------------------------------------------------------- /client/src/views/home/components/inforCard.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 30 | 31 | 60 | 61 | -------------------------------------------------------------------------------- /client/src/libs/axios.js: -------------------------------------------------------------------------------- 1 | 2 | import Axios from 'axios'; 3 | import Qs from 'qs'; 4 | import {Router} from '../router'; 5 | const config = { 6 | baseURL: 'api', 7 | timeout: 2000, //超过两秒的请求请用微服务来处理 8 | withCredentials: true, //是否允许跨域 9 | headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', 'X-Requested-With': 'XMLHttpRequest'}, 10 | transformRequest: [function (data) { 11 | // 这里可以在发送请求之前对请求数据做处理,比如form-data格式化等,这里可以使用开头引入的Qs(这个模块在安装axios的时候就已经安装了,不需要另外安装) 12 | data = Qs.stringify(data); 13 | return data; 14 | }], 15 | //返回数据类型 16 | responseType: 'json' 17 | }; 18 | //那年乱世如麻,愿你们来世拥有锦绣年华.此生无悔入华夏,来世还在种花家 19 | const AsInst = Axios.create(config); 20 | //请求拦截器 21 | AsInst.interceptors.request.use((config) => { 22 | //若是有做鉴权token , 就给头部带上token 23 | if (window.localStorage.getItem('loginToken')) { 24 | config.headers.Authorization = `${window.localStorage.getItem('loginToken')}`; 25 | } 26 | return config; 27 | }, (err) => { 28 | return Promise.reject(err); 29 | }); 30 | //响应拦截器 31 | AsInst.interceptors.response.use(response => { 32 | //检查数据是否返回NULL 33 | if (response.data === null) { 34 | return Promise.reject(response); 35 | } 36 | //检查是否有权限 37 | if (response.data.code === 2000 && response.data.status === false) { 38 | return Promise.reject(response); 39 | } 40 | //检查登陆信息是否还存在 41 | if (response.data.code === 2001 && response.data.status === false) { 42 | window.localStorage.removeItem('userInfo'); 43 | window.localStorage.removeItem('loginToken'); 44 | Router.push({ 45 | path: '/passport/login' 46 | }); 47 | return Promise.reject(response); 48 | } 49 | return response; 50 | }, (error) => { 51 | // 下面是接口回调的status ,因为我做了一些错误页面,所以都会指向对应的报错页面 52 | if (error.response.status === 404) { 53 | Router.push({ 54 | path: '/error/404' 55 | }); 56 | } 57 | //请求错误时做些事 58 | return Promise.reject(error); 59 | }); 60 | export default AsInst; 61 | -------------------------------------------------------------------------------- /client/build/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | const merge = require('webpack-merge'); 6 | const webpackBaseConfig = require('./webpack.base.config.js'); 7 | const fs = require('fs'); 8 | const package = require('../package.json'); 9 | 10 | fs.open('./build/env.js', 'w', function (err, fd) { 11 | const buf = 'export default "development";'; 12 | fs.write(fd, buf, 0, buf.length, 0, function (err, written, buffer) {}); 13 | }); 14 | 15 | module.exports = merge(webpackBaseConfig, { 16 | devtool: '#source-map', 17 | devServer: { 18 | historyApiFallback: true, 19 | hot: true, 20 | inline: true, 21 | progress: true, 22 | port: 8080, 23 | host: '0.0.0.0', 24 | proxy: { 25 | '/api': { 26 | target: 'http://localhost:7001/', 27 | changeOrigin: true, 28 | secure: false 29 | } 30 | } 31 | }, 32 | output: { 33 | publicPath: '/dist/', 34 | filename: '[name].js', 35 | chunkFilename: '[name].chunk.js' 36 | }, 37 | plugins: [ 38 | new ExtractTextPlugin({ 39 | filename: '[name].css', 40 | allChunks: true 41 | }), 42 | new webpack.optimize.CommonsChunkPlugin({ 43 | name: ['vender-exten', 'vender-base'], 44 | minChunks: Infinity 45 | }), 46 | new HtmlWebpackPlugin({ 47 | title: 'iView admin v' + package.version, 48 | filename: '../index.html', 49 | inject: false 50 | }), 51 | new CopyWebpackPlugin([{ 52 | from: 'src/views/main-components/theme-switch/theme' 53 | }], { 54 | ignore: [ 55 | 'text-editor.vue' 56 | ] 57 | }) 58 | ] 59 | }); -------------------------------------------------------------------------------- /client/src/views/error-page/500.less: -------------------------------------------------------------------------------- 1 | @keyframes error500animation { 2 | 0% { 3 | transform: rotateZ(0deg); 4 | } 5 | 20% { 6 | transform: rotateZ(-10deg); 7 | } 8 | 40% { 9 | transform: rotateZ(5deg); 10 | } 11 | 60% { 12 | transform: rotateZ(-5deg); 13 | } 14 | 80% { 15 | transform: rotateZ(10deg); 16 | } 17 | 100% { 18 | transform: rotateZ(0deg); 19 | } 20 | } 21 | .error500{ 22 | &-body-con{ 23 | width: 700px; 24 | height: 500px; 25 | position: absolute; 26 | left: 50%; 27 | top: 50%; 28 | transform: translate(-50%,-50%); 29 | &-title{ 30 | text-align: center; 31 | font-size: 240px; 32 | font-weight: 700; 33 | color: #2d8cf0; 34 | height: 260px; 35 | line-height: 260px; 36 | margin-top: 40px; 37 | .error500-0-span{ 38 | display: inline-block; 39 | position: relative; 40 | width: 170px; 41 | height: 170px; 42 | border-radius: 50%; 43 | border: 20px solid #ed3f14; 44 | color: #ed3f14; 45 | margin-right: 10px; 46 | i{ 47 | display: inline-block; 48 | font-size: 120px; 49 | position: absolute; 50 | bottom: -10px; 51 | left: 10px; 52 | transform-origin: center bottom; 53 | animation: error500animation 3s ease 0s infinite alternate; 54 | } 55 | } 56 | } 57 | &-message{ 58 | display: block; 59 | text-align: center; 60 | font-size: 30px; 61 | font-weight: 500; 62 | letter-spacing: 4px; 63 | color: #dddde2; 64 | } 65 | } 66 | &-btn-con{ 67 | text-align: center; 68 | padding: 20px 0; 69 | margin-bottom: 40px; 70 | } 71 | } -------------------------------------------------------------------------------- /client/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import iView from 'iview'; 3 | import Util from '../libs/util'; 4 | import VueRouter from 'vue-router'; 5 | import Cookies from 'js-cookie'; 6 | import {routers, otherRouter, appRouter} from './router'; 7 | 8 | Vue.use(VueRouter); 9 | 10 | // 路由配置 11 | const RouterConfig = { 12 | //mode: 'history', 13 | routes: routers 14 | }; 15 | 16 | export const router = new VueRouter(RouterConfig); 17 | 18 | router.beforeEach((to, from, next) => { 19 | iView.LoadingBar.start(); 20 | Util.title(to.meta.title); 21 | if (Cookies.get('locking') === '1' && to.name !== 'locking') { // 判断当前是否是锁定状态 22 | next({ 23 | replace: true, 24 | name: 'locking' 25 | }); 26 | } else if (Cookies.get('locking') === '0' && to.name === 'locking') { 27 | next(false); 28 | } else { 29 | if (!Cookies.get('user') && to.name !== 'login') { // 判断是否已经登录且前往的页面不是登录页 30 | next({ 31 | name: 'login' 32 | }); 33 | } else if (Cookies.get('user') && to.name === 'login') { // 判断是否已经登录且前往的是登录页 34 | Util.title(); 35 | next({ 36 | name: 'home_index' 37 | }); 38 | } else { 39 | const curRouterObj = Util.getRouterObjByName([otherRouter, ...appRouter], to.name); 40 | if (curRouterObj && curRouterObj.access !== undefined) { // 需要判断权限的路由 41 | if (curRouterObj.access === parseInt(Cookies.get('access'))) { 42 | Util.toDefaultPage([otherRouter, ...appRouter], to.name, router, next); // 如果在地址栏输入的是一级菜单则默认打开其第一个二级菜单的页面 43 | } else { 44 | next({ 45 | replace: true, 46 | name: 'error-403' 47 | }); 48 | } 49 | } else { // 没有配置权限的路由, 直接通过 50 | Util.toDefaultPage([...routers], to.name, router, next); 51 | } 52 | } 53 | } 54 | }); 55 | 56 | router.afterEach((to) => { 57 | Util.openNewPage(router.app, to.name, to.params, to.query); 58 | iView.LoadingBar.finish(); 59 | window.scrollTo(0, 0); 60 | }); 61 | -------------------------------------------------------------------------------- /client/src/locale/locale.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'zh-CN': { 3 | home: '首页', 4 | switchLangTitle: '切换语言', 5 | international: '多语言切换', 6 | iviewComponentTitle: 'iview组件多语言切换', 7 | tip: '注:iview-admin只是为了示范如何实现多语言切换,所以只对当前页做了翻译。', 8 | intro: 'iview目前支持15种语言,只要你看得到的iview组件出现iview内置文字的地方都会根据你设置的语言类型自动切换对应的语言。', 9 | placeholderText: '请输入文字...', 10 | placeholderDate: '选择日期', 11 | name: '姓名', 12 | company: '公司', 13 | btnText: '点击查看模态框', 14 | modalText: '在这里你可以看到iview模态框默认的确定和取消按钮会切换语言', 15 | poptip: '国际化的气泡提示', 16 | showPoptipText: '点击显示气泡提示' 17 | }, 18 | 'zh-TW': { 19 | home: '首頁', 20 | switchLangTitle: '切換語言', 21 | international: '多語言切換', 22 | iviewComponentTitle: 'iview組件多語言切換', 23 | tip: '注:iview-admin只是為了示範如何實現多語言切換,所以只對當前頁做了翻譯。', 24 | intro: 'iview目前支持15種語言,只要你看得到的iview組件出現iview內置文字的地方都會根據你設置的語言類型自動切換對應的語言。', 25 | placeholderText: '請輸入文字...', 26 | placeholderDate: '選擇日期', 27 | name: '姓名', 28 | company: '公司', 29 | btnText: '點擊查看模態框', 30 | modalText: '在這裡你可以看到iview模態框默認的確定和取消按鈕會切換語言', 31 | poptip: '國際化的氣泡提示', 32 | showPoptipText: '點擊顯示氣泡提示' 33 | }, 34 | 'en-US': { 35 | home: 'home', 36 | switchLangTitle: 'Switch Lang', 37 | international: 'Switch Lang', 38 | tip: 'Note: iview-admin just to demonstrate how to achieve multi-language switching, so only the current page to do the translation.', 39 | iviewComponentTitle: 'The effect on the iview', 40 | intro: 'iview currently supports 15 languages, as long as you see the iview component where the text will be based on your language type automatically set the corresponding language.', 41 | placeholderText: 'please enter text...', 42 | placeholderDate: 'Select Date', 43 | name: 'name', 44 | company: 'company', 45 | btnText: 'Click to show modal', 46 | modalText: 'Here you can see the iview modal box by default to the OK and Cancel buttons that will switch the language', 47 | poptip: 'international poptip', 48 | showPoptipText: 'Click to show poptip' 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /client/src/views/home/map-data/get-style-json.js: -------------------------------------------------------------------------------- 1 | export default [{ 2 | 'featureType': 'water', 3 | 'elementType': 'all', 4 | 'stylers': { 5 | 'visibility': 'off' 6 | } 7 | }, { 8 | 'featureType': 'land', 9 | 'elementType': 'all', 10 | 'stylers': { 11 | 'color': 'red' 12 | } 13 | }, { 14 | 'featureType': 'railway', 15 | 'elementType': 'all', 16 | 'stylers': { 17 | 'visibility': 'off' 18 | } 19 | }, { 20 | 'featureType': 'highway', 21 | 'elementType': 'all', 22 | 'stylers': { 23 | 'color': '#fdfdfd' 24 | } 25 | }, { 26 | 'featureType': 'highway', 27 | 'elementType': 'labels', 28 | 'stylers': { 29 | 'visibility': 'off' 30 | } 31 | }, { 32 | 'featureType': 'arterial', 33 | 'elementType': 'geometry', 34 | 'stylers': { 35 | 'color': '#00ff00' 36 | } 37 | }, { 38 | 'featureType': 'arterial', 39 | 'elementType': 'geometry.fill', 40 | 'stylers': { 41 | 'color': '#fefefe' 42 | } 43 | }, { 44 | 'featureType': 'poi', 45 | 'elementType': 'all', 46 | 'stylers': { 47 | 'visibility': 'off' 48 | } 49 | }, { 50 | 'featureType': 'green', 51 | 'elementType': 'all', 52 | 'stylers': { 53 | 'visibility': 'off' 54 | } 55 | }, { 56 | 'featureType': 'subway', 57 | 'elementType': 'all', 58 | 'stylers': { 59 | 'visibility': 'off' 60 | } 61 | }, { 62 | 'featureType': 'manmade', 63 | 'elementType': 'all', 64 | 'stylers': { 65 | 'color': 'red' 66 | } 67 | }, { 68 | 'featureType': 'local', 69 | 'elementType': 'all', 70 | 'stylers': { 71 | 'color': 'red' 72 | } 73 | }, { 74 | 'featureType': 'arterial', 75 | 'elementType': 'labels', 76 | 'stylers': { 77 | 'visibility': 'off' 78 | } 79 | }, { 80 | 'featureType': 'boundary', 81 | 'elementType': 'all', 82 | 'stylers': { 83 | 'color': '#000' 84 | } 85 | }, { 86 | 'featureType': 'building', 87 | 'elementType': 'all', 88 | 'stylers': { 89 | 'color': '#d1d1d1' 90 | } 91 | }, { 92 | 'featureType': 'label', 93 | 'elementType': 'labels.text.fill', 94 | 'stylers': { 95 | 'color': '#999999' 96 | } 97 | }]; 98 | -------------------------------------------------------------------------------- /server/app/model/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var moment = require('moment'); 3 | module.exports = app => { 4 | const { 5 | INTEGER, 6 | STRING, 7 | DATE, 8 | UUID, 9 | UUIDV4 10 | } = app.Sequelize; 11 | 12 | const UserModel = app.model.define('user', { 13 | id: { 14 | type: UUID, 15 | defaultValue: UUIDV4, 16 | allowNull: false, 17 | primaryKey: true, 18 | // autoIncrement: true, 19 | }, 20 | username: { 21 | type: STRING(50), 22 | allowNull: false, 23 | unique: true, 24 | }, 25 | realname: { 26 | type: STRING(50), 27 | allowNull: false, 28 | unique: false, 29 | }, 30 | password: { 31 | type: STRING(50), 32 | allowNull: false, 33 | }, 34 | email: { 35 | type: STRING(50), 36 | allowNull: true, 37 | }, 38 | mobile: { 39 | type: STRING(20), 40 | allowNull: true, 41 | }, 42 | createTime: { 43 | type: DATE, 44 | allowNull: false, 45 | defaultValue: new Date(), 46 | get() { 47 | return moment(this.getDataValue('createTime')).format('YYYY-MM-DD HH:mm:ss'); 48 | } 49 | }, 50 | updateTime: { 51 | type: DATE, 52 | allowNull: false, 53 | defaultValue: new Date(), 54 | get() { 55 | return moment(this.getDataValue('updateTime')).format('YYYY-MM-DD HH:mm:ss'); 56 | } 57 | }, 58 | status: { 59 | type: INTEGER, 60 | allowNull: false, 61 | default: 0 62 | }, 63 | desc: { 64 | type: STRING(250), 65 | allowNull: true 66 | }, 67 | }, { 68 | timestamps: false, 69 | tableName: 'user', 70 | }); 71 | 72 | UserModel.beforeBulkUpdate(user => { 73 | user.attributes.updateTime = new Date(); 74 | return user; 75 | }); 76 | 77 | // UserModel.beforeCreate((user) => { 78 | // console.log(user) 79 | // return user 80 | // }) 81 | 82 | return UserModel; 83 | }; -------------------------------------------------------------------------------- /client/src/views/home/components/dataSourcePie.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 59 | -------------------------------------------------------------------------------- /client/src/views/advanced-router/component/shopping-info.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 19 | 20 | 75 | -------------------------------------------------------------------------------- /server/app/controller/api/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Controller = require('egg').Controller; 4 | 5 | const Parameter = require('parameter'); 6 | const jwt = require('jsonwebtoken'); 7 | const Check = new Parameter(); 8 | 9 | class UserController extends Controller { 10 | 11 | constructor(ctx) { 12 | super(ctx); 13 | this.session = ctx.session; 14 | this.ResponseCode = ctx.response.ResponseCode; 15 | this.ServerResponse = ctx.response.ServerResponse; 16 | } 17 | 18 | // 登录 19 | async login() { 20 | const token = jwt.sign({ 21 | foo: 'bar' 22 | }, 'shhhhh'); 23 | const ctx = this.ctx; 24 | const createRule = { 25 | username: { 26 | type: 'string', 27 | required: true, 28 | allowEmpty: false, 29 | max: 20, 30 | }, 31 | password: { 32 | type: 'string', 33 | required: true, 34 | allowEmpty: false, 35 | max: 20, 36 | min: 6, 37 | }, 38 | }; 39 | let errors = []; 40 | try { 41 | errors = ctx.validate(createRule, this.ctx.request.body); 42 | } catch (error) { 43 | errors = error.errors; 44 | } 45 | 46 | if (errors) { 47 | this.ctx.body = this.ServerResponse.createByErrorMsg(errors[0].field + ' ' + errors[0].code + ' ' + errors[0].message); 48 | return; 49 | } 50 | 51 | const { 52 | username, 53 | password, 54 | } = this.ctx.request.body; 55 | 56 | const response = await this.ctx.service.user.login(username, password); 57 | 58 | if (response.isSuccess()) { 59 | this.session.currentUser = response.getData(); 60 | } 61 | 62 | this.ctx.body = response; 63 | } 64 | 65 | async add() { 66 | const user = this.ctx.request.body; 67 | const respponse = await this.ctx.service.user.add(user); 68 | this.ctx.body = respponse; 69 | } 70 | 71 | async list() { 72 | const users = await this.ctx.service.user.list(); 73 | this.ctx.body = this.ServerResponse.createBySuccessMsgAndData('获取成功', users); 74 | } 75 | 76 | } 77 | 78 | module.exports = UserController; -------------------------------------------------------------------------------- /client/src/views/home/components/userFlow.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 82 | -------------------------------------------------------------------------------- /server/app/service/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Service = require('egg').Service; 4 | const md5 = require('md5'); 5 | const _ = require('lodash'); 6 | 7 | class UserService extends Service { 8 | constructor(ctx) { 9 | super(ctx); 10 | this.session = ctx.session; 11 | this.ResponseCode = ctx.response.ResponseCode; 12 | this.ServerResponse = ctx.response.ServerResponse; 13 | } 14 | 15 | /** 16 | * @feature 校验 username email 17 | * @param value {String} 18 | * @param type {String} 19 | * @return ServerResponse.msg 20 | */ 21 | async checkValid(type, value) { 22 | if (type.trim()) { 23 | 24 | } 25 | return this.ServerResponse.createByErrorMsg('参数错误'); 26 | } 27 | 28 | async login(username, password) { 29 | 30 | //检验参数 31 | const validResponse = await this.checkValid('username', username); 32 | if (validResponse.isSuccess()) return validResponse; 33 | 34 | // 检查密码是否正确 35 | const user = await this.ctx.model.User.findOne({ 36 | attributes: ['id', 'username', 'email', 'mobile'], 37 | where: { 38 | username, 39 | password: md5(password), 40 | }, 41 | }); 42 | 43 | if (!user) return this.ServerResponse.createByErrorMsg('密码错误'); 44 | 45 | const userInfo = user.toJSON() 46 | let redirectTo=''; 47 | 48 | return this.ServerResponse.createBySuccessMsgAndData('登录成功', { ...userInfo, 49 | redirectTo 50 | }); 51 | } 52 | 53 | /** 54 | * 新增用户 55 | */ 56 | async add(user) { 57 | user.password = md5(user.password); 58 | try { 59 | user = await this.ctx.model.User.create(user, { 60 | attributes: { 61 | exclude: ['password'] 62 | }, 63 | }); 64 | if (!user) return this.ServerResponse.createByErrorMsg('注册失败'); 65 | 66 | user = user.toJSON(); 67 | _.unset(user, 'password'); 68 | 69 | return this.ServerResponse.createBySuccessMsgAndData('注册成功', user); 70 | } catch (e) { 71 | console.log(e); 72 | return this.ServerResponse.createByErrorMsg('注册失败'); 73 | } 74 | } 75 | 76 | async list(){ 77 | const users = await this.ctx.model.User.findAll(); 78 | return users; 79 | } 80 | } 81 | 82 | module.exports = UserService; -------------------------------------------------------------------------------- /client/src/libs/toast/index.js: -------------------------------------------------------------------------------- 1 | 2 | import './toast.css'; 3 | class Toast { 4 | } 5 | Toast.install = function (Vue, options) { 6 | let opt = { 7 | defaultType: 'bottom', 8 | duration: '2500' 9 | }; 10 | for (let property in options) { 11 | opt[property] = options[property]; 12 | } 13 | Vue.prototype.$toast = function (tips, type) { 14 | let curType; 15 | if (type) { 16 | curType = type; 17 | } else { 18 | curType = opt.defaultType; 19 | } 20 | if (document.querySelector('.lx-toast')) { 21 | // 如果toast还在,则不再执行 22 | return; 23 | } 24 | const ToastTpl = Vue.extend({ 25 | template: '
' + tips + '
' 26 | }); 27 | const tpl = new ToastTpl().$mount().$el; 28 | document.body.appendChild(tpl); 29 | setTimeout(function () { 30 | document.body.removeChild(tpl); 31 | }, opt.duration); 32 | }; 33 | ['bottom', 'center', 'top'].forEach(function (type) { 34 | Vue.prototype.$toast[type] = function (tips) { 35 | return Vue.prototype.$toast(tips, type); 36 | }; 37 | }); 38 | Vue.prototype.$loading = function (tips, type = '') { 39 | let load = document.querySelector('.lx-load-mark'); 40 | if (type === 'close') { 41 | load && document.body.removeChild(load); 42 | } else { 43 | if (load) { 44 | // 如果loading还在,则不再执行 45 | return; 46 | } 47 | const LoadTpl = Vue.extend({ 48 | template: '
' + tips + '
' 49 | }); 50 | const tpl = new LoadTpl().$mount().$el; 51 | document.body.appendChild(tpl); 52 | } 53 | }; 54 | ['open', 'close'].forEach(function (type) { 55 | Vue.prototype.$loading[type] = function (tips) { 56 | return Vue.prototype.$loading(tips, type); 57 | }; 58 | }); 59 | }; 60 | export default Toast; 61 | -------------------------------------------------------------------------------- /client/src/views/main-components/shrinkable-menu/shrinkable-menu.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 24 | 25 | 84 | -------------------------------------------------------------------------------- /client/src/views/main-components/shrinkable-menu/components/sidebarMenuShrink.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 58 | -------------------------------------------------------------------------------- /client/src/views/main-components/lockscreen/lockscreen.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 66 | 67 | -------------------------------------------------------------------------------- /client/src/views/error-page/403.less: -------------------------------------------------------------------------------- 1 | @keyframes error403animation { 2 | 0% { 3 | transform: rotateZ(0deg); 4 | } 5 | 40% { 6 | transform: rotateZ(-20deg); 7 | } 8 | 45% { 9 | transform: rotateZ(-15deg); 10 | } 11 | 50% { 12 | transform: rotateZ(-20deg); 13 | } 14 | 55% { 15 | transform: rotateZ(-15deg); 16 | } 17 | 60% { 18 | transform: rotateZ(-20deg); 19 | } 20 | 100% { 21 | transform: rotateZ(0deg); 22 | } 23 | } 24 | .error403{ 25 | &-body-con{ 26 | width: 700px; 27 | height: 500px; 28 | position: absolute; 29 | left: 50%; 30 | top: 50%; 31 | transform: translate(-50%,-50%); 32 | &-title{ 33 | text-align: center; 34 | font-size: 240px; 35 | font-weight: 700; 36 | color: #2d8cf0; 37 | height: 260px; 38 | line-height: 260px; 39 | margin-top: 40px; 40 | .error403-0-span{ 41 | display: inline-block; 42 | position: relative; 43 | width: 170px; 44 | height: 170px; 45 | border-radius: 50%; 46 | border: 20px solid #ed3f14; 47 | color: #ed3f14; 48 | margin-right: 10px; 49 | i{ 50 | display: inline-block; 51 | font-size: 120px; 52 | position: absolute; 53 | left: 50%; 54 | top: 50%; 55 | transform: translate(-50%,-50%); 56 | } 57 | } 58 | .error403-key-span{ 59 | display: inline-block; 60 | position: relative; 61 | width: 100px; 62 | height: 190px; 63 | border-radius: 50%; 64 | margin-right: 10px; 65 | i{ 66 | display: inline-block; 67 | font-size: 190px; 68 | position: absolute; 69 | left: 20px; 70 | transform: translate(-50%,-60%); 71 | transform-origin: center bottom; 72 | animation: error403animation 2.8s ease 0s infinite; 73 | } 74 | } 75 | } 76 | &-message{ 77 | display: block; 78 | text-align: center; 79 | font-size: 30px; 80 | font-weight: 500; 81 | letter-spacing: 4px; 82 | color: #dddde2; 83 | } 84 | } 85 | &-btn-con{ 86 | text-align: center; 87 | padding: 20px 0; 88 | margin-bottom: 40px; 89 | } 90 | } -------------------------------------------------------------------------------- /client/src/views/home/components/map.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /client/src/views/home/components/visiteVolume.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 77 | -------------------------------------------------------------------------------- /client/src/views/main-components/fullscreen.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 75 | -------------------------------------------------------------------------------- /client/src/views/advanced-router/mutative-router.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 22 | 23 | 93 | -------------------------------------------------------------------------------- /client/src/views/main-components/lockscreen/components/unlock.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 28 | 29 | 81 | -------------------------------------------------------------------------------- /client/build/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | const cleanWebpackPlugin = require('clean-webpack-plugin'); 6 | const UglifyJsParallelPlugin = require('webpack-uglify-parallel'); 7 | const merge = require('webpack-merge'); 8 | const webpackBaseConfig = require('./webpack.base.config.js'); 9 | const os = require('os'); 10 | const fs = require('fs'); 11 | const path = require('path'); 12 | const package = require('../package.json'); 13 | 14 | fs.open('./build/env.js', 'w', function(err, fd) { 15 | const buf = 'export default "production";'; 16 | fs.write(fd, buf, 0, buf.length, 0, function(err, written, buffer) {}); 17 | }); 18 | 19 | module.exports = merge(webpackBaseConfig, { 20 | output: { 21 | path: path.resolve(__dirname, '../dist'), 22 | //publicPath: '/', // 修改 https://iv...admin 这部分为你的服务器域名 23 | filename: '[name].[hash].js', 24 | chunkFilename: '[name].[hash].chunk.js' 25 | }, 26 | plugins: [ 27 | new cleanWebpackPlugin(['dist/*'], { 28 | root: path.resolve(__dirname, '../') 29 | }), 30 | new ExtractTextPlugin({ 31 | filename: '[name].[hash].css', 32 | allChunks: true 33 | }), 34 | new webpack.optimize.CommonsChunkPlugin({ 35 | // name: 'vendors', 36 | // filename: 'vendors.[hash].js' 37 | name: ['vender-exten', 'vender-base'], 38 | minChunks: Infinity 39 | }), 40 | new webpack.DefinePlugin({ 41 | 'process.env': { 42 | NODE_ENV: '"production"' 43 | } 44 | }), 45 | new webpack.optimize.UglifyJsPlugin({ 46 | compress: { 47 | warnings: false 48 | } 49 | }), 50 | // new UglifyJsParallelPlugin({ 51 | // workers: os.cpus().length, 52 | // mangle: true, 53 | // compressor: { 54 | // warnings: false, 55 | // drop_console: true, 56 | // drop_debugger: true 57 | // } 58 | // }), 59 | new CopyWebpackPlugin([ 60 | { 61 | from: 'td_icon.ico' 62 | }, 63 | { 64 | from: 'src/styles/fonts', 65 | to: 'fonts' 66 | }, 67 | { 68 | from: 'src/views/main-components/theme-switch/theme' 69 | } 70 | ], { 71 | ignore: [ 72 | 'text-editor.vue' 73 | ] 74 | }), 75 | new HtmlWebpackPlugin({ 76 | title: 'iView admin v' + package.version, 77 | template: 'index.prod.html', 78 | favicon: './td_icon.ico', 79 | filename: '../dist/index.html', 80 | template: '!!ejs-loader!./src/template/index.ejs', 81 | inject: true 82 | }) 83 | ] 84 | }); -------------------------------------------------------------------------------- /client/build/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const os = require('os'); 3 | const webpack = require('webpack'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | const HappyPack = require('happypack'); 6 | var happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length }); 7 | function resolve (dir) { 8 | return path.join(__dirname, dir); 9 | } 10 | module.exports = { 11 | entry: { 12 | main: ["@/main"], 13 | 'vender-base': ['@/vendors/vendors.base.js'], 14 | 'vender-exten': ['@/vendors/vendors.exten.js'] 15 | }, 16 | output: { 17 | path: path.resolve(__dirname, '../dist') 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.vue$/, 23 | loader: 'vue-loader', 24 | options: { 25 | loaders: { 26 | css: 'vue-style-loader!css-loader', 27 | less: 'vue-style-loader!css-loader!less-loader' 28 | }, 29 | postLoaders: { 30 | html: 'babel-loader' 31 | } 32 | } 33 | }, 34 | { 35 | test: /iview\/.*?js$/, 36 | loader: 'happypack/loader?id=happybabel', 37 | exclude: /node_modules/ 38 | }, 39 | { 40 | test: /\.js$/, 41 | loader: 'happypack/loader?id=happybabel', 42 | exclude: /node_modules/ 43 | }, 44 | { 45 | test: /\.js[x]?$/, 46 | include: [resolve('src')], 47 | exclude: /node_modules/, 48 | loader: 'happypack/loader?id=happybabel' 49 | }, 50 | { 51 | test: /\.css$/, 52 | use: ExtractTextPlugin.extract({ 53 | use: ['css-loader?minimize', 'autoprefixer-loader'], 54 | fallback: 'style-loader' 55 | }) 56 | }, 57 | { 58 | test: /\.less$/, 59 | use: ExtractTextPlugin.extract({ 60 | use: ['css-loader?minimize','autoprefixer-loader', 'less-loader'], 61 | fallback: 'style-loader' 62 | }), 63 | }, 64 | { 65 | test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, 66 | loader: 'url-loader?limit=1024' 67 | }, 68 | { 69 | test: /\.(html|tpl)$/, 70 | loader: 'html-loader' 71 | } 72 | ] 73 | }, 74 | plugins: [ 75 | new HappyPack({ 76 | id: 'happybabel', 77 | loaders: ['babel-loader'], 78 | threadPool: happyThreadPool, 79 | verbose: true 80 | }) 81 | ], 82 | resolve: { 83 | extensions: ['.js', '.vue'], 84 | alias: { 85 | 'vue': 'vue/dist/vue.esm.js', 86 | '@': resolve('../src'), 87 | } 88 | } 89 | }; -------------------------------------------------------------------------------- /client/src/views/advanced-router/argument-page.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | 21 | 95 | -------------------------------------------------------------------------------- /client/src/views/home/components/countUp.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 109 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iview-admin", 3 | "version": "1.3.1", 4 | "description": "a management bases on iview", 5 | "main": "index.js", 6 | "scripts": { 7 | "init": "webpack --progress --config build/webpack.dev.config.js", 8 | "dev": "webpack-dev-server --content-base ./ --open --inline --hot --compress --config build/webpack.dev.config.js", 9 | "build": "webpack --progress --hide-modules --config build/webpack.prod.config.js", 10 | "lint": "eslint --fix --ext .js,.vue src", 11 | "test": "npm run lint" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/iview/iview-admin.git" 16 | }, 17 | "author": "Lison", 18 | "license": "MIT", 19 | "dependencies": { 20 | "area-data": "^1.0.0", 21 | "axios": "^0.17.1", 22 | "babel-polyfill": "^6.26.0", 23 | "clipboard": "^1.7.1", 24 | "countup": "^1.8.2", 25 | "cropperjs": "^1.2.2", 26 | "echarts": "^3.8.5", 27 | "html2canvas": "^0.5.0-beta4", 28 | "iview": "^2.11.0", 29 | "iview-area": "^1.5.16", 30 | "js-cookie": "^2.2.0", 31 | "json-server": "^0.12.1", 32 | "mockjs": "^1.0.1-beta3", 33 | "rasterizehtml": "^1.2.4", 34 | "simplemde": "^1.11.2", 35 | "sortablejs": "^1.7.0", 36 | "tinymce": "^4.7.4", 37 | "vue": "^2.5.13", 38 | "vue-router": "^3.0.1", 39 | "vue-virtual-scroller": "^0.10.6", 40 | "vuex": "^3.0.1", 41 | "xlsx": "^0.11.17" 42 | }, 43 | "devDependencies": { 44 | "autoprefixer-loader": "^3.2.0", 45 | "babel": "^6.23.0", 46 | "babel-core": "^6.23.1", 47 | "babel-eslint": "^8.2.1", 48 | "babel-loader": "^7.1.2", 49 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 50 | "babel-plugin-transform-runtime": "^6.12.0", 51 | "babel-preset-env": "^1.6.1", 52 | "babel-preset-es2015": "^6.9.0", 53 | "babel-preset-stage-3": "^6.24.1", 54 | "babel-runtime": "^6.11.6", 55 | "browser-sync": "^2.23.6", 56 | "clean-webpack-plugin": "^0.1.17", 57 | "copy-webpack-plugin": "^4.3.1", 58 | "css-hot-loader": "^1.3.5", 59 | "css-loader": "^0.28.8", 60 | "ejs-loader": "^0.3.0", 61 | "eslint": "^4.15.0", 62 | "eslint-config-google": "^0.9.1", 63 | "eslint-config-standard": "^10.2.1", 64 | "eslint-plugin-html": "^4.0.1", 65 | "eslint-plugin-import": "^2.8.0", 66 | "eslint-plugin-node": "^5.2.1", 67 | "eslint-plugin-promise": "^3.6.0", 68 | "eslint-plugin-standard": "^3.0.1", 69 | "extract-text-webpack-plugin": "^3.0.2", 70 | "file-loader": "^1.1.6", 71 | "gulp": "^3.9.1", 72 | "gulp-nodemon": "^2.2.1", 73 | "happypack": "^4.0.0", 74 | "html-loader": "^0.5.4", 75 | "html-webpack-plugin": "^2.28.0", 76 | "less": "^2.7.3", 77 | "less-loader": "^4.0.5", 78 | "semver": "^5.4.1", 79 | "style-loader": "^0.19.1", 80 | "unsupported": "^1.1.0", 81 | "url-loader": "^0.6.2", 82 | "vue-hot-reload-api": "^2.2.4", 83 | "vue-html-loader": "^1.2.3", 84 | "vue-i18n": "^5.0.3", 85 | "vue-loader": "^13.7.0", 86 | "vue-style-loader": "^3.0.3", 87 | "vue-template-compiler": "^2.5.13", 88 | "webpack": "^3.10.0", 89 | "webpack-dev-server": "^2.10.1", 90 | "webpack-merge": "^4.1.1", 91 | "webpack-uglify-parallel": "^0.1.4" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /client/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.2.7 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: { 14 | '/admin': { 15 | target: 'http://api.m.yc.cn/', 16 | changeOrigin: true, 17 | pathRewrite: { 18 | '^/admin': '/admin' 19 | } 20 | }, 21 | '/common': { 22 | target: 'http://api.m.yc.cn/', 23 | changeOrigin: true, 24 | pathRewrite: { 25 | '^/common': '/common' 26 | } 27 | } 28 | }, 29 | 30 | // Various Dev Server settings 31 | host: 'localhost', // can be overwritten by process.env.HOST 32 | port: 1314, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 33 | autoOpenBrowser: true, 34 | errorOverlay: true, 35 | notifyOnErrors: true, 36 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 37 | 38 | // Use Eslint Loader? 39 | // If true, your code will be linted during bundling and 40 | // linting errors and warnings will be shown in the console. 41 | useEslint: true, 42 | // If true, eslint errors and warnings will also be shown in the error overlay 43 | // in the browser. 44 | showEslintErrorsInOverlay: false, 45 | 46 | /** 47 | * Source Maps 48 | */ 49 | 50 | // https://webpack.js.org/configuration/devtool/#development 51 | devtool: 'eval-source-map', 52 | 53 | // If you have problems debugging vue-files in devtools, 54 | // set this to false - it *may* help 55 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 56 | cacheBusting: true, 57 | 58 | // CSS Sourcemaps off by default because relative paths are "buggy" 59 | // with this option, according to the CSS-Loader README 60 | // (https://github.com/webpack/css-loader#sourcemaps) 61 | // In our experience, they generally work as expected, 62 | // just be aware of this issue when enabling this option. 63 | cssSourceMap: false, 64 | }, 65 | 66 | build: { 67 | // Template for index.html 68 | index: path.resolve(__dirname, '../dist/index.html'), 69 | 70 | // Paths 71 | assetsRoot: path.resolve(__dirname, '../dist'), 72 | assetsSubDirectory: 'static', 73 | assetsPublicPath: '/', 74 | 75 | /** 76 | * Source Maps 77 | */ 78 | 79 | productionSourceMap: false, 80 | // https://webpack.js.org/configuration/devtool/#production 81 | devtool: '#source-map', 82 | 83 | // Gzip off by default as many popular static hosts such as 84 | // Surge or Netlify already gzip all static assets for you. 85 | // Before setting to `true`, make sure to: 86 | // npm install --save-dev compression-webpack-plugin 87 | productionGzip: true, 88 | productionGzipExtensions: ['js', 'css'], 89 | 90 | // Run the build command with an extra argument to 91 | // View the bundle analyzer report after build finishes: 92 | // `npm run build --report` 93 | // Set to `true` or `false` to always turn it on or off 94 | bundleAnalyzerReport: process.env.npm_config_report 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /client/src/views/main-components/shrinkable-menu/components/sidebarMenu.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 40 | 41 | 77 | -------------------------------------------------------------------------------- /client/src/views/home/components/serviceRequests.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /client/src/views/error-page/error-page.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 85 | 86 | 98 | -------------------------------------------------------------------------------- /client/src/libs/table2excel.js: -------------------------------------------------------------------------------- 1 | var idTmr; 2 | function getExplorer () { 3 | var explorer = window.navigator.userAgent; 4 | if (explorer.indexOf('MSIE') >= 0) { 5 | // ie 6 | return 'ie'; 7 | } else if (explorer.indexOf('Firefox') >= 0) { 8 | // firefox 9 | return 'Firefox'; 10 | } else if (explorer.indexOf('Chrome') >= 0) { 11 | // Chrome 12 | return 'Chrome'; 13 | } else if (explorer.indexOf('Opera') >= 0) { 14 | // Opera 15 | return 'Opera'; 16 | } else if (explorer.indexOf('Safari') >= 0) { 17 | // Safari 18 | return 'Safari'; 19 | }; 20 | }; 21 | function tranform (table, aId, name) { 22 | let tableHead = table.$children[0].$el; 23 | let tableBody = table.$children[1].$el; 24 | let tableInnerHTML = ''; 25 | if (table.$children.length !== 1) { 26 | let len = tableBody.rows.length; 27 | let i = -1; 28 | while (i < len) { 29 | if (i === -1) { 30 | Array.from(tableHead.rows[0].children).forEach((td) => { 31 | tableInnerHTML = tableInnerHTML + '' + td.children[0].children[0].innerHTML + ''; 32 | }); 33 | tableInnerHTML += ''; 34 | } else { 35 | tableInnerHTML += ''; 36 | Array.from(tableBody.rows[i].children).forEach((td) => { 37 | tableInnerHTML = tableInnerHTML + '' + td.children[0].children[0].innerHTML + ''; 38 | }); 39 | tableInnerHTML += ''; 40 | } 41 | i++; 42 | } 43 | tableInnerHTML += ''; 44 | } 45 | 46 | if (getExplorer() !== 'Safari' && name.substr(-1, 4) !== '.xls') { 47 | name += '.xls'; 48 | } 49 | 50 | if (getExplorer() === 'ie') { 51 | var curTbl = table; 52 | var oXL = new ActiveXObject('Excel.Application'); 53 | var oWB = oXL.Workbooks.Add(); 54 | var xlsheet = oWB.Worksheets(1); 55 | var sel = document.body.createTextRange(); 56 | sel.moveToElementText(curTbl); 57 | sel.select(); 58 | sel.execCommand('Copy'); 59 | xlsheet.Paste(); 60 | oXL.Visible = true; 61 | 62 | try { 63 | var fname = oXL.Application.GetSaveAsFilename('Excel.xls', 'Excel Spreadsheets (*.xls), *.xls'); 64 | } catch (e) { 65 | print('Nested catch caught ' + e); 66 | } finally { 67 | oWB.SaveAs(fname); 68 | // oWB.Close(savechanges = false); 69 | oXL.Quit(); 70 | oXL = null; 71 | idTmr = setInterval(Cleanup(), 1); 72 | } 73 | } else { 74 | tableToExcel(tableInnerHTML, aId, name); 75 | } 76 | } 77 | function Cleanup () { 78 | window.clearInterval(idTmr); 79 | // CollectGarbage(); 80 | } 81 | let tableToExcel = (function () { 82 | let uri = 'data:application/vnd.ms-excel;base64,'; 83 | let template = '{table}
'; 84 | let base64 = function (s) { return window.btoa(unescape(encodeURIComponent(s))); }; 85 | let format = function (s, c) { 86 | return s.replace(/{(\w+)}/g, function (m, p) { return c[p]; }); 87 | }; 88 | return function (table, aId, name) { 89 | let ctx = {worksheet: name || 'Worksheet', table: table}; 90 | document.getElementById(aId).href = uri + base64(format(template, ctx)); 91 | document.getElementById(aId).download = name; 92 | document.getElementById(aId).click(); 93 | }; 94 | })(); 95 | 96 | const table2excel = {}; 97 | 98 | table2excel.transform = tranform; 99 | 100 | export default table2excel; 101 | -------------------------------------------------------------------------------- /client/src/views/message/message.less: -------------------------------------------------------------------------------- 1 | .message{ 2 | &-main-con{ 3 | position: absolute; 4 | left: 0px; 5 | top: 0px; 6 | right: 0px; 7 | bottom: 0px; 8 | } 9 | &-mainlist-con{ 10 | position: absolute; 11 | left: 0; 12 | top: 10px; 13 | width: 300px; 14 | bottom: 0; 15 | padding: 10px 0; 16 | div{ 17 | padding: 10px; 18 | margin: 0 20px; 19 | border-bottom: 1px dashed #d2d3d2; 20 | &:last-child{ 21 | border: none; 22 | } 23 | .message-count-badge-outer{ 24 | float: right; 25 | } 26 | .message-count-badge{ 27 | background: #d2d3d2; 28 | } 29 | &:first-child .message-count-badge{ 30 | background: #ed3f14; 31 | } 32 | .mes-type-btn-text{ 33 | margin-left: 10px; 34 | } 35 | } 36 | } 37 | &-content-con{ 38 | position: absolute; 39 | top: 10px; 40 | right: 10px; 41 | bottom: 10px; 42 | left: 300px; 43 | background: white; 44 | border-radius: 3px; 45 | box-shadow: 2px 2px 10px 2px rgba(0, 0, 0, .1); 46 | overflow: auto; 47 | .message-title-list-con{ 48 | width: 100%; 49 | height: 100%; 50 | } 51 | .message-content-top-bar{ 52 | height: 40px; 53 | width: 100%; 54 | background: white; 55 | position: absolute; 56 | left: 0; 57 | top: 0; 58 | border-bottom: 1px solid #dededb; 59 | .mes-back-btn-con{ 60 | position: absolute; 61 | width: 70px; 62 | height: 30px; 63 | left: 0; 64 | top: 5px; 65 | } 66 | .mes-title{ 67 | position: absolute; 68 | top: 0; 69 | right: 70px; 70 | bottom: 0; 71 | left: 70px; 72 | line-height: 40px; 73 | padding: 0 30px; 74 | white-space: nowrap; 75 | overflow: hidden; 76 | text-overflow: ellipsis; 77 | text-align: center; 78 | } 79 | } 80 | .mes-time-con{ 81 | position: absolute; 82 | width: 100%; 83 | top: 40px; 84 | left: 0; 85 | padding: 20px 0; 86 | text-align: center; 87 | font-size: 14px; 88 | color: #b7b7b5; 89 | } 90 | .message-content-body{ 91 | position: absolute; 92 | top: 90px; 93 | right: 0; 94 | bottom: 0; 95 | left: 0; 96 | overflow: auto; 97 | .message-content{ 98 | padding: 10px 20px; 99 | } 100 | } 101 | } 102 | } 103 | .back-message-list-enter, .back-message-list-leave-to{ 104 | opacity: 0; 105 | } 106 | .back-message-list-enter-active, .back-message-list-leave-active{ 107 | transition: all .5s; 108 | } 109 | .back-message-list-enter-to, .back-message-list-leave{ 110 | opacity: 1; 111 | } 112 | .view-message-enter, .view-message-leave-to{ 113 | opacity: 0; 114 | } 115 | .view-message-enter-active, .view-message-leave-active{ 116 | transition: all .5s; 117 | } 118 | .view-message-enter-to, .view-message-leave{ 119 | opacity: 1; 120 | } 121 | 122 | .mes-current-type-btn-enter, .mes-current-type-btn-leave-to{ 123 | opacity: 0; 124 | width: 0; 125 | } 126 | .mes-current-type-btn-enter-active, .mes-current-type-btn-leave-active{ 127 | transition: all .3s; 128 | } 129 | .mes-current-type-btn-enter-to, .mes-current-type-btn-leave{ 130 | opacity: 1; 131 | width: 12px; 132 | } -------------------------------------------------------------------------------- /client/src/views/login.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 39 | 40 | 99 | 100 | 103 | -------------------------------------------------------------------------------- /client/src/libs/http.js: -------------------------------------------------------------------------------- 1 | 2 | import methodMap from './apiMap'; 3 | import AsInst from './axios'; 4 | import {Message} from 'iview'; 5 | class Http { 6 | } 7 | Http.install = function (Vue) { 8 | /** 9 | * 全局请求接口 10 | * @param method 方法 11 | * @param opts 参数 12 | * @param toast 是否提示 13 | * @returns {string} 14 | */ 15 | Vue.prototype.request = function (method, opts, toast) { 16 | let m = methodMap[method]; 17 | if (m) { 18 | let optsType = typeof (opts); 19 | if (optsType === null || optsType !== 'object') { 20 | opts = {}; 21 | } 22 | if (typeof m.method === 'undefined') { 23 | console.log('method 错误', '缺少请求 method 方法', '\n'); 24 | return false; 25 | } 26 | //如果有给 toast 参数则显示 loading 加载数据 27 | if (toast && typeof (toast) === 'boolean') { 28 | Vue.prototype.$loading('加载中...'); 29 | } else if (toast && typeof (toast) === 'string') { 30 | Vue.prototype.$loading(toast); 31 | } 32 | if (m.method === 'get') { 33 | return Vue.prototype.apiGet(m.url, opts); 34 | } else if (m.method === 'post') { 35 | return Vue.prototype.apiPost(m.url, opts); 36 | } else { 37 | return false; 38 | } 39 | } else { 40 | closeLoading(); 41 | console.log('url 错误', '返回结果:err = ', '无法请求,无效的请求!', '\n'); 42 | } 43 | }; 44 | /** 45 | * POST 请求 无提示 46 | * @param url 请求URL 47 | * @param data 请求数据 48 | * @param toast 是否显示 modal 49 | * @returns {Promise} 50 | */ 51 | Vue.prototype.apiPost = function (url, data, toast = false) { 52 | if (toast && typeof (toast) === 'boolean') { 53 | Vue.prototype.$loading('加载中...'); 54 | } else if (toast && typeof (toast) === 'string') { 55 | Vue.prototype.$loading(toast); 56 | } 57 | return new Promise((resolve, reject) => { 58 | AsInst.post(url, data).then((response) => { 59 | setTimeout(() => closeLoading(), 800); 60 | resolve(response.data); 61 | }).catch((error) => { 62 | if (error.status === 200) { 63 | if (!error.data) { 64 | Message.error('接口输出异常...'); 65 | console.log('Customize Notice', error); 66 | } else if (error.data && error.data.code === 2000) { 67 | Message.error(error.data.msg); 68 | console.log('Customize Notice', error); 69 | } 70 | } else if (error.status === 500) { 71 | Message.error('后端服务请求500错误,如一直出现错误,请联系我们'); 72 | console.log('Customize Notice', error); 73 | } else { 74 | Message.error('服务请求出错'); 75 | console.log('Customize Notice', error); 76 | } 77 | closeLoading(); 78 | reject(error); 79 | }); 80 | }); 81 | }; 82 | /** 83 | * GET 请求 无提示 84 | * @param url 请求URL 85 | * @param data 请求数据 86 | * @returns {Promise} 87 | */ 88 | Vue.prototype.apiGet = function (url, data, toast = false) { 89 | if (toast && typeof (toast) === 'boolean') { 90 | Vue.prototype.$loading('加载中...'); 91 | } else if (toast && typeof (toast) === 'string') { 92 | Vue.prototype.$loading(toast); 93 | } 94 | return new Promise((resolve, reject) => { 95 | AsInst.get(url, { 96 | params: data 97 | }).then((response) => { 98 | setTimeout(() => closeLoading(), 800); 99 | resolve(response.data); 100 | }).catch((error) => { 101 | if (error.status === 200) { 102 | if (!error.data) { 103 | Message.error('接口输出异常...'); 104 | console.log('Customize Notice', error); 105 | } else if (error.data && error.data.code === 2000) { 106 | this.$Message.error(error.data.msg); 107 | console.log('Customize Notice', error); 108 | } 109 | } else if (error.status === 500) { 110 | Message.error('后端服务请求500错误,如一直出现错误,请联系我们'); 111 | console.log('Customize Notice', error); 112 | } else { 113 | Message.error('服务请求出错'); 114 | console.log('Customize Notice', error); 115 | } 116 | closeLoading(); 117 | reject(error); 118 | }); 119 | }); 120 | }; 121 | /** 122 | * 关闭方法 123 | */ 124 | function closeLoading() { 125 | Vue.prototype.$loading.close(); 126 | } 127 | }; 128 | export default Http; 129 | -------------------------------------------------------------------------------- /client/src/router/router.js: -------------------------------------------------------------------------------- 1 | import Main from '@/views/Main.vue'; 2 | 3 | // 不作为Main组件的子页面展示的页面单独写,如下 4 | export const loginRouter = { 5 | path: '/login', 6 | name: 'login', 7 | meta: { 8 | title: 'Login - 登录' 9 | }, 10 | component: () => import('@/views/login.vue') 11 | }; 12 | 13 | export const page404 = { 14 | path: '/*', 15 | name: 'error-404', 16 | meta: { 17 | title: '404-页面不存在' 18 | }, 19 | component: () => import('@/views/error-page/404.vue') 20 | }; 21 | 22 | export const page403 = { 23 | path: '/403', 24 | meta: { 25 | title: '403-权限不足' 26 | }, 27 | name: 'error-403', 28 | component: () => import('@//views/error-page/403.vue') 29 | }; 30 | 31 | export const page500 = { 32 | path: '/500', 33 | meta: { 34 | title: '500-服务端错误' 35 | }, 36 | name: 'error-500', 37 | component: () => import('@/views/error-page/500.vue') 38 | }; 39 | 40 | export const locking = { 41 | path: '/locking', 42 | name: 'locking', 43 | component: () => import('@/views/main-components/lockscreen/components/locking-page.vue') 44 | }; 45 | 46 | // 作为Main组件的子页面展示但是不在左侧菜单显示的路由写在otherRouter里 47 | export const otherRouter = { 48 | path: '/', 49 | name: 'otherRouter', 50 | redirect: '/home', 51 | component: Main, 52 | children: [ 53 | { path: 'home', title: {i18n: 'home'}, name: 'home_index', component: () => import('@/views/home/home.vue') }, 54 | { path: 'ownspace', title: '个人中心', name: 'ownspace_index', component: () => import('@/views/own-space/own-space.vue') }, 55 | { path: 'order/:order_id', title: '订单详情', name: 'order-info', component: () => import('@/views/advanced-router/component/order-info.vue') }, // 用于展示动态路由 56 | { path: 'shopping', title: '购物详情', name: 'shopping', component: () => import('@/views/advanced-router/component/shopping-info.vue') }, // 用于展示带参路由 57 | { path: 'message', title: '消息中心', name: 'message_index', component: () => import('@/views/message/message.vue') } 58 | ] 59 | }; 60 | 61 | // 作为Main组件的子页面展示并且在左侧菜单显示的路由写在appRouter里 62 | export const appRouter = [ 63 | { 64 | path: '/system', 65 | icon: 'key', 66 | name: 'system', 67 | title: '系统设置', 68 | component: Main, 69 | children: [ 70 | { path: 'user_list', title: '用户列表', name: 'user_list', component: () => import('@/views/system/user/list.vue') }, 71 | { path: 'role_list', title: '角色列表', name: 'role_list', component: () => import('@/views/system/role/list.vue') }, 72 | ] 73 | }, 74 | { 75 | path: '/example', 76 | icon: 'android-sad', 77 | title: '综合样例', 78 | name: 'example', 79 | component: Main, 80 | children: [{ 81 | path: 'table', 82 | title: '表格', 83 | name: 'table', 84 | component: () =>import ('@/views/example/table/index.vue'), 85 | children:[ 86 | { path: 'editableTable', title: '可编辑表格', name: 'editable-table', icon: 'edit', component: () => import('@/views/example/table/editable-table.vue') }, 87 | { path: 'searchableTable', title: '可搜索表格', name: 'searchable-table', icon: 'search', component: () => import('@/views/example/table/searchable-table.vue') }, 88 | ] 89 | }] 90 | }, 91 | { 92 | path: '/advanced-router', 93 | icon: 'ios-infinite', 94 | name: 'advanced-router', 95 | title: '高级路由', 96 | component: Main, 97 | children: [ 98 | { path: 'mutative-router', title: '动态路由', name: 'mutative-router', icon: 'link', component: () => import('@/views/advanced-router/mutative-router.vue') }, 99 | { path: 'argument-page', title: '带参页面', name: 'argument-page', icon: 'android-send', component: () => import('@/views/advanced-router/argument-page.vue') } 100 | ] 101 | }, 102 | { 103 | path: '/error-page', 104 | icon: 'android-sad', 105 | title: '错误页面', 106 | name: 'errorpage', 107 | component: Main, 108 | children: [ 109 | { path: 'index', title: '错误页面', name: 'errorpage_index', component: () => import('@/views/error-page/error-page.vue') } 110 | ] 111 | } 112 | ]; 113 | 114 | // 所有上面定义的路由都要写在下面的routers里 115 | export const routers = [ 116 | loginRouter, 117 | otherRouter, 118 | locking, 119 | ...appRouter, 120 | page500, 121 | page403, 122 | page404 123 | ]; 124 | -------------------------------------------------------------------------------- /client/src/libs/plugin/index.js: -------------------------------------------------------------------------------- 1 | 2 | class Plugin { 3 | } 4 | 5 | Plugin.install = function (Vue, options) { 6 | /** 日期过滤插件 **/ 7 | Vue.prototype.$formatDate = function (value, format = 'yyyy-MM-dd h:m:s') { 8 | if (!value) return; 9 | //防止PHP后台输出为10位的时间戳 10 | if (value.length === 10) { 11 | value = value * 1000; 12 | } 13 | let newFormat = ''; 14 | if (format) { 15 | newFormat = format; 16 | } else { 17 | newFormat = 'yyyy-MM-dd h:m:s'; 18 | } 19 | let time = new Date(parseInt(value)); 20 | let date = { 21 | 'M+': time.getMonth() + 1, 22 | 'd+': time.getDate(), 23 | 'h+': time.getHours(), 24 | 'm+': time.getMinutes(), 25 | 's+': time.getSeconds(), 26 | 'q+': Math.floor((time.getMonth() + 3) / 3), 27 | 'S+': time.getMilliseconds() 28 | }; 29 | if (/(y+)/i.test(newFormat)) { 30 | newFormat = newFormat.replace(RegExp.$1, (time.getFullYear() + '').substr(4 - RegExp.$1.length)); 31 | } 32 | for (let k in date) { 33 | if (new RegExp('(' + k + ')').test(newFormat)) { 34 | newFormat = newFormat.replace(RegExp.$1, RegExp.$1.length === 1 ? date[k] : ('00' + date[k]).substr(('' + date[k]).length)); 35 | } 36 | } 37 | return newFormat; 38 | }; 39 | /** 金钱格式化 **/ 40 | Vue.prototype.$formatMoney = (money, num = 2) => { 41 | num = num > 0 && num <= 20 ? num : 2; 42 | money = parseFloat((money + '').replace(/[^\d\\.-]/g, '')).toFixed(num) + ''; 43 | let l = money.split('.')[0].split('').reverse(); 44 | let r = money.split('.')[1]; 45 | let t = ''; 46 | for (let i = 0; i < l.length; i++) { 47 | t += l[i] + ((i + 1) % 3 === 0 && (i + 1) !== l.length ? ',' : ''); 48 | } 49 | return t.split('').reverse().join('') + '.' + r; 50 | }; 51 | /** 52 | * 判断获取字符串长度 53 | * @param str 54 | * @returns {number} 55 | */ 56 | Vue.prototype.$strLen = (str) => { 57 | let len = 0; 58 | for (let i = 0; i < str.length; i++) { 59 | if (str.charCodeAt(i) > 127 || str.charCodeAt(i) === 94) { 60 | len += 1; 61 | } else { 62 | len++; 63 | } 64 | } 65 | return len; 66 | }; 67 | /** 68 | * 除法计算 69 | * @param arg1 70 | * @param arg2 71 | * @returns {number} 72 | */ 73 | Vue.prototype.$accDiv = (arg1, arg2) => { 74 | let t1 = 0; 75 | let t2 = 0; 76 | let r1; 77 | let r2; 78 | try { 79 | t1 = arg1.toString().split('.')[1].length; 80 | } catch (e) { 81 | } 82 | try { 83 | t2 = arg2.toString().split('.')[1].length; 84 | } catch (e) { 85 | } 86 | r1 = Number(arg1.toString().replace('.', '')); 87 | r2 = Number(arg2.toString().replace('.', '')); 88 | return (r1 / r2) * Math.pow(10, t2 - t1); 89 | }; 90 | /** 91 | * 乘法计算 92 | * @param arg1 93 | * @param arg2 94 | * @returns {number} 95 | */ 96 | Vue.prototype.$accMul = (arg1, arg2) => { 97 | let m = 0; 98 | let s1 = arg1.toString(); 99 | let s2 = arg2.toString(); 100 | try { 101 | m += s1.split('.')[1].length; 102 | } catch (e) { 103 | } 104 | try { 105 | m += s2.split('.')[1].length; 106 | } catch (e) { 107 | } 108 | return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m); 109 | }; 110 | /** 111 | * 减法计算 112 | * @param arg1 113 | * @param arg2 114 | * @returns {number} 115 | */ 116 | Vue.prototype.$accSub = (arg1, arg2) => { 117 | let r1, r2, m, n; 118 | try { 119 | r1 = arg1.toString().split('.')[1].length; 120 | } catch (e) { 121 | r1 = 0; 122 | } 123 | try { 124 | r2 = arg2.toString().split('.')[1].length; 125 | } catch (e) { 126 | r2 = 0; 127 | } 128 | m = Math.pow(10, Math.max(r1, r2)); 129 | //动态控制精度长度 130 | n = (r1 >= r2) ? r1 : r2; 131 | return Number(((arg1 * m - arg2 * m) / m).toFixed(n)); 132 | }; 133 | /** 134 | * 加法计算 135 | * @param arg1 136 | * @param arg2 137 | * @returns {number} 138 | */ 139 | Vue.prototype.$accAdd = (arg1, arg2) => { 140 | let r1, r2, m; 141 | try { 142 | r1 = arg1.toString().split('.')[1].length; 143 | } catch (e) { 144 | r1 = 0; 145 | } 146 | try { 147 | r2 = arg2.toString().split('.')[1].length; 148 | } catch (e) { 149 | r2 = 0; 150 | } 151 | m = Math.pow(10, Math.max(r1, r2)); 152 | return Number((arg1 * m + arg2 * m) / m); 153 | }; 154 | }; 155 | export default Plugin; 156 | -------------------------------------------------------------------------------- /client/src/views/example/table/components/multiPageTable.vue: -------------------------------------------------------------------------------- 1 | 11 | 112 | -------------------------------------------------------------------------------- /client/src/views/example/table/searchable-table.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 57 | 58 | 117 | -------------------------------------------------------------------------------- /client/src/views/example/table/data/table2csv.js: -------------------------------------------------------------------------------- 1 | export const table2csvData = [ 2 | { 3 | 'name': '推广名称1', 4 | 'fav': 0, 5 | 'show': 7302, 6 | 'weak': 5627, 7 | 'signin': 1563, 8 | 'click': 4254, 9 | 'active': 1438, 10 | 'day7': 274, 11 | 'day30': 285, 12 | 'tomorrow': 1727, 13 | 'day': 558, 14 | 'week': 4440, 15 | 'month': 5610 16 | }, 17 | { 18 | 'name': '推广名称2', 19 | 'fav': 0, 20 | 'show': 4720, 21 | 'weak': 4086, 22 | 'signin': 3792, 23 | 'click': 8690, 24 | 'active': 8470, 25 | 'day7': 8172, 26 | 'day30': 5197, 27 | 'tomorrow': 1684, 28 | 'day': 2593, 29 | 'week': 2507, 30 | 'month': 1537 31 | }, 32 | { 33 | 'name': '推广名称3', 34 | 'fav': 0, 35 | 'show': 7181, 36 | 'weak': 8007, 37 | 'signin': 8477, 38 | 'click': 1879, 39 | 'active': 16, 40 | 'day7': 2249, 41 | 'day30': 3450, 42 | 'tomorrow': 377, 43 | 'day': 1561, 44 | 'week': 3219, 45 | 'month': 1588 46 | }, 47 | { 48 | 'name': '推广名称4', 49 | 'fav': 0, 50 | 'show': 9911, 51 | 'weak': 8976, 52 | 'signin': 8807, 53 | 'click': 8050, 54 | 'active': 7668, 55 | 'day7': 1547, 56 | 'day30': 2357, 57 | 'tomorrow': 7278, 58 | 'day': 5309, 59 | 'week': 1655, 60 | 'month': 9043 61 | }, 62 | { 63 | 'name': '推广名称5', 64 | 'fav': 0, 65 | 'show': 934, 66 | 'weak': 1394, 67 | 'signin': 6463, 68 | 'click': 5278, 69 | 'active': 9256, 70 | 'day7': 209, 71 | 'day30': 3563, 72 | 'tomorrow': 8285, 73 | 'day': 1230, 74 | 'week': 4840, 75 | 'month': 9908 76 | }, 77 | { 78 | 'name': '推广名称6', 79 | 'fav': 0, 80 | 'show': 6856, 81 | 'weak': 1608, 82 | 'signin': 457, 83 | 'click': 4949, 84 | 'active': 2909, 85 | 'day7': 4525, 86 | 'day30': 6171, 87 | 'tomorrow': 1920, 88 | 'day': 1966, 89 | 'week': 904, 90 | 'month': 6851 91 | }, 92 | { 93 | 'name': '推广名称7', 94 | 'fav': 0, 95 | 'show': 5107, 96 | 'weak': 6407, 97 | 'signin': 4166, 98 | 'click': 7970, 99 | 'active': 1002, 100 | 'day7': 8701, 101 | 'day30': 9040, 102 | 'tomorrow': 7632, 103 | 'day': 4061, 104 | 'week': 4359, 105 | 'month': 3676 106 | } 107 | ]; 108 | 109 | export const csvColumns = [ 110 | { 111 | 'title': '名称', 112 | 'key': 'name', 113 | 'fixed': 'left', 114 | 'width': 200 115 | }, 116 | { 117 | 'title': '展示', 118 | 'key': 'show', 119 | 'width': 150, 120 | 'sortable': true, 121 | filters: [ 122 | { 123 | label: '大于4000', 124 | value: 1 125 | }, 126 | { 127 | label: '小于4000', 128 | value: 2 129 | } 130 | ], 131 | filterMultiple: false, 132 | filterMethod (value, row) { 133 | if (value === 1) { 134 | return row.show > 4000; 135 | } else if (value === 2) { 136 | return row.show < 4000; 137 | } 138 | } 139 | }, 140 | { 141 | 'title': '唤醒', 142 | 'key': 'weak', 143 | 'width': 150, 144 | 'sortable': true 145 | }, 146 | { 147 | 'title': '登录', 148 | 'key': 'signin', 149 | 'width': 150, 150 | 'sortable': true 151 | }, 152 | { 153 | 'title': '点击', 154 | 'key': 'click', 155 | 'width': 150, 156 | 'sortable': true 157 | }, 158 | { 159 | 'title': '激活', 160 | 'key': 'active', 161 | 'width': 150, 162 | 'sortable': true 163 | }, 164 | { 165 | 'title': '7日留存', 166 | 'key': 'day7', 167 | 'width': 150, 168 | 'sortable': true 169 | }, 170 | { 171 | 'title': '30日留存', 172 | 'key': 'day30', 173 | 'width': 150, 174 | 'sortable': true 175 | }, 176 | { 177 | 'title': '次日留存', 178 | 'key': 'tomorrow', 179 | 'width': 150, 180 | 'sortable': true 181 | }, 182 | { 183 | 'title': '日活跃', 184 | 'key': 'day', 185 | 'width': 150, 186 | 'sortable': true 187 | }, 188 | { 189 | 'title': '周活跃', 190 | 'key': 'week', 191 | 'width': 150, 192 | 'sortable': true 193 | }, 194 | { 195 | 'title': '月活跃', 196 | 'key': 'month', 197 | 'width': 150, 198 | 'sortable': true 199 | } 200 | ]; 201 | -------------------------------------------------------------------------------- /client/src/views/example/table/data/table2excel.js: -------------------------------------------------------------------------------- 1 | export const table2excelData = [ 2 | { 3 | 'name': '推广名称1', 4 | 'fav': 0, 5 | 'show': 7302, 6 | 'weak': 5627, 7 | 'signin': 1563, 8 | 'click': 4254, 9 | 'active': 1438, 10 | 'day7': 274, 11 | 'day30': 285, 12 | 'tomorrow': 1727, 13 | 'day': 558, 14 | 'week': 4440, 15 | 'month': 5610 16 | }, 17 | { 18 | 'name': '推广名称2', 19 | 'fav': 0, 20 | 'show': 4720, 21 | 'weak': 4086, 22 | 'signin': 3792, 23 | 'click': 8690, 24 | 'active': 8470, 25 | 'day7': 8172, 26 | 'day30': 5197, 27 | 'tomorrow': 1684, 28 | 'day': 2593, 29 | 'week': 2507, 30 | 'month': 1537 31 | }, 32 | { 33 | 'name': '推广名称3', 34 | 'fav': 0, 35 | 'show': 7181, 36 | 'weak': 8007, 37 | 'signin': 8477, 38 | 'click': 1879, 39 | 'active': 16, 40 | 'day7': 2249, 41 | 'day30': 3450, 42 | 'tomorrow': 377, 43 | 'day': 1561, 44 | 'week': 3219, 45 | 'month': 1588 46 | }, 47 | { 48 | 'name': '推广名称4', 49 | 'fav': 0, 50 | 'show': 9911, 51 | 'weak': 8976, 52 | 'signin': 8807, 53 | 'click': 8050, 54 | 'active': 7668, 55 | 'day7': 1547, 56 | 'day30': 2357, 57 | 'tomorrow': 7278, 58 | 'day': 5309, 59 | 'week': 1655, 60 | 'month': 9043 61 | }, 62 | { 63 | 'name': '推广名称5', 64 | 'fav': 0, 65 | 'show': 934, 66 | 'weak': 1394, 67 | 'signin': 6463, 68 | 'click': 5278, 69 | 'active': 9256, 70 | 'day7': 209, 71 | 'day30': 3563, 72 | 'tomorrow': 8285, 73 | 'day': 1230, 74 | 'week': 4840, 75 | 'month': 9908 76 | }, 77 | { 78 | 'name': '推广名称6', 79 | 'fav': 0, 80 | 'show': 6856, 81 | 'weak': 1608, 82 | 'signin': 457, 83 | 'click': 4949, 84 | 'active': 2909, 85 | 'day7': 4525, 86 | 'day30': 6171, 87 | 'tomorrow': 1920, 88 | 'day': 1966, 89 | 'week': 904, 90 | 'month': 6851 91 | }, 92 | { 93 | 'name': '推广名称7', 94 | 'fav': 0, 95 | 'show': 5107, 96 | 'weak': 6407, 97 | 'signin': 4166, 98 | 'click': 7970, 99 | 'active': 1002, 100 | 'day7': 8701, 101 | 'day30': 9040, 102 | 'tomorrow': 7632, 103 | 'day': 4061, 104 | 'week': 4359, 105 | 'month': 3676 106 | }, 107 | { 108 | 'name': '推广名称8', 109 | 'fav': 0, 110 | 'show': 5107, 111 | 'weak': 6407, 112 | 'signin': 4166, 113 | 'click': 7970, 114 | 'active': 1002, 115 | 'day7': 8701, 116 | 'day30': 9040, 117 | 'tomorrow': 7632, 118 | 'day': 4061, 119 | 'week': 4359, 120 | 'month': 3676 121 | }, 122 | { 123 | 'name': '推广名称9', 124 | 'fav': 0, 125 | 'show': 5107, 126 | 'weak': 6407, 127 | 'signin': 4166, 128 | 'click': 7970, 129 | 'active': 1002, 130 | 'day7': 8701, 131 | 'day30': 9040, 132 | 'tomorrow': 7632, 133 | 'day': 4061, 134 | 'week': 4359, 135 | 'month': 3676 136 | }, 137 | { 138 | 'name': '推广名称10', 139 | 'fav': 0, 140 | 'show': 5107, 141 | 'weak': 6407, 142 | 'signin': 4166, 143 | 'click': 7970, 144 | 'active': 1002, 145 | 'day7': 8701, 146 | 'day30': 9040, 147 | 'tomorrow': 7632, 148 | 'day': 4061, 149 | 'week': 4359, 150 | 'month': 3676 151 | } 152 | ]; 153 | 154 | export const excelColumns = [ 155 | { 156 | 'title': '名称', 157 | 'key': 'name' 158 | }, 159 | { 160 | 'title': '展示', 161 | 'key': 'show', 162 | 'sortable': true, 163 | filters: [ 164 | { 165 | label: '大于4000', 166 | value: 1 167 | }, 168 | { 169 | label: '小于4000', 170 | value: 2 171 | } 172 | ], 173 | filterMultiple: false, 174 | filterMethod (value, row) { 175 | if (value === 1) { 176 | return row.show > 4000; 177 | } else if (value === 2) { 178 | return row.show < 4000; 179 | } 180 | } 181 | }, 182 | { 183 | 'title': '唤醒', 184 | 'key': 'weak', 185 | 'sortable': true 186 | }, 187 | { 188 | 'title': '登录', 189 | 'key': 'signin', 190 | 'sortable': true 191 | }, 192 | { 193 | 'title': '点击', 194 | 'key': 'click', 195 | 'sortable': true 196 | }, 197 | { 198 | 'title': '激活', 199 | 'key': 'active', 200 | 'sortable': true 201 | }, 202 | { 203 | 'title': '30日留存', 204 | 'key': 'day30', 205 | 'sortable': true 206 | }, 207 | { 208 | 'title': '月活跃', 209 | 'key': 'month', 210 | 'sortable': true 211 | } 212 | ]; 213 | -------------------------------------------------------------------------------- /client/src/views/main-components/lockscreen/styles/unlock.less: -------------------------------------------------------------------------------- 1 | .unlock-body-con{ 2 | position: absolute; 3 | width: 400px; 4 | height: 100px; 5 | left: 50%; 6 | top: 50%; 7 | margin-left: -200px; 8 | margin-top: -200px; 9 | transform-origin: center center; 10 | z-index: 10; 11 | .unlock-avator-con{ 12 | position: absolute; 13 | left: 50%; 14 | top: 50%; 15 | transform: translate(-50%,-50%); 16 | box-sizing: border-box; 17 | width: 100px; 18 | height: 100px; 19 | border-radius: 50%; 20 | overflow: hidden; 21 | border: 2px solid white; 22 | cursor: pointer; 23 | transition: all .5s; 24 | z-index: 12; 25 | box-shadow: 0 0 10px 2px rgba(255, 255, 255, .3); 26 | .unlock-avator-img{ 27 | width: 100%; 28 | height: 100%; 29 | display: block; 30 | z-index: 7; 31 | } 32 | .unlock-avator-cover{ 33 | width: 100%; 34 | height: 100%; 35 | background: rgba(0, 0, 0, .6); 36 | z-index: 11600; 37 | position: absolute; 38 | left: 0; 39 | top: 0; 40 | opacity: 0; 41 | transition: opacity .2s; 42 | color: white; 43 | span{ 44 | display: block; 45 | margin: 20px auto 5px; 46 | text-align: center; 47 | } 48 | p{ 49 | text-align: center; 50 | font-size: 16px; 51 | font-weight: 500; 52 | } 53 | } 54 | &:hover .unlock-avator-cover{ 55 | opacity: 1; 56 | transition: opacity .2s; 57 | } 58 | } 59 | .unlock-avator-under-back{ 60 | position: absolute; 61 | left: 50%; 62 | top: 50%; 63 | transform: translate(-45px,-50%); 64 | box-sizing: border-box; 65 | width: 100px; 66 | height: 100px; 67 | border-radius: 50%; 68 | background: #667aa6; 69 | transition: all .5s; 70 | z-index: 5; 71 | } 72 | .unlock-input-con{ 73 | position: absolute; 74 | height: 70px; 75 | width: 350px; 76 | top: 15px; 77 | right: 0px; 78 | .unlock-input-overflow-con{ 79 | position: absolute; 80 | width: 100%; 81 | height: 100%; 82 | left: 0; 83 | top: 0; 84 | overflow: hidden; 85 | .unlock-overflow-body{ 86 | position: absolute; 87 | top: 0; 88 | right: 0; 89 | width: 100%; 90 | height: 100%; 91 | transition: all .5s ease .5s; 92 | .unlock-input{ 93 | float: left; 94 | display: block; 95 | box-sizing: content-box; 96 | height: 22px; 97 | width: 230px; 98 | font-size: 18px; 99 | outline: none; 100 | padding: 11px 10px 11px 30px; 101 | border: 2px solid #e2ddde; 102 | margin-top: 10px; 103 | } 104 | .unlock-btn{ 105 | float: left; 106 | display: block; 107 | font-size: 20px; 108 | padding: 7px 30px; 109 | cursor: pointer; 110 | border-radius: 0 25px 25px 0; 111 | border: 2px solid #e2ddde; 112 | border-left: none; 113 | background: #2d8cf0; 114 | outline: none; 115 | transition: all .2s; 116 | margin-top: 10px; 117 | &:hover{ 118 | background: #5cadff; 119 | box-shadow: 0 0 10px 3px rgba(255, 255, 255, .2); 120 | } 121 | } 122 | .click-unlock-btn{ 123 | background: #2b85e4 !important; 124 | } 125 | } 126 | } 127 | } 128 | .unlock-locking-tip-con{ 129 | width: 100px; 130 | height: 30px; 131 | text-align: center; 132 | position: absolute; 133 | left: 50%; 134 | margin-left: -50px; 135 | bottom: -80px; 136 | color: white; 137 | font-size: 18px; 138 | } 139 | } 140 | @keyframes unlock-in{ 141 | 0% { 142 | transform: scale(0); 143 | } 144 | 80%{ 145 | transform: scale(0); 146 | } 147 | 88% { 148 | transform: scale(1.3); 149 | } 150 | 100% { 151 | transform: scale(1); 152 | } 153 | } 154 | @keyframes unlock-out{ 155 | 0% { 156 | transform: scale(1); 157 | } 158 | 60%{ 159 | transform: scale(1.2); 160 | } 161 | 100% { 162 | transform: scale(0); 163 | } 164 | } 165 | .show-unlock-enter-active{ 166 | animation: unlock-in 1.4s ease; 167 | } 168 | .show-unlock-leave-to{ 169 | opacity: 0; 170 | } 171 | .show-unlock-leave-active{ 172 | transition: opacity .2s; 173 | } 174 | .unlock-con{ 175 | width: 100%; 176 | height: 100%; 177 | } -------------------------------------------------------------------------------- /client/src/views/example/table/components/table_data.js: -------------------------------------------------------------------------------- 1 | export const table1Columns = [ 2 | { 3 | title: '序号', 4 | type: 'index', 5 | width: 80, 6 | align: 'center' 7 | }, 8 | { 9 | title: '姓名', 10 | align: 'center', 11 | key: 'name', 12 | editable: true 13 | }, 14 | { 15 | title: '性别', 16 | align: 'center', 17 | key: 'sex' 18 | }, 19 | { 20 | title: '岗位', 21 | align: 'center', 22 | key: 'work', 23 | editable: true 24 | }, 25 | { 26 | title: '操作', 27 | align: 'center', 28 | width: 120, 29 | key: 'handle', 30 | handle: ['delete'] 31 | } 32 | ]; 33 | 34 | export const table1Data = [ 35 | { 36 | name: 'Aresn', 37 | sex: '男', 38 | work: '前端开发' 39 | }, 40 | { 41 | name: 'Lison', 42 | sex: '男', 43 | work: '前端开发' 44 | }, 45 | { 46 | name: 'lisa', 47 | sex: '女', 48 | work: '程序员鼓励师' 49 | } 50 | ]; 51 | 52 | export const editInlineColumns = [ 53 | { 54 | title: '序号', 55 | type: 'index', 56 | width: 80, 57 | align: 'center' 58 | }, 59 | { 60 | title: '姓名', 61 | align: 'center', 62 | key: 'name', 63 | width: 90, 64 | editable: true 65 | }, 66 | { 67 | title: '性别', 68 | align: 'center', 69 | key: 'sex' 70 | }, 71 | { 72 | title: '岗位', 73 | align: 'center', 74 | key: 'work', 75 | width: 150, 76 | editable: true 77 | }, 78 | { 79 | title: '操作', 80 | align: 'center', 81 | width: 190, 82 | key: 'handle', 83 | handle: ['edit', 'delete'] 84 | } 85 | ]; 86 | 87 | export const editInlineData = [ 88 | { 89 | name: 'Aresn', 90 | sex: '男', 91 | work: '前端开发' 92 | }, 93 | { 94 | name: 'Lison', 95 | sex: '男', 96 | work: '前端开发' 97 | }, 98 | { 99 | name: 'lisa', 100 | sex: '女', 101 | work: '程序员鼓励师' 102 | } 103 | ]; 104 | 105 | export const editIncellColumns = [ 106 | { 107 | title: '序号', 108 | type: 'index', 109 | width: 80, 110 | align: 'center' 111 | }, 112 | { 113 | title: '姓名', 114 | align: 'center', 115 | key: 'name', 116 | width: 120, 117 | editable: true 118 | }, 119 | { 120 | title: '性别', 121 | align: 'center', 122 | key: 'sex' 123 | }, 124 | { 125 | title: '岗位', 126 | align: 'center', 127 | width: 160, 128 | key: 'work', 129 | editable: true 130 | }, 131 | { 132 | title: '操作', 133 | align: 'center', 134 | width: 120, 135 | key: 'handle', 136 | handle: ['delete'] 137 | } 138 | ]; 139 | 140 | export const editIncellData = [ 141 | { 142 | name: 'Aresn', 143 | sex: '男', 144 | work: '前端开发' 145 | }, 146 | { 147 | name: 'Lison', 148 | sex: '男', 149 | work: '前端开发' 150 | }, 151 | { 152 | name: 'lisa', 153 | sex: '女', 154 | work: '程序员鼓励师' 155 | } 156 | ]; 157 | 158 | export const editInlineAndCellColumn = [ 159 | { 160 | title: '序号', 161 | type: 'index', 162 | width: 80, 163 | align: 'center' 164 | }, 165 | { 166 | title: '姓名', 167 | align: 'center', 168 | key: 'name', 169 | width: 300, 170 | editable: true 171 | }, 172 | { 173 | title: '性别', 174 | align: 'center', 175 | key: 'sex' 176 | }, 177 | { 178 | title: '岗位', 179 | align: 'center', 180 | width: 300, 181 | key: 'work', 182 | editable: true 183 | }, 184 | { 185 | title: '操作', 186 | align: 'center', 187 | width: 200, 188 | key: 'handle', 189 | handle: ['edit', 'delete'] 190 | } 191 | ]; 192 | 193 | export const editInlineAndCellData = [ 194 | { 195 | name: 'Aresn', 196 | sex: '男', 197 | work: '前端开发' 198 | }, 199 | { 200 | name: 'Lison', 201 | sex: '男', 202 | work: '前端开发' 203 | }, 204 | { 205 | name: 'lisa', 206 | sex: '女', 207 | work: '程序员鼓励师' 208 | } 209 | ]; 210 | 211 | export const showCurrentColumns = [ 212 | { 213 | title: '序号', 214 | type: 'index', 215 | width: 80, 216 | align: 'center' 217 | }, 218 | { 219 | title: '姓名', 220 | align: 'center', 221 | key: 'name', 222 | width: 300, 223 | editable: true 224 | }, 225 | { 226 | title: '性别', 227 | align: 'center', 228 | key: 'sex' 229 | }, 230 | { 231 | title: '岗位', 232 | align: 'center', 233 | width: 300, 234 | key: 'work', 235 | editable: true 236 | } 237 | ]; 238 | 239 | const tableData = { 240 | table1Columns: table1Columns, 241 | table1Data: table1Data, 242 | editInlineColumns: editInlineColumns, 243 | editInlineData: editInlineData, 244 | editIncellColumns: editIncellColumns, 245 | editIncellData: editIncellData, 246 | editInlineAndCellColumn: editInlineAndCellColumn, 247 | editInlineAndCellData: editInlineAndCellData, 248 | showCurrentColumns: showCurrentColumns 249 | }; 250 | 251 | export default tableData; 252 | -------------------------------------------------------------------------------- /client/src/views/example/table/exportable-table.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 71 | 72 | 125 | --------------------------------------------------------------------------------