├── 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 |
6 |
7 | 角色列表
8 |
9 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
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 |
2 |
6 |
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 | {{ itemTitle(item) }}
8 |
9 |
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 |
2 |
5 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{ content }}
15 |
16 |
17 |
18 |
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 |
6 |
7 |
8 |
9 | 4 4
10 | YOU LOOK LOST
11 |
12 | 返回首页
13 | 返回上一页
14 |
15 |
16 |
17 |
18 |
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 |
6 |
7 |
8 |
9 | 4
10 | You don't have permission
11 |
12 | 返回首页
13 | 返回上一页
14 |
15 |
16 |
17 |
18 |
19 |
20 |
35 |
--------------------------------------------------------------------------------
/client/src/views/error-page/500.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 5
11 |
12 | Oops! the server is wrong
13 |
14 | 返回首页
15 | 返回上一页
16 |
17 |
18 |
19 |
20 |
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 |
2 |
11 |
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 |
6 |
7 |
8 |
9 | 职业:
10 | {{ row.job }}
11 |
12 |
13 | 兴趣:
14 | {{ row.interest }}
15 |
16 |
17 | 生日:
18 | {{ row.birthday }}
19 |
20 |
21 |
22 |
23 | 最喜欢的书:
24 | 《{{ row.book }}》
25 |
26 |
27 | 最喜欢的电影:
28 | {{ row.movie }}
29 |
30 |
31 | 最喜欢的音乐:
32 | {{ row.music }}
33 |
34 |
35 |
36 |
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 |
2 |
7 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 | {{ introText }}
24 |
25 |
26 |
27 |
28 |
29 |
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 |
2 |
3 |
4 |
5 |
59 |
--------------------------------------------------------------------------------
/client/src/views/advanced-router/component/shopping-info.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 订单详情
13 |
14 |
15 |
16 |
17 |
18 |
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 |
2 |
3 |
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: ''
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 |
6 |
23 |
24 |
25 |
84 |
--------------------------------------------------------------------------------
/client/src/views/main-components/shrinkable-menu/components/sidebarMenuShrink.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{ itemTitle(child) }}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {{ itemTitle(item.children[0]) }}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
58 |
--------------------------------------------------------------------------------
/client/src/views/main-components/lockscreen/lockscreen.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
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 |
2 |
3 |
4 |
5 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/client/src/views/home/components/visiteVolume.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
77 |
--------------------------------------------------------------------------------
/client/src/views/main-components/fullscreen.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
75 |
--------------------------------------------------------------------------------
/client/src/views/advanced-router/mutative-router.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 订单详情(动态路由)
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
93 |
--------------------------------------------------------------------------------
/client/src/views/main-components/lockscreen/components/unlock.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
24 |
已锁定
25 |
26 |
27 |
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 |
6 |
7 |
8 |
9 |
10 |
11 | 购物记录(传递参数)
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
95 |
--------------------------------------------------------------------------------
/client/src/views/home/components/countUp.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ startVal }} {{ unit }}
4 |
5 |
6 |
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 |
6 |
7 |
8 |
9 |
10 | {{ itemTitle(item.children[0]) }}
11 |
12 |
13 |
14 |
15 |
16 | {{ itemTitle(item) }}
17 |
18 |
19 |
20 |
21 | {{ itemTitle(child) }}
22 |
23 |
24 |
25 |
26 | {{ itemTitle(child) }}
27 |
28 |
29 |
30 |
31 | {{ itemTitle(childItem) }}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
77 |
--------------------------------------------------------------------------------
/client/src/views/home/components/serviceRequests.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/client/src/views/error-page/error-page.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 404-页面不存在
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 当访问的页面不存在时会跳转到404页面,您可以在浏览器地址栏中修改url为一个不存在的路径,体验一下效果
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 403-权限不足
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | 在当前登录用户不具有执行当前操作的权限时跳转到该页面,您可以在ajax请求方法中判断返回的状态码为403时跳转到该页面
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 500-服务端错误
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | 当请求之后出现服务端错误时跳转到该页面,您可以在ajax请求方法中判断返回的状态码为500时跳转到该页面
78 |
79 |
80 |
81 |
82 |
83 |
84 |
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 = ' ';
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 |
6 |
7 |
8 |
9 |
10 |
11 | 欢迎登录
12 |
13 |
35 |
36 |
37 |
38 |
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 |
2 |
10 |
11 |
112 |
--------------------------------------------------------------------------------
/client/src/views/example/table/searchable-table.vue:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
56 |
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 |
7 |
8 |
9 |
10 |
11 |
12 | 导出表格数据到 .Csv 文件
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 导出原始数据
21 | 导出排序和过滤后的数据
22 |
23 |
24 |
25 | 选取行范围:
26 | -
27 |
28 |
29 |
30 | 选取列范围:
31 | -
32 |
33 |
34 |
35 | 输入文件名:
36 |
37 |
38 |
39 | 导出自定义数据
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 导出表格数据到 .Xls 文件 (Excel)
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | 输入文件名:
59 |
60 |
61 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
125 |
--------------------------------------------------------------------------------