├── .gitignore ├── APP ├── app.js ├── package-lock.json ├── package.json ├── pool.js ├── router │ ├── admin.js │ └── tests.js ├── utils │ ├── err.js │ ├── index.js │ ├── menuString.js │ └── roleString.js └── 数据库 │ └── vue_admin.sql ├── LICENSE ├── README.md └── item ├── .env.development ├── .env.production ├── .env.staging ├── babel.config.js ├── build └── index.js ├── jest.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── admin │ │ └── index.js │ └── tests │ │ └── index.js ├── assets │ ├── 401_images │ │ └── 401.gif │ ├── 404_images │ │ ├── 404.png │ │ └── 404_cloud.png │ ├── custom-theme │ │ ├── fonts │ │ │ ├── element-icons.ttf │ │ │ └── element-icons.woff │ │ └── index.css │ └── images │ │ ├── gitee.png │ │ ├── github.png │ │ ├── node.png │ │ ├── qq.png │ │ ├── star.png │ │ └── vue.png ├── components │ ├── Breadcrumb │ │ └── index.vue │ ├── ErrorLog │ │ └── index.vue │ ├── Hamburger │ │ └── index.vue │ ├── HeaderSearch │ │ └── index.vue │ ├── IconSelect │ │ ├── index.vue │ │ └── requireIcons.js │ ├── ImageCropper │ │ ├── index.vue │ │ └── utils │ │ │ ├── data2blob.js │ │ │ ├── effectRipple.js │ │ │ ├── language.js │ │ │ └── mimes.js │ ├── LangSelect │ │ └── index.vue │ ├── Pagination │ │ └── index.vue │ ├── RightPanel │ │ └── index.vue │ ├── Screenfull │ │ └── index.vue │ ├── SizeSelect │ │ └── index.vue │ ├── Sticky │ │ └── index.vue │ ├── SvgIcon │ │ └── index.vue │ ├── ThemePicker │ │ └── index.vue │ └── parentView │ │ └── index.vue ├── filters │ └── index.js ├── icons │ ├── index.js │ ├── svg │ │ ├── 404.svg │ │ ├── bug.svg │ │ ├── chart.svg │ │ ├── clipboard.svg │ │ ├── component.svg │ │ ├── dashboard.svg │ │ ├── documentation.svg │ │ ├── drag.svg │ │ ├── edit.svg │ │ ├── education.svg │ │ ├── email.svg │ │ ├── example.svg │ │ ├── excel.svg │ │ ├── exit-fullscreen.svg │ │ ├── eye-open.svg │ │ ├── eye.svg │ │ ├── form.svg │ │ ├── fullscreen.svg │ │ ├── guide.svg │ │ ├── icon.svg │ │ ├── international.svg │ │ ├── language.svg │ │ ├── link.svg │ │ ├── list.svg │ │ ├── lock.svg │ │ ├── menu.svg │ │ ├── message.svg │ │ ├── money.svg │ │ ├── nested.svg │ │ ├── password.svg │ │ ├── pdf.svg │ │ ├── people.svg │ │ ├── peoples.svg │ │ ├── qq.svg │ │ ├── role.svg │ │ ├── search.svg │ │ ├── shopping.svg │ │ ├── size.svg │ │ ├── skill.svg │ │ ├── star.svg │ │ ├── tab.svg │ │ ├── table.svg │ │ ├── theme.svg │ │ ├── tree-table.svg │ │ ├── tree.svg │ │ ├── user.svg │ │ ├── wechat.svg │ │ └── zip.svg │ └── svgo.yml ├── lang │ ├── en.js │ ├── es.js │ ├── index.js │ ├── ja.js │ └── zh.js ├── layout │ ├── components │ │ ├── AppMain.vue │ │ ├── Navbar.vue │ │ ├── Settings │ │ │ └── index.vue │ │ ├── Sidebar │ │ │ ├── FixiOSBug.js │ │ │ ├── Item.vue │ │ │ ├── Link.vue │ │ │ ├── Logo.vue │ │ │ ├── SidebarItem.vue │ │ │ └── index.vue │ │ ├── TagsView │ │ │ ├── ScrollPane.vue │ │ │ └── index.vue │ │ └── index.js │ ├── index.vue │ └── mixin │ │ └── ResizeHandler.js ├── main.js ├── permission.js ├── router │ └── index.js ├── settings.js ├── store │ ├── getters.js │ ├── index.js │ └── modules │ │ ├── app.js │ │ ├── errorLog.js │ │ ├── permission.js │ │ ├── settings.js │ │ ├── tagsView.js │ │ └── user.js ├── styles │ ├── btn.scss │ ├── element-ui.scss │ ├── element-variables.scss │ ├── index.scss │ ├── mixin.scss │ ├── sidebar.scss │ ├── transition.scss │ └── variables.scss ├── utils │ ├── auth.js │ ├── axios.js │ ├── clipboard.js │ ├── directive.js │ ├── error-log.js │ ├── get-page-title.js │ ├── i18n.js │ ├── index.js │ ├── open-window.js │ ├── permission.js │ ├── scroll-to.js │ └── validate.js └── views │ ├── admin │ ├── dict.vue │ ├── dictItem.vue │ ├── menu.vue │ ├── more.vue │ ├── role.vue │ └── user.vue │ ├── dashboard │ └── index.vue │ ├── error-log │ ├── components │ │ ├── ErrorTestA.vue │ │ └── ErrorTestB.vue │ └── index.vue │ ├── error-page │ ├── 401.vue │ └── 404.vue │ ├── icons │ ├── element-icons.js │ ├── index.vue │ └── svg-icons.js │ ├── login │ ├── auth-redirect.vue │ ├── components │ │ └── SocialSignin.vue │ └── index.vue │ ├── redirect │ └── index.vue │ └── test │ ├── index.vue │ └── roleApi.vue └── vue.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | logs/ -------------------------------------------------------------------------------- /APP/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyparser = require('body-parser'); //body中间件 3 | const testsRouter=require("./router/tests.js");//测试信息路由 4 | const adminRouter = require('./router/admin.js'); //管理菜单等路由 5 | const cors = require('cors'); //解决跨域的中间件 6 | const server = express(); 7 | const {errLog}= require("./utils/err"); 8 | const utils = require("./utils/index.js"); 9 | server.listen(3000); 10 | server.use(cors({origin: "*",})); 11 | server.use(express.static('./public')); //用户的静态资源 12 | server.use(bodyparser.json()); 13 | // server.use(bodyparser.urlencoded({//body中间件 14 | // extended:false 15 | // })); 16 | server.use(async function (req, res, next) { 17 | if(req.headers.token){ 18 | let user=await utils.getUserInfo(req,res); 19 | if(user.status===0) return res.send(utils.returnData({code: 203, msg: "你账号已被禁用,请联系管理员!!",req})); 20 | } 21 | next(); 22 | }) 23 | process.on('unhandledRejection', (err, test) => { 24 | errLog({err,code:500,msg:"后端系统错误!",funName:"fatal"}); 25 | }).on('uncaughtException', err => { 26 | errLog({err,code:500,msg:"后端系统错误!!",funName:"fatal"}); 27 | }); 28 | 29 | server.use('/admin', adminRouter); //挂载用户信息路由 30 | server.use("/tests",testsRouter);//挂载测试信息路由 31 | console.log('后端接口启动成功'); 32 | -------------------------------------------------------------------------------- /APP/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ming", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve --port 8080 --host 127.0.0.1", 7 | "build": "vue-cli-service build", 8 | "dev": "nodemon ./app.js" 9 | }, 10 | "dependencies": { 11 | "body-parser": "^1.19.0", 12 | "cors": "^2.8.5", 13 | "cos-nodejs-sdk-v5": "^2.5.20", 14 | "express": "^4.17.1", 15 | "express-session": "^1.16.1", 16 | "fs": "0.0.1-security", 17 | "jsonwebtoken": "^8.5.1", 18 | "koa": "^2.13.4", 19 | "koa-body": "^4.1.1", 20 | "log4js": "^6.9.1", 21 | "md5": "^2.2.1", 22 | "multer": "^1.4.2", 23 | "mysql": "^2.17.1", 24 | "nodemon": "^2.0.2", 25 | "request": "^2.88.2" 26 | }, 27 | "devDependencies": {}, 28 | "postcss": { 29 | "plugins": { 30 | "autoprefixer": {} 31 | } 32 | }, 33 | "browserslist": [ 34 | "> 1%", 35 | "last 2 versions", 36 | "not ie <= 8" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /APP/pool.js: -------------------------------------------------------------------------------- 1 | const mysql=require("mysql"); 2 | const pool=mysql.createPool({ 3 | host:"127.0.0.1", 4 | port:3306, 5 | user:"root", 6 | password:"root", 7 | database:"vue_admin", 8 | connectionLimit:15 9 | }); 10 | module.exports=pool; 11 | -------------------------------------------------------------------------------- /APP/router/tests.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const pool = require('../pool.js'); 4 | const utils = require("../utils/index.js"); 5 | const {primary}=require("../utils/roleString"); 6 | //添加测试账号 7 | router.post("/addTests", async (req, res) => { 8 | let sql = "INSERT INTO tests(name,remark,more_id) VALUES (?,?,?)", 9 | obj = req.body; 10 | let user = await utils.getUserInfo(req, res); 11 | if(user.admin==1) return res.send(utils.returnData({ code:-1,msg:"终极管理员无权 增加多账号数据~" })); 12 | pool.query(sql, [obj.name, obj.remark,user.moreId], (err, result) => { 13 | if (err) return res.send(utils.returnData({ code: -1 ,err,req})); 14 | res.send(utils.returnData({ data: result })); 15 | }); 16 | }); 17 | //查询测试账号 18 | router.post("/getTests", async (req, res) => { 19 | let user = await utils.getUserInfo(req, res),obj=req.body; 20 | let {page,size}=utils.pageSize(obj.page,obj.size); 21 | //如果不是终极管理,要根据多账号进行取 22 | let sql = `SELECT id,name,remark FROM tests WHERE name LIKE "%${obj.name||''}%" ${user.admin?"":"AND more_id="+user.moreId} ORDER BY id DESC LIMIT ?,?`; 23 | let {total}=await utils.getSum({name:"tests",where:`WHERE name LIKE "%${obj.name||''}%" ${user.admin?"":"AND more_id="+user.moreId}`,res,req}); 24 | pool.query(sql,[page,size],(err, result) => { 25 | if (err) return res.send(utils.returnData({ code: -1 ,err,req})); 26 | res.send(utils.returnData({ data: result ,total})); 27 | }); 28 | }); 29 | 30 | //修改测试账号 31 | router.post("/upTests", async (req, res) => { 32 | let sql = "UPDATE tests SET name=?,remark=? WHERE id=?", 33 | obj = req.body; 34 | pool.query(sql, [obj.name, obj.remark, obj.id], (err, result) => { 35 | if (err) return res.send(utils.returnData({ code: -1 ,err,req})); 36 | res.send(utils.returnData({ data: result })); 37 | }); 38 | }); 39 | 40 | //删除测试账号 41 | router.post("/delTests", async (req, res) => { 42 | let sql = "DELETE FROM tests WHERE id=?", 43 | obj = req.body; 44 | pool.query(sql, [obj.id], (err, result) => { 45 | if (err) return res.send(utils.returnData({ code: -1 ,err,req})); 46 | res.send(utils.returnData({ data: result })); 47 | }); 48 | }); 49 | 50 | //测试菜单接口权限 51 | router.post("/checkMenu",async (req,res)=>{ 52 | //需要有roleKey1 这个菜单权限才能请求! 53 | await utils.checkPermi({role:["roleKey1"],res,req}); 54 | res.send(utils.returnData({data:{msg:"请求成功了!"}})); 55 | }); 56 | //测试角色接口权限 57 | router.post("/checkRole",async (req,res)=>{ 58 | //需要有primary 这个角色权限才能请求! 59 | await utils.checkRole({req,res,role:[primary]}); 60 | res.send(utils.returnData({data:{msg:"请求成功了!"}})); 61 | }); 62 | module.exports = router; 63 | -------------------------------------------------------------------------------- /APP/utils/err.js: -------------------------------------------------------------------------------- 1 | const log4js = require("log4js"); 2 | module.exports={ 3 | /** 4 | * 记录错误信息日志 5 | * @param object err 错误信息 6 | * @param number code 状态值 7 | * @param string msg 提示语 8 | * @param string funName 等级函数名:trace debug info warn error fatal 9 | * @param object req req主体 10 | * **/ 11 | errLog({err,code,msg,funName="error",req={}}){ 12 | log4js.configure({ 13 | appenders:{ 14 | console:{ 15 | type:'dateFile', 16 | filename:`./logs/${funName}.log`, 17 | pattern:'yyyy-MM-dd', 18 | // pattern:'yyyy-MM-dd-hh-mm-ss', 19 | keepFileExt: true, 20 | alwaysIncludePattern: true, 21 | daysToKeep: 14,//保留时间(天) 22 | layout:{ 23 | type: "pattern", 24 | pattern:'{"date":"%d{yyyy-MM-dd hh:mm:ss}","type":"%p","data":"%m"} %n' 25 | } 26 | }, 27 | }, 28 | categories:{ 29 | default: { appenders: ["console"], level: "all" }, 30 | } 31 | }); 32 | let init=log4js.getLogger(); 33 | try { 34 | let data={err,code,msg,portName:req.originalUrl,body:req.body,query:req.query,method:req.method}; 35 | init[funName](data); 36 | }catch (e) { 37 | init[funName]("错误日志--记录错误:"+e); 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /APP/utils/menuString.js: -------------------------------------------------------------------------------- 1 | module.exports ={ 2 | systemSettings:{ 3 | menus:{ 4 | menuQuery:"menu_query", 5 | menuAdd:"menu_add", 6 | menuUp:"menu_up", 7 | menuDelte:"menu_delete", 8 | }, 9 | user:{ 10 | userQuery:"user_query", 11 | userAdd:"user_add", 12 | userUp:"user_up", 13 | userDelte:"user_delete", 14 | userPwd:"user_pwd", 15 | }, 16 | role:{ 17 | roleQuery:"role_query", 18 | roleAdd:"role_add", 19 | roleUp:"role_up", 20 | roleDelte:"role_delete" 21 | }, 22 | more:{ 23 | moreQuery:"more_query", 24 | moreAdd:"more_add", 25 | moreUp:"more_up", 26 | moreDelte:"more_delete" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /APP/utils/roleString.js: -------------------------------------------------------------------------------- 1 | module.exports ={ 2 | primary:"primary", 3 | middle:"middle" 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 小陈 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 最新消息:vue3版本已经开发完成!点击获取最新vue3源码 2 | 3 | 4 | 5 | ### 最新升级版已完成,预览地址:点击进入效果预览 6 | 7 | ### 升级新功能如下: 8 | 9 | 1. 整体进行优化代码,使得更小更容易上手。 10 | 2. 加入登陆图形验证码验证,升级密码加密难度。 11 | 3. 加入下载模板、导入、导出功能。 12 | 4. 加入上传图片、上传文件(视频、音频等其他格式文件)。 13 | 5. 加入个人信息管理页面。 14 | 6. 加入富文本编辑功能。 15 | 7. 加入大屏统计echarts展示功能。 16 | 17 | 18 | 19 | ### **目前仓库是旧版源码(不建议继续使用),升级版源码请:**点击免费获取vue2升级版源码 20 | 21 | ------ 22 | 23 | 24 | 25 | ## 一、旧版项目预览地址 26 | 27 | - #### vue+node后台管理旧版预览地址已失效 28 | 29 | - #### 请预览升级版:点击进入效果预览 30 | 31 | - 默认账号:admin 32 | 33 | - 默认密码:666666 34 | 35 | 36 | 37 | 38 | ## 二、交流群、问题解答入口 39 | 40 | - #### 点击加入 问题解答交流社区 41 | 42 | ## 三、项目启动 43 | 44 | 45 | 46 | - ### 前端页面启动 47 | 48 | 1. ##### #克隆项目 49 | 50 | ``` 51 | git https://github.com/MingMinter/vue_node_admin.git 52 | ``` 53 | 54 | 2. ##### #进入目录 55 | 56 | ``` 57 | cd item 58 | ``` 59 | 60 | 3. ##### #安装依赖(建议使用node版本16以下 10以上) 61 | 62 | ``` 63 | npm install 64 | ``` 65 | 66 | 4. ##### #启动项目 67 | 68 | ``` 69 | npm run dev 70 | ``` 71 | 72 | 73 | 74 | 75 | - ### 数据库导入 76 | 77 | 1. ##### #创建数据库 78 | 79 | 1. 创建数据库 名:**vue_admin** 80 | 2. 基字符集 选:默认(default) 或者 utf8 81 | 3. 排序规则 选:默认(default) 或者 utf8_general_ci 82 | 83 | 2. ##### #导入数据库 84 | 85 | 1. 将APP/数据库 文件夹的vue_admin.sql导入新建的vue_admin库中。 86 | 87 | 3. ##### #数据库配置信息 88 | 89 | ``` 90 | //配置文件在APP/poo.js 中,请根据自身环境配置 91 | const pool=mysql.createPool({ 92 | host:"127.0.0.1", 93 | port:3306,//端口 94 | user:"root",//账户名 95 | password:"root",//登录密码 96 | database:"vue_admin",//数据库名称 97 | connectionLimit:15 98 | }); 99 | ``` 100 | 101 | 102 | 103 | 104 | 105 | - ### 后端服务启动 106 | 107 | 108 | 1. ##### #克隆项目(如果已经执行了前端页面启动的克隆,可略过这点) 109 | 110 | ``` 111 | git clone https://github.com/MingMinter/vue_node_admin.git 112 | ``` 113 | 114 | 2. ##### #进入目录 115 | 116 | ``` 117 | cd APP 118 | ``` 119 | 120 | 3. ##### #安装依赖 121 | 122 | ``` 123 | npm install 124 | ``` 125 | 126 | 4. ##### #启动服务 127 | 128 | ``` 129 | npm run dev 130 | ``` 131 | 132 | 133 | 134 | 135 | 136 | ## 四、功能简明 137 | 138 | - #### 前端功能 (点击进入前端功能详细文档) 139 | 140 | > - [x] 141 | > ​ ***动态路由*** 142 | > - [x] ​ ***菜单管理*** 143 | > - [x] ​ ***用户管理*** 144 | > - [x] ​ ***角色管理*** 145 | > - [x] ​ ***多账号管理*** 146 | > - [x] ​ ***字典管理*** 147 | > - [x] ​ ***主题自定义*** 148 | > - [x] ​ ***菜单权限*** 149 | > - [x] ​ ***角色权限*** 150 | 151 | 152 | 153 | - #### 后端功能 (点击进入后端功能详细文档) 154 | 155 | > - [x] 156 | > ​ ***jsonwebtoken(token)*** 157 | > - [x] ​ ***菜单权限拦截*** 158 | > - [x] ​ ***角色权限拦截*** 159 | > - [x] ​ ***错误日志log`*** 160 | 161 | 162 | 163 | ## 五、效果截图 164 | 165 | ![登录](https://foruda.gitee.com/images/1681197680240561436/dfbf1881_8986810.png "登录.png") 166 | | ![用户管理](https://foruda.gitee.com/images/1681197937191145993/c434e92e_8986810.png "用户管理.png") | ![主题修改](https://foruda.gitee.com/images/1681263456631597583/3077263f_8986810.png "主题修改.png") | 167 | | ------------------------------------------------------------ | ------------------------------------------------------------ | 168 | | ![菜单管理](https://foruda.gitee.com/images/1681197808163981644/96f27575_8986810.png "菜单管理.png") | ![菜单修改](https://foruda.gitee.com/images/1681197825348384952/281da67c_8986810.png "菜单修改.png") | 169 | | ![角色管理](https://foruda.gitee.com/images/1681197773630264222/4ec415e3_8986810.png "角色管理.png") | ![字典管理](https://foruda.gitee.com/images/1681197948454663203/788cbd7e_8986810.png "字典管理.png") | 170 | ------ 171 | 172 | ------ 173 | 174 | ------ 175 | 176 | ## vue3.0支持名单!点击浏览 -------------------------------------------------------------------------------- /item/.env.development: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'development' 3 | 4 | # base api 5 | VUE_APP_BASE_API = 'http://127.0.0.1:3000' 6 | -------------------------------------------------------------------------------- /item/.env.production: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'production' 3 | 4 | # base api 5 | VUE_APP_BASE_API = 'http://127.0.0.1:3000' 6 | 7 | -------------------------------------------------------------------------------- /item/.env.staging: -------------------------------------------------------------------------------- 1 | NODE_ENV = production 2 | 3 | # just a flag 4 | ENV = 'staging' 5 | 6 | # base api 7 | VUE_APP_BASE_API = 'http://127.0.0.1:3000' 8 | 9 | -------------------------------------------------------------------------------- /item/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app 4 | '@vue/cli-plugin-babel/preset' 5 | ], 6 | 'env': { 7 | 'development': { 8 | // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). 9 | // This plugin can significantly increase the speed of hot updates, when you have a large number of pages. 10 | // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html 11 | 'plugins': ['dynamic-import-node'] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /item/build/index.js: -------------------------------------------------------------------------------- 1 | const { run } = require('runjs') 2 | const chalk = require('chalk') 3 | const config = require('../vue.config.js') 4 | const rawArgv = process.argv.slice(2) 5 | const args = rawArgv.join(' ') 6 | 7 | if (process.env.npm_config_preview || rawArgv.includes('--preview')) { 8 | const report = rawArgv.includes('--report') 9 | 10 | run(`vue-cli-service build ${args}`) 11 | 12 | const port = 9526 13 | const publicPath = config.publicPath 14 | 15 | var connect = require('connect') 16 | var serveStatic = require('serve-static') 17 | const app = connect() 18 | 19 | app.use( 20 | publicPath, 21 | serveStatic('./dist', { 22 | index: ['index.html', '/'] 23 | }) 24 | ) 25 | 26 | app.listen(port, function () { 27 | console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) 28 | if (report) { 29 | console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) 30 | } 31 | 32 | }) 33 | } else { 34 | run(`vue-cli-service build ${args}`) 35 | } 36 | -------------------------------------------------------------------------------- /item/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 6 | 'jest-transform-stub', 7 | '^.+\\.jsx?$': 'babel-jest' 8 | }, 9 | moduleNameMapper: { 10 | '^@/(.*)$': '/src/$1' 11 | }, 12 | snapshotSerializers: ['jest-serializer-vue'], 13 | testMatch: [ 14 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 15 | ], 16 | collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'], 17 | coverageDirectory: '/tests/unit/coverage', 18 | // 'collectCoverage': true, 19 | 'coverageReporters': [ 20 | 'lcov', 21 | 'text-summary' 22 | ], 23 | testURL: 'http://localhost/' 24 | } 25 | -------------------------------------------------------------------------------- /item/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } -------------------------------------------------------------------------------- /item/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue_node", 3 | "version": "4.3.1", 4 | "description": "vue+node后台管理系统", 5 | "author": "XiaoMing", 6 | "scripts": { 7 | "dev": "vue-cli-service serve", 8 | "build:prod": "vue-cli-service build", 9 | "build:stage": "vue-cli-service build --mode staging" 10 | }, 11 | "dependencies": { 12 | "@riophae/vue-treeselect": "^0.4.0", 13 | "axios": "^0.18.1", 14 | "clipboard": "2.0.4", 15 | "core-js": "3.6.5", 16 | "dropzone": "5.5.1", 17 | "echarts": "4.2.1", 18 | "element-ui": "^2.15.13", 19 | "fuse.js": "3.4.4", 20 | "js-cookie": "2.2.0", 21 | "js-md5": "^0.7.3", 22 | "normalize.css": "7.0.0", 23 | "nprogress": "0.2.0", 24 | "path-to-regexp": "2.4.0", 25 | "pinyin": "2.9.0", 26 | "screenfull": "4.2.0", 27 | "vue": "2.6.10", 28 | "vue-i18n": "7.3.2", 29 | "vue-router": "3.0.2", 30 | "vuex": "3.1.0" 31 | }, 32 | "devDependencies": { 33 | "@vue/cli-plugin-babel": "4.4.4", 34 | "@vue/cli-plugin-eslint": "4.4.4", 35 | "@vue/cli-plugin-unit-jest": "4.4.4", 36 | "@vue/cli-service": "4.4.4", 37 | "@vue/test-utils": "1.0.0-beta.29", 38 | "autoprefixer": "9.5.1", 39 | "babel-eslint": "10.1.0", 40 | "babel-jest": "23.6.0", 41 | "babel-plugin-dynamic-import-node": "2.3.3", 42 | "chalk": "2.4.2", 43 | "chokidar": "2.1.5", 44 | "connect": "3.6.6", 45 | "eslint": "6.7.2", 46 | "eslint-plugin-vue": "6.2.2", 47 | "html-webpack-plugin": "3.2.0", 48 | "husky": "1.3.1", 49 | "lint-staged": "8.1.5", 50 | "mockjs": "1.0.1-beta3", 51 | "plop": "2.3.0", 52 | "runjs": "4.3.2", 53 | "sass": "1.26.2", 54 | "sass-loader": "8.0.2", 55 | "script-ext-html-webpack-plugin": "2.1.3", 56 | "serve-static": "1.13.2", 57 | "svg-sprite-loader": "4.1.3", 58 | "svgo": "1.2.0", 59 | "vue-template-compiler": "2.6.10" 60 | }, 61 | "browserslist": [ 62 | "> 1%", 63 | "last 2 versions" 64 | ], 65 | "bugs": { 66 | "url": "https://gitee.com/MMinter/vue_node/issues" 67 | }, 68 | "engines": { 69 | "node": ">=8.9", 70 | "npm": ">= 3.0.0" 71 | }, 72 | "keywords": [ 73 | "vue", 74 | "admin", 75 | "dashboard", 76 | "element-ui", 77 | "boilerplate", 78 | "admin-template", 79 | "management-system" 80 | ], 81 | "license": "MIT", 82 | "lint-staged": { 83 | "src/**/*.{js,vue}": [ 84 | "eslint --fix", 85 | "git add" 86 | ] 87 | }, 88 | "husky": { 89 | "hooks": { 90 | "pre-commit": "lint-staged" 91 | } 92 | }, 93 | "repository": { 94 | "type": "git", 95 | "url": "https://gitee.com/MMinter/vue_node" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /item/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /item/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MingMinter/vue_node_admin/521616c3e299ea24c4abe4a464f0960f71a3f21e/item/public/favicon.ico -------------------------------------------------------------------------------- /item/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= webpackConfig.name %> 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /item/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 17 | -------------------------------------------------------------------------------- /item/src/api/admin/index.js: -------------------------------------------------------------------------------- 1 | import request from "@/utils/axios"; 2 | 3 | export function getRouter(data) { 4 | return request({ 5 | path: "/admin/getRouter", 6 | data 7 | }); 8 | } 9 | export function getRouterSystem(data) { 10 | return request({ 11 | path: "/admin/getRouterSystem", 12 | data 13 | }); 14 | } 15 | export function getUserInfo(data) { 16 | return request({ 17 | path: "/admin/getUserInfo", 18 | }); 19 | } 20 | export function getRoles(data) { 21 | return request({ 22 | path: "/admin/getRoles", 23 | data 24 | }); 25 | } 26 | export function getRolesAll(data) { 27 | return request({ 28 | path: "/admin/getRolesAll", 29 | data 30 | }); 31 | } 32 | export function addRoles(data) { 33 | return request({ 34 | path: "/admin/addRoles", 35 | data, 36 | }); 37 | } 38 | export function upRoles(data) { 39 | return request({ 40 | path: "/admin/upRoles", 41 | data, 42 | }); 43 | } 44 | export function delRoles(data) { 45 | return request({ 46 | path: "/admin/delRoles", 47 | data, 48 | }); 49 | } 50 | 51 | export function addMenu(data) { 52 | return request({ 53 | path: "/admin/addMenu", 54 | data, 55 | }); 56 | } 57 | export function changeMenu(data) { 58 | return request({ 59 | path: "/admin/changeMenu", 60 | data, 61 | }); 62 | } 63 | export function delMenu(data) { 64 | return request({ 65 | path: "/admin/delMenu", 66 | data, 67 | }); 68 | } 69 | export function getUser(data) { 70 | return request({ 71 | path: "/admin/getUser", 72 | data, 73 | }); 74 | } 75 | 76 | export function addUser(data) { 77 | return request({ 78 | path: "/admin/addUser", 79 | data, 80 | }); 81 | } 82 | 83 | export function upUser(data) { 84 | return request({ 85 | path: "/admin/upUser", 86 | data, 87 | }); 88 | } 89 | export function upUserPwd(data) { 90 | return request({ 91 | path: "/admin/upUserPwd", 92 | data, 93 | }); 94 | } 95 | export function delUser(data) { 96 | return request({ 97 | path: "/admin/delUser", 98 | data, 99 | }); 100 | } 101 | export function upTheme(data) { 102 | return request({ 103 | path: "/admin/upTheme", 104 | data, 105 | }); 106 | } 107 | export function login(data) { 108 | return request({ 109 | path: "/admin/login", 110 | data, 111 | }); 112 | } 113 | 114 | export function addMore(data) { 115 | return request({ 116 | path: "/admin/addMore", 117 | data, 118 | }); 119 | } 120 | 121 | export function getMore(data) { 122 | return request({ 123 | path: "/admin/getMore", 124 | data, 125 | }); 126 | } 127 | export function getMoreAll(data) { 128 | return request({ 129 | path: "/admin/getMoreAll", 130 | data, 131 | }); 132 | } 133 | export function upMore(data) { 134 | return request({ 135 | path: "/admin/upMore", 136 | data, 137 | }); 138 | } 139 | export function delMore(data) { 140 | return request({ 141 | path: "/admin/delMore", 142 | data, 143 | }); 144 | } 145 | 146 | export function addDict(data) { 147 | return request({ 148 | path: "/admin/addDict", 149 | data, 150 | }); 151 | } 152 | 153 | export function getDict(data) { 154 | return request({ 155 | path: "/admin/getDict", 156 | data, 157 | }); 158 | } 159 | 160 | export function upDict(data) { 161 | return request({ 162 | path: "/admin/upDict", 163 | data, 164 | }); 165 | } 166 | 167 | export function delDict(data) { 168 | return request({ 169 | path: "/admin/delDict", 170 | data, 171 | }); 172 | } 173 | 174 | export function getDictAll(data) { 175 | return request({ 176 | path: "/admin/getDictAll", 177 | data, 178 | }); 179 | } 180 | 181 | export function addDictItem(data) { 182 | return request({ 183 | path: "/admin/addDictItem", 184 | data, 185 | }); 186 | } 187 | 188 | export function getDictItem(data) { 189 | return request({ 190 | path: "/admin/getDictItem", 191 | data, 192 | }); 193 | } 194 | 195 | export function upDictItem(data) { 196 | return request({ 197 | path: "/admin/upDictItem", 198 | data, 199 | }); 200 | } 201 | 202 | export function delDictItem(data) { 203 | return request({ 204 | path: "/admin/delDictItem", 205 | data, 206 | }); 207 | } 208 | //根据类型查询字典 209 | export function getDictType(type="") { 210 | return request({ 211 | path: "/admin/getDictType", 212 | data:{type}, 213 | }); 214 | } 215 | -------------------------------------------------------------------------------- /item/src/api/tests/index.js: -------------------------------------------------------------------------------- 1 | import request from "@/utils/axios"; 2 | 3 | export function addTests(data) { 4 | return request({ 5 | path: "/tests/addTests", 6 | data 7 | }); 8 | } 9 | 10 | export function getTests(data) { 11 | return request({ 12 | path: "/tests/getTests", 13 | data 14 | }); 15 | } 16 | 17 | export function upTests(data) { 18 | return request({ 19 | path: "/tests/upTests", 20 | data 21 | }); 22 | } 23 | 24 | export function delTests(data) { 25 | return request({ 26 | path: "/tests/delTests", 27 | data 28 | }); 29 | } 30 | //菜单权限接口测试 31 | export function checkMenu(data) { 32 | return request({ 33 | path: "/tests/checkMenu", 34 | data 35 | }); 36 | } 37 | 38 | //角色权限接口测试 39 | export function checkRole(data) { 40 | return request({ 41 | path: "/tests/checkRole", 42 | data 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /item/src/assets/401_images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MingMinter/vue_node_admin/521616c3e299ea24c4abe4a464f0960f71a3f21e/item/src/assets/401_images/401.gif -------------------------------------------------------------------------------- /item/src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MingMinter/vue_node_admin/521616c3e299ea24c4abe4a464f0960f71a3f21e/item/src/assets/404_images/404.png -------------------------------------------------------------------------------- /item/src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MingMinter/vue_node_admin/521616c3e299ea24c4abe4a464f0960f71a3f21e/item/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /item/src/assets/custom-theme/fonts/element-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MingMinter/vue_node_admin/521616c3e299ea24c4abe4a464f0960f71a3f21e/item/src/assets/custom-theme/fonts/element-icons.ttf -------------------------------------------------------------------------------- /item/src/assets/custom-theme/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MingMinter/vue_node_admin/521616c3e299ea24c4abe4a464f0960f71a3f21e/item/src/assets/custom-theme/fonts/element-icons.woff -------------------------------------------------------------------------------- /item/src/assets/images/gitee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MingMinter/vue_node_admin/521616c3e299ea24c4abe4a464f0960f71a3f21e/item/src/assets/images/gitee.png -------------------------------------------------------------------------------- /item/src/assets/images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MingMinter/vue_node_admin/521616c3e299ea24c4abe4a464f0960f71a3f21e/item/src/assets/images/github.png -------------------------------------------------------------------------------- /item/src/assets/images/node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MingMinter/vue_node_admin/521616c3e299ea24c4abe4a464f0960f71a3f21e/item/src/assets/images/node.png -------------------------------------------------------------------------------- /item/src/assets/images/qq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MingMinter/vue_node_admin/521616c3e299ea24c4abe4a464f0960f71a3f21e/item/src/assets/images/qq.png -------------------------------------------------------------------------------- /item/src/assets/images/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MingMinter/vue_node_admin/521616c3e299ea24c4abe4a464f0960f71a3f21e/item/src/assets/images/star.png -------------------------------------------------------------------------------- /item/src/assets/images/vue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MingMinter/vue_node_admin/521616c3e299ea24c4abe4a464f0960f71a3f21e/item/src/assets/images/vue.png -------------------------------------------------------------------------------- /item/src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 74 | 75 | 88 | -------------------------------------------------------------------------------- /item/src/components/ErrorLog/index.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 70 | 71 | 79 | -------------------------------------------------------------------------------- /item/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 45 | -------------------------------------------------------------------------------- /item/src/components/IconSelect/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 44 | 45 | 69 | -------------------------------------------------------------------------------- /item/src/components/IconSelect/requireIcons.js: -------------------------------------------------------------------------------- 1 | 2 | const req = require.context('../../icons/svg', false, /\.svg$/) 3 | const requireAll = requireContext => requireContext.keys() 4 | 5 | const re = /\.\/(.*)\.svg/ 6 | 7 | const icons = requireAll(req).map(i => { 8 | return i.match(re)[1] 9 | }) 10 | 11 | export default icons 12 | -------------------------------------------------------------------------------- /item/src/components/ImageCropper/utils/data2blob.js: -------------------------------------------------------------------------------- 1 | /** 2 | * database64文件格式转换为2进制 3 | * 4 | * @param {[String]} data dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了 5 | * @param {[String]} mime [description] 6 | * @return {[blob]} [description] 7 | */ 8 | export default function(data, mime) { 9 | data = data.split(',')[1] 10 | data = window.atob(data) 11 | var ia = new Uint8Array(data.length) 12 | for (var i = 0; i < data.length; i++) { 13 | ia[i] = data.charCodeAt(i) 14 | } 15 | // canvas.toDataURL 返回的默认格式就是 image/png 16 | return new Blob([ia], { 17 | type: mime 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /item/src/components/ImageCropper/utils/effectRipple.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 点击波纹效果 3 | * 4 | * @param {[event]} e [description] 5 | * @param {[Object]} arg_opts [description] 6 | * @return {[bollean]} [description] 7 | */ 8 | export default function(e, arg_opts) { 9 | var opts = Object.assign({ 10 | ele: e.target, // 波纹作用元素 11 | type: 'hit', // hit点击位置扩散center中心点扩展 12 | bgc: 'rgba(0, 0, 0, 0.15)' // 波纹颜色 13 | }, arg_opts) 14 | var target = opts.ele 15 | if (target) { 16 | var rect = target.getBoundingClientRect() 17 | var ripple = target.querySelector('.e-ripple') 18 | if (!ripple) { 19 | ripple = document.createElement('span') 20 | ripple.className = 'e-ripple' 21 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px' 22 | target.appendChild(ripple) 23 | } else { 24 | ripple.className = 'e-ripple' 25 | } 26 | switch (opts.type) { 27 | case 'center': 28 | ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px' 29 | ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px' 30 | break 31 | default: 32 | ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px' 33 | ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px' 34 | } 35 | ripple.style.backgroundColor = opts.bgc 36 | ripple.className = 'e-ripple z-active' 37 | return false 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /item/src/components/ImageCropper/utils/mimes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'jpg': 'image/jpeg', 3 | 'png': 'image/png', 4 | 'gif': 'image/gif', 5 | 'svg': 'image/svg+xml', 6 | 'psd': 'image/photoshop' 7 | } 8 | -------------------------------------------------------------------------------- /item/src/components/LangSelect/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 42 | -------------------------------------------------------------------------------- /item/src/components/Pagination/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 98 | 99 | 109 | -------------------------------------------------------------------------------- /item/src/components/RightPanel/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 78 | 79 | 86 | 87 | 147 | -------------------------------------------------------------------------------- /item/src/components/Screenfull/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 50 | 51 | 61 | -------------------------------------------------------------------------------- /item/src/components/SizeSelect/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 58 | -------------------------------------------------------------------------------- /item/src/components/Sticky/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 92 | -------------------------------------------------------------------------------- /item/src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 47 | 48 | 63 | -------------------------------------------------------------------------------- /item/src/components/parentView/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/filters/index.js: -------------------------------------------------------------------------------- 1 | // import parseTime, formatTime and set to filter 2 | export { parseTime, formatTime } from '@/utils' 3 | 4 | /** 5 | * Show plural label if time is plural number 6 | * @param {number} time 7 | * @param {string} label 8 | * @return {string} 9 | */ 10 | function pluralize(time, label) { 11 | if (time === 1) { 12 | return time + label 13 | } 14 | return time + label + 's' 15 | } 16 | 17 | /** 18 | * @param {number} time 19 | */ 20 | export function timeAgo(time) { 21 | const between = Date.now() / 1000 - Number(time) 22 | if (between < 3600) { 23 | return pluralize(~~(between / 60), ' minute') 24 | } else if (between < 86400) { 25 | return pluralize(~~(between / 3600), ' hour') 26 | } else { 27 | return pluralize(~~(between / 86400), ' day') 28 | } 29 | } 30 | 31 | /** 32 | * Number formatting 33 | * like 10000 => 10k 34 | * @param {number} num 35 | * @param {number} digits 36 | */ 37 | export function numberFormatter(num, digits) { 38 | const si = [ 39 | { value: 1E18, symbol: 'E' }, 40 | { value: 1E15, symbol: 'P' }, 41 | { value: 1E12, symbol: 'T' }, 42 | { value: 1E9, symbol: 'G' }, 43 | { value: 1E6, symbol: 'M' }, 44 | { value: 1E3, symbol: 'k' } 45 | ] 46 | for (let i = 0; i < si.length; i++) { 47 | if (num >= si[i].value) { 48 | return (num / si[i].value).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol 49 | } 50 | } 51 | return num.toString() 52 | } 53 | 54 | /** 55 | * 10000 => "10,000" 56 | * @param {number} num 57 | */ 58 | export function toThousandFilter(num) { 59 | return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ',')) 60 | } 61 | 62 | /** 63 | * Upper case first char 64 | * @param {String} string 65 | */ 66 | export function uppercaseFirst(string) { 67 | return string.charAt(0).toUpperCase() + string.slice(1) 68 | } 69 | -------------------------------------------------------------------------------- /item/src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg component 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const req = require.context('./svg', false, /\.svg$/) 8 | const requireAll = requireContext => requireContext.keys().map(requireContext) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /item/src/icons/svg/404.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/bug.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/chart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/clipboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/component.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/dashboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/documentation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/drag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/education.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/excel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/exit-fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/eye-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/form.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/guide.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/international.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/language.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/menu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/message.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/money.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/nested.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/pdf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/people.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/peoples.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/qq.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/role.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/shopping.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/size.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/skill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/theme.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/tree-table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/wechat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svg/zip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /item/src/icons/svgo.yml: -------------------------------------------------------------------------------- 1 | # replace default config 2 | 3 | # multipass: true 4 | # full: true 5 | 6 | plugins: 7 | 8 | # - name 9 | # 10 | # or: 11 | # - name: false 12 | # - name: true 13 | # 14 | # or: 15 | # - name: 16 | # param1: 1 17 | # param2: 2 18 | 19 | - removeAttrs: 20 | attrs: 21 | - 'fill' 22 | - 'fill-rule' 23 | -------------------------------------------------------------------------------- /item/src/lang/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import Cookies from 'js-cookie' 4 | import elementEnLocale from 'element-ui/lib/locale/lang/en' // element-ui lang 5 | import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN'// element-ui lang 6 | import elementEsLocale from 'element-ui/lib/locale/lang/es'// element-ui lang 7 | import elementJaLocale from 'element-ui/lib/locale/lang/ja'// element-ui lang 8 | import enLocale from './en' 9 | import zhLocale from './zh' 10 | import esLocale from './es' 11 | import jaLocale from './ja' 12 | 13 | Vue.use(VueI18n) 14 | 15 | const messages = { 16 | en: { 17 | ...enLocale, 18 | ...elementEnLocale 19 | }, 20 | zh: { 21 | ...zhLocale, 22 | ...elementZhLocale 23 | }, 24 | es: { 25 | ...esLocale, 26 | ...elementEsLocale 27 | }, 28 | ja: { 29 | ...jaLocale, 30 | ...elementJaLocale 31 | } 32 | } 33 | export function getLanguage() { 34 | const chooseLanguage = Cookies.get('language') 35 | if (chooseLanguage) return chooseLanguage 36 | 37 | // if has not choose language 38 | const language = (navigator.language || navigator.browserLanguage).toLowerCase() 39 | const locales = Object.keys(messages) 40 | for (const locale of locales) { 41 | if (language.indexOf(locale) > -1) { 42 | return locale 43 | } 44 | } 45 | return 'zh' 46 | } 47 | const i18n = new VueI18n({ 48 | // set locale 49 | // options: en | zh | es 50 | locale: getLanguage(), 51 | // set locale messages 52 | messages 53 | }) 54 | 55 | export default i18n 56 | -------------------------------------------------------------------------------- /item/src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 24 | 25 | 49 | 50 | 58 | -------------------------------------------------------------------------------- /item/src/layout/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 89 | 90 | 173 | -------------------------------------------------------------------------------- /item/src/layout/components/Settings/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 104 | 105 | 138 | -------------------------------------------------------------------------------- /item/src/layout/components/Sidebar/FixiOSBug.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | device() { 4 | return this.$store.state.app.device 5 | } 6 | }, 7 | mounted() { 8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug 9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135 10 | this.fixBugIniOS() 11 | }, 12 | methods: { 13 | fixBugIniOS() { 14 | const $subMenu = this.$refs.subMenu 15 | if ($subMenu) { 16 | const handleMouseleave = $subMenu.handleMouseleave 17 | $subMenu.handleMouseleave = (e) => { 18 | if (this.device === 'mobile') { 19 | return 20 | } 21 | handleMouseleave(e) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /item/src/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 42 | -------------------------------------------------------------------------------- /item/src/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 44 | -------------------------------------------------------------------------------- /item/src/layout/components/Sidebar/Logo.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 39 | 40 | 89 | -------------------------------------------------------------------------------- /item/src/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 99 | -------------------------------------------------------------------------------- /item/src/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 55 | -------------------------------------------------------------------------------- /item/src/layout/components/TagsView/ScrollPane.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 78 | 79 | 95 | -------------------------------------------------------------------------------- /item/src/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as AppMain } from './AppMain' 2 | export { default as Navbar } from './Navbar' 3 | export { default as Settings } from './Settings' 4 | export { default as Sidebar } from './Sidebar/index.vue' 5 | export { default as TagsView } from './TagsView/index.vue' 6 | -------------------------------------------------------------------------------- /item/src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 63 | 64 | 107 | -------------------------------------------------------------------------------- /item/src/layout/mixin/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | const { body } = document 4 | const WIDTH = 992 // refer to Bootstrap's responsive design 5 | 6 | export default { 7 | watch: { 8 | $route(route) { 9 | if (this.device === 'mobile' && this.sidebar.opened) { 10 | store.dispatch('app/closeSideBar', { withoutAnimation: false }) 11 | } 12 | } 13 | }, 14 | beforeMount() { 15 | window.addEventListener('resize', this.$_resizeHandler) 16 | }, 17 | beforeDestroy() { 18 | window.removeEventListener('resize', this.$_resizeHandler) 19 | }, 20 | mounted() { 21 | const isMobile = this.$_isMobile() 22 | if (isMobile) { 23 | store.dispatch('app/toggleDevice', 'mobile') 24 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 25 | } 26 | }, 27 | methods: { 28 | // use $_ for mixins properties 29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential 30 | $_isMobile() { 31 | const rect = body.getBoundingClientRect() 32 | return rect.width - 1 < WIDTH 33 | }, 34 | $_resizeHandler() { 35 | if (!document.hidden) { 36 | const isMobile = this.$_isMobile() 37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') 38 | 39 | if (isMobile) { 40 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /item/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import Cookies from 'js-cookie' 4 | 5 | import 'normalize.css/normalize.css' // a modern alternative to CSS resets 6 | 7 | import Element from 'element-ui' 8 | import './styles/element-variables.scss' 9 | 10 | import '@/styles/index.scss' // global css 11 | 12 | import App from './App' 13 | import store from './store' 14 | import router from './router' 15 | 16 | import i18n from './lang' // internationalization 17 | import './icons' // icon 18 | import './permission' // permission control 19 | import './utils/error-log' // error log 20 | import "@/utils/directive"; 21 | import * as filters from './filters' // global filters 22 | import Pagination from "@/components/Pagination"; 23 | import {getDictType} from "@/api/admin"; 24 | /** 25 | * If you don't want to use mock-server 26 | * you want to use MockJs for mock api 27 | * you can execute: mockXHR() 28 | * 29 | * Currently MockJs will be used in the production environment, 30 | * please remove it before going online ! ! ! 31 | */ 32 | // if (process.env.NODE_ENV === 'production') { 33 | // const { mockXHR } = require('../mock') 34 | // mockXHR() 35 | // } 36 | 37 | Vue.use(Element, { 38 | size: Cookies.get('size') || 'medium', // set element-ui default size 39 | i18n: (key, value) => i18n.t(key, value) 40 | }) 41 | 42 | // register global utility filters 43 | Object.keys(filters).forEach(key => { 44 | Vue.filter(key, filters[key]) 45 | }) 46 | Vue.prototype.$getDictType=getDictType; 47 | Vue.config.productionTip = false 48 | Vue.component('Pagination', Pagination) 49 | new Vue({ 50 | el: '#app', 51 | router, 52 | store, 53 | i18n, 54 | render: h => h(App) 55 | }) 56 | -------------------------------------------------------------------------------- /item/src/permission.js: -------------------------------------------------------------------------------- 1 | import router from "./router"; 2 | import store from "./store"; 3 | import { Message } from "element-ui"; 4 | import NProgress from "nprogress"; // progress bar 5 | import "nprogress/nprogress.css"; // progress bar style 6 | import { getToken } from "@/utils/auth"; // get token from cookie 7 | import getPageTitle from "@/utils/get-page-title"; 8 | 9 | NProgress.configure({ showSpinner: false }); // NProgress Configuration 10 | 11 | const whiteList = ["/login", "/auth-redirect"]; // no redirect whitelist 12 | 13 | router.beforeEach(async (to, from, next) => { 14 | //如果点击的是父级路由 15 | if(to.path.indexOf("/*")!==-1) return; 16 | // start progress bar 17 | NProgress.start(); 18 | 19 | // set page title 20 | document.title = getPageTitle(to.meta.title); 21 | 22 | // determine whether the user has logged in 23 | const hasToken = getToken(); 24 | 25 | if (hasToken) { 26 | if (to.path === "/login") { 27 | // if is logged in, redirect to the home page 28 | next({ path: "/" }); 29 | NProgress.done(); // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939 30 | } else { 31 | // determine whether the user has obtained his permission roles through getInfo 32 | const hasRoles = store.getters.roles && store.getters.roles.length > 0; 33 | if (hasRoles) { 34 | next(); 35 | } else { 36 | try { 37 | // get user info 38 | // note: roles must be a object array! such as: ['admin'] or ,['developer','editor'] 39 | await store.dispatch("user/getInfo"); 40 | // generate accessible routes map based on roles 41 | const accessRoutes = await store.dispatch("permission/generateRoutes"); 42 | accessRoutes.push({ 43 | path: '*', 44 | redirect: "/404", 45 | hidden: true 46 | }) 47 | router.addRoutes(accessRoutes); 48 | 49 | 50 | next({ ...to, replace: true }); 51 | } catch (error) { 52 | NProgress.done(); 53 | } 54 | } 55 | } 56 | } else { 57 | /* has no token*/ 58 | 59 | if (whiteList.indexOf(to.path) !== -1) { 60 | // in the free login whitelist, go directly 61 | next(); 62 | } else { 63 | // other pages that do not have permission to access are redirected to the login page. 64 | next(`/login?redirect=${to.path}`); 65 | NProgress.done(); 66 | } 67 | } 68 | }); 69 | 70 | router.afterEach(() => { 71 | // finish progress bar 72 | NProgress.done(); 73 | }); 74 | -------------------------------------------------------------------------------- /item/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | 6 | /* Layout */ 7 | import Layout from '@/layout' 8 | 9 | export const constantRoutes = [ 10 | { 11 | path: '/redirect', 12 | component: Layout, 13 | hidden: true, 14 | children: [ 15 | { 16 | path: '/redirect/:path(.*)', 17 | component: () => import('@/views/redirect/index') 18 | } 19 | ] 20 | }, 21 | { 22 | path: '/login', 23 | component: () => import('@/views/login/index'), 24 | hidden: true 25 | }, 26 | { 27 | path: '/auth-redirect', 28 | component: () => import('@/views/login/auth-redirect'), 29 | hidden: true 30 | }, 31 | { 32 | path: '/404', 33 | component: () => import('@/views/error-page/404'), 34 | hidden: true 35 | }, 36 | { 37 | path: '/401', 38 | component: () => import('@/views/error-page/401'), 39 | hidden: true 40 | }, 41 | { 42 | path: '/', 43 | component: Layout, 44 | redirect: '/dashboard', 45 | children: [ 46 | { 47 | path: 'dashboard', 48 | component: () => import('@/views/dashboard/index'), 49 | name: 'Dashboard', 50 | meta: { title: 'dashboard', icon: 'dashboard', affix: true } 51 | } 52 | ] 53 | } 54 | ] 55 | 56 | // 需要动态判断的路由 57 | export const asyncRoutes = [ 58 | 59 | ] 60 | 61 | const createRouter = () => new Router({ 62 | // mode: 'history', // require service support 63 | scrollBehavior: () => ({ y: 0 }), 64 | routes: constantRoutes 65 | }) 66 | 67 | const router = createRouter() 68 | 69 | // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 70 | export function resetRouter() { 71 | const newRouter = createRouter() 72 | router.matcher = newRouter.matcher // reset router 73 | } 74 | 75 | export default router 76 | -------------------------------------------------------------------------------- /item/src/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: '后台管理', 3 | 4 | /** 5 | * @type {boolean} true | false 6 | * @description Whether show the settings right-panel 7 | */ 8 | showSettings: true, 9 | 10 | /** 11 | * @type {boolean} true | false 12 | * @description Whether need tagsView 13 | */ 14 | tagsView: true, 15 | 16 | /** 17 | * @type {boolean} true | false 18 | * @description Whether fix the header 19 | */ 20 | fixedHeader: true, 21 | 22 | /** 23 | * @type {boolean} true | false 24 | * @description Whether show the logo in sidebar 25 | */ 26 | sidebarLogo: true, 27 | 28 | /** 29 | * @type {boolean} true | false 30 | * @description Whether support pinyin search in headerSearch 31 | * Bundle size minified 47.3kb,minified + gzipped 63kb 32 | */ 33 | supportPinyinSearch: true, 34 | 35 | /** 36 | * @type {string | array} 'production' | ['production', 'development'] 37 | * @description Need show err logs component. 38 | * The default is only used in the production env 39 | * If you want to also use it in dev, you can pass ['production', 'development'] 40 | */ 41 | errorLog: 'production' 42 | } 43 | -------------------------------------------------------------------------------- /item/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | language: state => state.app.language, 4 | size: state => state.app.size, 5 | device: state => state.app.device, 6 | visitedViews: state => state.tagsView.visitedViews, 7 | cachedViews: state => state.tagsView.cachedViews, 8 | token: state => state.user.token, 9 | avatar: state => state.user.avatar, 10 | name: state => state.user.name, 11 | introduction: state => state.user.introduction, 12 | roles: state => state.user.roles, 13 | getAdmin: state => state.user.admin, 14 | permission_routes: state => state.permission.routes, 15 | errorLogs: state => state.errorLog.logs, 16 | getRole: state => state.permission.role, 17 | } 18 | export default getters 19 | -------------------------------------------------------------------------------- /item/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import getters from './getters' 4 | 5 | Vue.use(Vuex) 6 | 7 | // https://webpack.js.org/guides/dependency-management/#requirecontext 8 | const modulesFiles = require.context('./modules', true, /\.js$/) 9 | 10 | // you do not need `import app from './modules/app'` 11 | // it will auto require all vuex module from modules file 12 | const modules = modulesFiles.keys().reduce((modules, modulePath) => { 13 | // set './app.js' => 'app' 14 | const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') 15 | const value = modulesFiles(modulePath) 16 | modules[moduleName] = value.default 17 | return modules 18 | }, {}) 19 | 20 | const store = new Vuex.Store({ 21 | modules, 22 | getters 23 | }) 24 | 25 | export default store 26 | -------------------------------------------------------------------------------- /item/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | import { getLanguage } from '@/lang/index' 3 | 4 | const state = { 5 | sidebar: { 6 | opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, 7 | withoutAnimation: false 8 | }, 9 | device: 'desktop', 10 | language: getLanguage(), 11 | size: Cookies.get('size') || 'medium' 12 | } 13 | 14 | const mutations = { 15 | TOGGLE_SIDEBAR: state => { 16 | state.sidebar.opened = !state.sidebar.opened 17 | state.sidebar.withoutAnimation = false 18 | if (state.sidebar.opened) { 19 | Cookies.set('sidebarStatus', 1) 20 | } else { 21 | Cookies.set('sidebarStatus', 0) 22 | } 23 | }, 24 | CLOSE_SIDEBAR: (state, withoutAnimation) => { 25 | Cookies.set('sidebarStatus', 0) 26 | state.sidebar.opened = false 27 | state.sidebar.withoutAnimation = withoutAnimation 28 | }, 29 | TOGGLE_DEVICE: (state, device) => { 30 | state.device = device 31 | }, 32 | SET_LANGUAGE: (state, language) => { 33 | state.language = language 34 | Cookies.set('language', language) 35 | }, 36 | SET_SIZE: (state, size) => { 37 | state.size = size 38 | Cookies.set('size', size) 39 | } 40 | } 41 | 42 | const actions = { 43 | toggleSideBar({ commit }) { 44 | commit('TOGGLE_SIDEBAR') 45 | }, 46 | closeSideBar({ commit }, { withoutAnimation }) { 47 | commit('CLOSE_SIDEBAR', withoutAnimation) 48 | }, 49 | toggleDevice({ commit }, device) { 50 | commit('TOGGLE_DEVICE', device) 51 | }, 52 | setLanguage({ commit }, language) { 53 | commit('SET_LANGUAGE', language) 54 | }, 55 | setSize({ commit }, size) { 56 | commit('SET_SIZE', size) 57 | } 58 | } 59 | 60 | export default { 61 | namespaced: true, 62 | state, 63 | mutations, 64 | actions 65 | } 66 | -------------------------------------------------------------------------------- /item/src/store/modules/errorLog.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | logs: [] 3 | } 4 | 5 | const mutations = { 6 | ADD_ERROR_LOG: (state, log) => { 7 | state.logs.push(log) 8 | }, 9 | CLEAR_ERROR_LOG: (state) => { 10 | state.logs.splice(0) 11 | } 12 | } 13 | 14 | const actions = { 15 | addErrorLog({ commit }, log) { 16 | commit('ADD_ERROR_LOG', log) 17 | }, 18 | clearErrorLog({ commit }) { 19 | commit('CLEAR_ERROR_LOG') 20 | } 21 | } 22 | 23 | export default { 24 | namespaced: true, 25 | state, 26 | mutations, 27 | actions 28 | } 29 | -------------------------------------------------------------------------------- /item/src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import { constantRoutes } from '@/router' 2 | import Layout from '@/layout' 3 | import parentView from '@/components/parentView' 4 | import { getRouter } from '@/api/admin' 5 | /** 6 | * Use meta.role to determine if the current user has permission 7 | * @param roles 8 | * @param route 9 | */ 10 | function hasPermission(roles, route) { 11 | 12 | if (route.meta && route.meta.roles) { 13 | return roles.some(role => route.meta.roles.includes(role)) 14 | } else { 15 | return true 16 | } 17 | } 18 | export const loadView = (view) => { // 路由懒加载 19 | return (resolve) => require([`@/views/${view}.vue`], resolve) 20 | } 21 | /** 22 | * Filter asynchronous routing tables by recursion 23 | * @param routes asyncRoutes 24 | * @param roles 25 | */ 26 | export function filterAsyncRoutes(routes) { 27 | //菜单 权限数组 28 | const menu = [],role=[]; 29 | routes.forEach(route => { 30 | const tmp = { ...route }; 31 | // 动态加载页面 32 | if (tmp.component !== '') { 33 | tmp.component = loadView(tmp.component); 34 | } 35 | // 动态加载左边主体菜单 36 | if (tmp.layout) { 37 | tmp.component = Layout 38 | } 39 | // 如果还需要套二级 40 | if (tmp.menuType==="M"&&tmp.parentId!=0) { 41 | tmp.component = parentView 42 | } 43 | if (tmp.children && tmp.children.length != 0) { 44 | let childrenRes=filterAsyncRoutes(tmp.children); 45 | tmp.children =childrenRes.menu; 46 | role.push(...childrenRes.role); 47 | } 48 | 49 | tmp.roleKey&&role.push(tmp.roleKey); 50 | menu.push(tmp) 51 | }) 52 | 53 | return { 54 | menu, 55 | role 56 | } 57 | } 58 | 59 | const state = { 60 | routes: [], 61 | addRoutes: [], 62 | role:[] 63 | } 64 | 65 | const mutations = { 66 | SET_ROUTES: (state, routes) => { 67 | state.addRoutes = routes 68 | state.routes = constantRoutes.concat(routes) 69 | }, 70 | SET_ROLE:(state,role)=>state.role=role 71 | } 72 | 73 | const actions = { 74 | async generateRoutes({ commit }) { 75 | const {data} = await getRouter(); 76 | return new Promise(resolve => { 77 | let {menu,role} = filterAsyncRoutes(data.routerMenu); 78 | commit('SET_ROUTES', menu); 79 | commit('SET_ROLE', role); 80 | resolve(menu) 81 | }) 82 | } 83 | } 84 | 85 | export default { 86 | namespaced: true, 87 | state, 88 | mutations, 89 | actions 90 | } 91 | -------------------------------------------------------------------------------- /item/src/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | import variables from '@/styles/element-variables.scss' 2 | import defaultSettings from '@/settings' 3 | 4 | const { showSettings, tagsView, fixedHeader, sidebarLogo, supportPinyinSearch } = defaultSettings 5 | 6 | const state = { 7 | theme: variables.theme, 8 | showSettings, 9 | tagsView, 10 | fixedHeader, 11 | sidebarLogo, 12 | supportPinyinSearch 13 | } 14 | 15 | const mutations = { 16 | CHANGE_SETTING: (state, { key, value }) => { 17 | // eslint-disable-next-line no-prototype-builtins 18 | if (state.hasOwnProperty(key)) { 19 | state[key] = value 20 | } 21 | } 22 | } 23 | 24 | const actions = { 25 | changeSetting({ commit }, data) { 26 | commit('CHANGE_SETTING', data) 27 | } 28 | } 29 | 30 | export default { 31 | namespaced: true, 32 | state, 33 | mutations, 34 | actions 35 | } 36 | 37 | -------------------------------------------------------------------------------- /item/src/store/modules/tagsView.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | visitedViews: [], 3 | cachedViews: [] 4 | } 5 | 6 | const mutations = { 7 | ADD_VISITED_VIEW: (state, view) => { 8 | if (state.visitedViews.some(v => v.path === view.path)) return 9 | state.visitedViews.push( 10 | Object.assign({}, view, { 11 | title: view.meta.title || 'no-name' 12 | }) 13 | ) 14 | }, 15 | ADD_CACHED_VIEW: (state, view) => { 16 | if (state.cachedViews.includes(view.name)) return 17 | if (!view.meta.noCache) { 18 | state.cachedViews.push(view.name) 19 | } 20 | }, 21 | 22 | DEL_VISITED_VIEW: (state, view) => { 23 | for (const [i, v] of state.visitedViews.entries()) { 24 | if (v.path === view.path) { 25 | state.visitedViews.splice(i, 1) 26 | break 27 | } 28 | } 29 | }, 30 | DEL_CACHED_VIEW: (state, view) => { 31 | const index = state.cachedViews.indexOf(view.name) 32 | index > -1 && state.cachedViews.splice(index, 1) 33 | }, 34 | 35 | DEL_OTHERS_VISITED_VIEWS: (state, view) => { 36 | state.visitedViews = state.visitedViews.filter(v => { 37 | return v.meta.affix || v.path === view.path 38 | }) 39 | }, 40 | DEL_OTHERS_CACHED_VIEWS: (state, view) => { 41 | const index = state.cachedViews.indexOf(view.name) 42 | if (index > -1) { 43 | state.cachedViews = state.cachedViews.slice(index, index + 1) 44 | } else { 45 | // if index = -1, there is no cached tags 46 | state.cachedViews = [] 47 | } 48 | }, 49 | 50 | DEL_ALL_VISITED_VIEWS: state => { 51 | // keep affix tags 52 | const affixTags = state.visitedViews.filter(tag => tag.meta.affix) 53 | state.visitedViews = affixTags 54 | }, 55 | DEL_ALL_CACHED_VIEWS: state => { 56 | state.cachedViews = [] 57 | }, 58 | 59 | UPDATE_VISITED_VIEW: (state, view) => { 60 | for (let v of state.visitedViews) { 61 | if (v.path === view.path) { 62 | v = Object.assign(v, view) 63 | break 64 | } 65 | } 66 | } 67 | } 68 | 69 | const actions = { 70 | addView({ dispatch }, view) { 71 | dispatch('addVisitedView', view) 72 | dispatch('addCachedView', view) 73 | }, 74 | addVisitedView({ commit }, view) { 75 | commit('ADD_VISITED_VIEW', view) 76 | }, 77 | addCachedView({ commit }, view) { 78 | commit('ADD_CACHED_VIEW', view) 79 | }, 80 | 81 | delView({ dispatch, state }, view) { 82 | return new Promise(resolve => { 83 | dispatch('delVisitedView', view) 84 | dispatch('delCachedView', view) 85 | resolve({ 86 | visitedViews: [...state.visitedViews], 87 | cachedViews: [...state.cachedViews] 88 | }) 89 | }) 90 | }, 91 | delVisitedView({ commit, state }, view) { 92 | return new Promise(resolve => { 93 | commit('DEL_VISITED_VIEW', view) 94 | resolve([...state.visitedViews]) 95 | }) 96 | }, 97 | delCachedView({ commit, state }, view) { 98 | return new Promise(resolve => { 99 | commit('DEL_CACHED_VIEW', view) 100 | resolve([...state.cachedViews]) 101 | }) 102 | }, 103 | 104 | delOthersViews({ dispatch, state }, view) { 105 | return new Promise(resolve => { 106 | dispatch('delOthersVisitedViews', view) 107 | dispatch('delOthersCachedViews', view) 108 | resolve({ 109 | visitedViews: [...state.visitedViews], 110 | cachedViews: [...state.cachedViews] 111 | }) 112 | }) 113 | }, 114 | delOthersVisitedViews({ commit, state }, view) { 115 | return new Promise(resolve => { 116 | commit('DEL_OTHERS_VISITED_VIEWS', view) 117 | resolve([...state.visitedViews]) 118 | }) 119 | }, 120 | delOthersCachedViews({ commit, state }, view) { 121 | return new Promise(resolve => { 122 | commit('DEL_OTHERS_CACHED_VIEWS', view) 123 | resolve([...state.cachedViews]) 124 | }) 125 | }, 126 | 127 | delAllViews({ dispatch, state }, view) { 128 | return new Promise(resolve => { 129 | dispatch('delAllVisitedViews', view) 130 | dispatch('delAllCachedViews', view) 131 | resolve({ 132 | visitedViews: [...state.visitedViews], 133 | cachedViews: [...state.cachedViews] 134 | }) 135 | }) 136 | }, 137 | delAllVisitedViews({ commit, state }) { 138 | return new Promise(resolve => { 139 | commit('DEL_ALL_VISITED_VIEWS') 140 | resolve([...state.visitedViews]) 141 | }) 142 | }, 143 | delAllCachedViews({ commit, state }) { 144 | return new Promise(resolve => { 145 | commit('DEL_ALL_CACHED_VIEWS') 146 | resolve([...state.cachedViews]) 147 | }) 148 | }, 149 | 150 | updateVisitedView({ commit }, view) { 151 | commit('UPDATE_VISITED_VIEW', view) 152 | } 153 | } 154 | 155 | export default { 156 | namespaced: true, 157 | state, 158 | mutations, 159 | actions 160 | } 161 | -------------------------------------------------------------------------------- /item/src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import {getToken, setToken, removeToken} from '@/utils/auth'; 2 | import router, {resetRouter} from '@/router'; 3 | import {getUserInfo} from "@/api/admin"; 4 | 5 | const state = { 6 | token: getToken(), 7 | name: '', 8 | avatar: '', 9 | roles: [],//角色权限 10 | admin:false//总管理员判断 11 | }; 12 | const mutations = { 13 | SET_TOKEN: (state, token) => { 14 | state.token = token; 15 | }, 16 | SET_INTRODUCTION: (state, introduction) => { 17 | state.introduction = introduction; 18 | }, 19 | SET_NAME: (state, name) => { 20 | state.name = name; 21 | }, 22 | SET_AVATAR: (state, avatar) => { 23 | state.avatar = avatar; 24 | }, 25 | SET_ROLES: (state, roles) => { 26 | state.roles = roles; 27 | }, 28 | SET_ADMIN: (state, admin) => { 29 | state.admin = admin; 30 | }, 31 | }; 32 | function setTheme(name,val){ 33 | document.getElementsByTagName('body')[0].style.setProperty(name,val); 34 | }; 35 | const actions = { 36 | // user login 37 | login({commit}, userInfo) { 38 | const {username, password} = userInfo; 39 | return new Promise((resolve, reject) => { 40 | const {data} = response; 41 | commit('SET_TOKEN', data.token); 42 | setToken(data.token); 43 | resolve(); 44 | }); 45 | }, 46 | // get user info 47 | async getInfo({commit,dispatch}) { 48 | let {data:user}=await getUserInfo(); 49 | commit('SET_ROLES', user.roleKey); 50 | commit('SET_NAME', user.user.name); 51 | commit('SET_ADMIN', !!user.user.admin||!!user.roleAdmin); 52 | commit('SET_AVATAR', "http://ordermobile.yknba.cn/img/bg.2fa5cb09.jpg"); 53 | try { 54 | //处理主题 55 | let {menuBg,menuHoverBg,menuActiveText,menuSubActiveText,menuSubBg,menuText}=user.theme; 56 | setTheme("--menuBg-color",menuBg);setTheme("--menuSubBg-color",menuSubBg);setTheme("--menuText-color",menuText);setTheme("--menuActiveText-color",menuActiveText);setTheme("--menuSubActiveText-color",menuSubActiveText);setTheme("--menuHoverBg-color",menuHoverBg); 57 | this.dispatch('settings/changeSetting', { 58 | key: 'theme', 59 | value: menuBg 60 | }); 61 | }catch (e) { 62 | 63 | } 64 | return user; 65 | }, 66 | // user logout 67 | logout({commit, state, dispatch}) { 68 | return new Promise((resolve, reject) => { 69 | commit('SET_TOKEN', ''); 70 | commit('SET_ROLES', []); 71 | removeToken(); 72 | resetRouter(); 73 | // reset visited views and cached views 74 | // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485 75 | dispatch('tagsView/delAllViews', null, {root: true}); 76 | resolve(); 77 | }); 78 | }, 79 | // remove token 80 | resetToken({commit}) { 81 | return new Promise((resolve) => { 82 | commit('SET_TOKEN', ''); 83 | commit('SET_ROLES', []); 84 | removeToken(); 85 | resolve(); 86 | }); 87 | }, 88 | // dynamically modify permissions 89 | async changeRoles({commit, dispatch}, role) { 90 | const token = role + '-token'; 91 | commit('SET_TOKEN', token); 92 | setToken(token); 93 | await dispatch('getInfo'); 94 | resetRouter(); 95 | // generate accessible routes map based on roles 96 | const accessRoutes = await dispatch('permission/generateRoutes'); 97 | // dynamically add accessible routes 98 | router.addRoutes(accessRoutes); 99 | // reset visited views and cached views 100 | dispatch('tagsView/delAllViews', null, {root: true}); 101 | }, 102 | }; 103 | export default { 104 | namespaced: true, 105 | state, 106 | mutations, 107 | actions, 108 | }; 109 | -------------------------------------------------------------------------------- /item/src/styles/btn.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | 3 | @mixin colorBtn($color) { 4 | background: $color; 5 | 6 | &:hover { 7 | color: $color; 8 | 9 | &:before, 10 | &:after { 11 | background: $color; 12 | } 13 | } 14 | } 15 | 16 | .blue-btn { 17 | @include colorBtn($blue) 18 | } 19 | 20 | .light-blue-btn { 21 | @include colorBtn($light-blue) 22 | } 23 | 24 | .red-btn { 25 | @include colorBtn($red) 26 | } 27 | 28 | .pink-btn { 29 | @include colorBtn($pink) 30 | } 31 | 32 | .green-btn { 33 | @include colorBtn($green) 34 | } 35 | 36 | .tiffany-btn { 37 | @include colorBtn($tiffany) 38 | } 39 | 40 | .yellow-btn { 41 | @include colorBtn($yellow) 42 | } 43 | 44 | .pan-btn { 45 | font-size: 14px; 46 | color: #fff; 47 | padding: 14px 36px; 48 | border-radius: 8px; 49 | border: none; 50 | outline: none; 51 | transition: 600ms ease all; 52 | position: relative; 53 | display: inline-block; 54 | 55 | &:hover { 56 | background: #fff; 57 | 58 | &:before, 59 | &:after { 60 | width: 100%; 61 | transition: 600ms ease all; 62 | } 63 | } 64 | 65 | &:before, 66 | &:after { 67 | content: ''; 68 | position: absolute; 69 | top: 0; 70 | right: 0; 71 | height: 2px; 72 | width: 0; 73 | transition: 400ms ease all; 74 | } 75 | 76 | &::after { 77 | right: inherit; 78 | top: inherit; 79 | left: 0; 80 | bottom: 0; 81 | } 82 | } 83 | 84 | .custom-button { 85 | display: inline-block; 86 | line-height: 1; 87 | white-space: nowrap; 88 | cursor: pointer; 89 | background: #fff; 90 | color: #fff; 91 | -webkit-appearance: none; 92 | text-align: center; 93 | box-sizing: border-box; 94 | outline: 0; 95 | margin: 0; 96 | padding: 10px 15px; 97 | font-size: 14px; 98 | border-radius: 4px; 99 | } 100 | -------------------------------------------------------------------------------- /item/src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | // cover some element-ui styles 2 | 3 | .el-breadcrumb__inner, 4 | .el-breadcrumb__inner a { 5 | font-weight: 400 !important; 6 | } 7 | 8 | .el-upload { 9 | input[type="file"] { 10 | display: none !important; 11 | } 12 | } 13 | 14 | .el-upload__input { 15 | display: none; 16 | } 17 | 18 | .cell { 19 | .el-tag { 20 | margin-right: 0px; 21 | } 22 | } 23 | 24 | .small-padding { 25 | .cell { 26 | padding-left: 5px; 27 | padding-right: 5px; 28 | } 29 | } 30 | 31 | .fixed-width { 32 | .el-button--mini { 33 | padding: 7px 10px; 34 | min-width: 60px; 35 | } 36 | } 37 | 38 | .status-col { 39 | .cell { 40 | padding: 0 10px; 41 | text-align: center; 42 | 43 | .el-tag { 44 | margin-right: 0px; 45 | } 46 | } 47 | } 48 | 49 | // to fixed https://github.com/ElemeFE/element/issues/2461 50 | .el-dialog { 51 | transform: none; 52 | left: 0; 53 | position: relative; 54 | margin: 0 auto; 55 | } 56 | 57 | // refine element ui upload 58 | .upload-container { 59 | .el-upload { 60 | width: 100%; 61 | 62 | .el-upload-dragger { 63 | width: 100%; 64 | height: 200px; 65 | } 66 | } 67 | } 68 | 69 | // dropdown 70 | .el-dropdown-menu { 71 | a { 72 | display: block 73 | } 74 | } 75 | 76 | // fix date-picker ui bug in filter-item 77 | .el-range-editor.el-input__inner { 78 | display: inline-flex !important; 79 | } 80 | 81 | // to fix el-date-picker css style 82 | .el-range-separator { 83 | box-sizing: content-box; 84 | } 85 | -------------------------------------------------------------------------------- /item/src/styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * I think element-ui's default theme color is too light for long-term use. 3 | * So I modified the default color and you can modify it to your liking. 4 | **/ 5 | /* theme color */ 6 | $--color-primary: #1890ff; 7 | $--color-success: #13ce66; 8 | $--color-warning: #ffba00; 9 | $--color-danger: #ff4949; 10 | // $--color-info: #1E1E1E; 11 | 12 | $--button-font-weight: 400; 13 | 14 | // $--color-text-regular: #1f2d3d; 15 | 16 | $--border-color-light: #dfe4ed; 17 | $--border-color-lighter: #e6ebf5; 18 | 19 | $--table-border: 1px solid #dfe6ec; 20 | 21 | /* icon font path, required */ 22 | $--font-path: "~element-ui/lib/theme-chalk/fonts"; 23 | 24 | @import "~element-ui/packages/theme-chalk/src/index"; 25 | // the :export directive is the magic sauce for webpack 26 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 27 | 28 | 29 | 30 | 31 | :export { 32 | theme: $--color-primary; 33 | } 34 | -------------------------------------------------------------------------------- /item/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | @import './mixin.scss'; 3 | @import './transition.scss'; 4 | @import './element-ui.scss'; 5 | @import './sidebar.scss'; 6 | @import './btn.scss'; 7 | 8 | body { 9 | height: 100%; 10 | -moz-osx-font-smoothing: grayscale; 11 | -webkit-font-smoothing: antialiased; 12 | text-rendering: optimizeLegibility; 13 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 14 | } 15 | 16 | label { 17 | font-weight: 700; 18 | } 19 | 20 | html { 21 | height: 100%; 22 | box-sizing: border-box; 23 | } 24 | 25 | #app { 26 | height: 100%; 27 | } 28 | 29 | *, 30 | *:before, 31 | *:after { 32 | box-sizing: inherit; 33 | } 34 | 35 | .no-padding { 36 | padding: 0px !important; 37 | } 38 | 39 | .padding-content { 40 | padding: 4px 0; 41 | } 42 | 43 | a:focus, 44 | a:active { 45 | outline: none; 46 | } 47 | 48 | a, 49 | a:focus, 50 | a:hover { 51 | cursor: pointer; 52 | color: inherit; 53 | text-decoration: none; 54 | } 55 | 56 | div:focus { 57 | outline: none; 58 | } 59 | 60 | .fr { 61 | float: right; 62 | } 63 | 64 | .fl { 65 | float: left; 66 | } 67 | 68 | .pr-5 { 69 | padding-right: 5px; 70 | } 71 | 72 | .pl-5 { 73 | padding-left: 5px; 74 | } 75 | 76 | .block { 77 | display: block; 78 | } 79 | 80 | .pointer { 81 | cursor: pointer; 82 | } 83 | 84 | .inlineBlock { 85 | display: block; 86 | } 87 | 88 | .clearfix { 89 | &:after { 90 | visibility: hidden; 91 | display: block; 92 | font-size: 0; 93 | content: " "; 94 | clear: both; 95 | height: 0; 96 | } 97 | } 98 | 99 | aside { 100 | background: #eef1f6; 101 | padding: 8px 24px; 102 | margin-bottom: 20px; 103 | border-radius: 2px; 104 | display: block; 105 | line-height: 32px; 106 | font-size: 16px; 107 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 108 | color: #2c3e50; 109 | -webkit-font-smoothing: antialiased; 110 | -moz-osx-font-smoothing: grayscale; 111 | 112 | a { 113 | color: #337ab7; 114 | cursor: pointer; 115 | 116 | &:hover { 117 | color: rgb(32, 160, 255); 118 | } 119 | } 120 | } 121 | 122 | //main-container全局样式 123 | .app-container { 124 | padding: 20px; 125 | } 126 | 127 | .components-container { 128 | margin: 30px 50px; 129 | position: relative; 130 | } 131 | 132 | .pagination-container { 133 | margin-top: 30px; 134 | } 135 | 136 | .text-center { 137 | text-align: center 138 | } 139 | 140 | .sub-navbar { 141 | height: 50px; 142 | line-height: 50px; 143 | position: relative; 144 | width: 100%; 145 | text-align: right; 146 | padding-right: 20px; 147 | transition: 600ms ease position; 148 | background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); 149 | 150 | .subtitle { 151 | font-size: 20px; 152 | color: #fff; 153 | } 154 | 155 | &.draft { 156 | background: #d0d0d0; 157 | } 158 | 159 | &.deleted { 160 | background: #d0d0d0; 161 | } 162 | } 163 | 164 | .link-type, 165 | .link-type:focus { 166 | color: #337ab7; 167 | cursor: pointer; 168 | 169 | &:hover { 170 | color: rgb(32, 160, 255); 171 | } 172 | } 173 | 174 | .filter-container { 175 | padding-bottom: 10px; 176 | 177 | .filter-item { 178 | display: inline-block; 179 | vertical-align: middle; 180 | margin-bottom: 10px; 181 | } 182 | } 183 | 184 | //refine vue-multiselect plugin 185 | .multiselect { 186 | line-height: 16px; 187 | } 188 | 189 | .multiselect--active { 190 | z-index: 1000 !important; 191 | } 192 | -------------------------------------------------------------------------------- /item/src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | 14 | &::-webkit-scrollbar { 15 | width: 6px; 16 | } 17 | 18 | &::-webkit-scrollbar-thumb { 19 | background: #99a9bf; 20 | border-radius: 20px; 21 | } 22 | } 23 | 24 | @mixin relative { 25 | position: relative; 26 | width: 100%; 27 | height: 100%; 28 | } 29 | 30 | @mixin pct($pct) { 31 | width: #{$pct}; 32 | position: relative; 33 | margin: 0 auto; 34 | } 35 | 36 | @mixin triangle($width, $height, $color, $direction) { 37 | $width: $width/2; 38 | $color-border-style: $height solid $color; 39 | $transparent-border-style: $width solid transparent; 40 | height: 0; 41 | width: 0; 42 | 43 | @if $direction==up { 44 | border-bottom: $color-border-style; 45 | border-left: $transparent-border-style; 46 | border-right: $transparent-border-style; 47 | } 48 | 49 | @else if $direction==right { 50 | border-left: $color-border-style; 51 | border-top: $transparent-border-style; 52 | border-bottom: $transparent-border-style; 53 | } 54 | 55 | @else if $direction==down { 56 | border-top: $color-border-style; 57 | border-left: $transparent-border-style; 58 | border-right: $transparent-border-style; 59 | } 60 | 61 | @else if $direction==left { 62 | border-right: $color-border-style; 63 | border-top: $transparent-border-style; 64 | border-bottom: $transparent-border-style; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /item/src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | // global transition css 2 | 3 | /* fade */ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /* fade-transform */ 15 | .fade-transform-leave-active, 16 | .fade-transform-enter-active { 17 | transition: all .5s; 18 | } 19 | 20 | .fade-transform-enter { 21 | opacity: 0; 22 | transform: translateX(-30px); 23 | } 24 | 25 | .fade-transform-leave-to { 26 | opacity: 0; 27 | transform: translateX(30px); 28 | } 29 | 30 | /* breadcrumb transition */ 31 | .breadcrumb-enter-active, 32 | .breadcrumb-leave-active { 33 | transition: all .5s; 34 | } 35 | 36 | .breadcrumb-enter, 37 | .breadcrumb-leave-active { 38 | opacity: 0; 39 | transform: translateX(20px); 40 | } 41 | 42 | .breadcrumb-move { 43 | transition: all .5s; 44 | } 45 | 46 | .breadcrumb-leave-active { 47 | position: absolute; 48 | } 49 | -------------------------------------------------------------------------------- /item/src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // base color 2 | $blue:#324157; 3 | $light-blue:#3A71A8; 4 | $red:#C03639; 5 | $pink: #E65D6E; 6 | $green: #30B08F; 7 | $tiffany: #4AB7BD; 8 | $yellow:#FEC171; 9 | $panGreen: #30B08F; 10 | 11 | // sidebar 12 | //$menuText:#bfcbd9; 13 | //$menuActiveText:#409EFF; 14 | //$subMenuActiveText:#f4f4f5; // https://github.com/ElemeFE/element/issues/12951 15 | 16 | //$menuBg:#304156; 17 | 18 | 19 | //$subMenuBg: #bd0d8e; 20 | //$subMenuHover:#001528; 21 | 22 | $sideBarWidth: 210px; 23 | 24 | // the :export directive is the magic sauce for webpack 25 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 26 | 27 | $menuBg:var(--menuBg-color, #304156);//默认背景 28 | $menuSubBg:var(--menuSubBg-color, #304156);//展开内部背景颜色 29 | $menuText:var(--menuText-color, #bfcad5);//默认文字 30 | $menuActiveText:var(--menuActiveText-color, #409eff);//选中的文字 31 | $menuSubActiveText:var(--menuSubActiveText-color, #fff);//展开选中父级的文字颜色 32 | $menuHoverBg:var(--menuHoverBg-color, #001528);//hover背景颜色 33 | // the :export directive is the magic sauce for webpack 34 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 35 | :export { 36 | menuBg: $menuBg;//默认背景颜色 37 | subMenuBg:$menuSubBg;//展开内部背景颜色 38 | menuText: $menuText;//默认文字颜色 39 | menuActiveText: $menuActiveText;//选中后文字颜色 40 | subMenuActiveText: $menuSubActiveText;//展开选中父级的文字颜色 41 | menuHover: $menuHoverBg;//hover颜色 42 | subMenuHover: $menuHoverBg;//展开内部hover颜色 43 | sideBarWidth: $sideBarWidth; 44 | } 45 | -------------------------------------------------------------------------------- /item/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'Admin-Token' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /item/src/utils/axios.js: -------------------------------------------------------------------------------- 1 | // 1.引入vue 2 | // import Vue from 'vue' 3 | // 2.引入axios库 4 | import requestAxios from "axios"; 5 | import router from "@/router/index"; 6 | import store from '@/store'//引入store(vuex) 7 | import { Message } from "element-ui"; 8 | import { getToken, setToken, removeToken } from "@/utils/auth"; 9 | 10 | requestAxios.defaults.timeout = 5000; // 请求超时时间 11 | 12 | requestAxios.interceptors.request.use( 13 | // 请求拦截 14 | (config) => { 15 | if (getToken()) config.headers.common["token"] = getToken(); 16 | return config; 17 | }, 18 | 19 | (err) => { 20 | return Promise.reject(err); 21 | } 22 | ); 23 | 24 | requestAxios.interceptors.response.use( 25 | (response) => { 26 | // 相应拦截 27 | let { data } = response; 28 | if (data.code == -1) { 29 | Message.error(data.msg || "请求异常!"); 30 | return Promise.reject(data); 31 | } 32 | if (data.code == 203) { 33 | Message.error(data.msg || "登陆失效!"); 34 | removeToken(); 35 | store.dispatch('user/logout'); 36 | router.push("/login"); 37 | return Promise.reject(data); 38 | } 39 | try { 40 | if (data.data.token) setToken(data.data.token); 41 | } catch (err) { 42 | 43 | } 44 | return response; 45 | }, 46 | (err) => { 47 | Message.error("请求异常!!"); 48 | return Promise.reject(err); 49 | } 50 | ); 51 | // requestAxios.defaults.baseURL="" 52 | const axios = function ({ path, method = "POST", data = {} } = {}) { 53 | return new Promise((resolve, reject) => { 54 | let datas = { params: { ...data } }; 55 | if (method === "POST") datas = { ...{ data } }; 56 | requestAxios({ 57 | url:process.env.VUE_APP_BASE_API+path, 58 | method, 59 | ...datas, 60 | }) 61 | .then((res) => { 62 | resolve(res.data); 63 | }) 64 | .catch((err) => { 65 | reject(err); 66 | }); 67 | }); 68 | }; 69 | export default axios; 70 | -------------------------------------------------------------------------------- /item/src/utils/clipboard.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Clipboard from 'clipboard' 3 | 4 | function clipboardSuccess() { 5 | Vue.prototype.$message({ 6 | message: 'Copy successfully', 7 | type: 'success', 8 | duration: 1500 9 | }) 10 | } 11 | 12 | function clipboardError() { 13 | Vue.prototype.$message({ 14 | message: 'Copy failed', 15 | type: 'error' 16 | }) 17 | } 18 | 19 | export default function handleClipboard(text, event) { 20 | const clipboard = new Clipboard(event.target, { 21 | text: () => text 22 | }) 23 | clipboard.on('success', () => { 24 | clipboardSuccess() 25 | clipboard.destroy() 26 | }) 27 | clipboard.on('error', () => { 28 | clipboardError() 29 | clipboard.destroy() 30 | }) 31 | clipboard.onClick(event) 32 | } 33 | -------------------------------------------------------------------------------- /item/src/utils/error-log.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store' 3 | import { isString, isArray } from '@/utils/validate' 4 | import settings from '@/settings' 5 | 6 | // you can set in settings.js 7 | // errorLog:'production' | ['production', 'development'] 8 | const { errorLog: needErrorLog } = settings 9 | 10 | function checkNeed() { 11 | const env = process.env.NODE_ENV 12 | if (isString(needErrorLog)) { 13 | return env === needErrorLog 14 | } 15 | if (isArray(needErrorLog)) { 16 | return needErrorLog.includes(env) 17 | } 18 | return false 19 | } 20 | 21 | if (checkNeed()) { 22 | Vue.config.errorHandler = function(err, vm, info, a) { 23 | // Don't ask me why I use Vue.nextTick, it just a hack. 24 | // detail see https://forum.vuejs.org/t/dispatch-in-vue-config-errorhandler-has-some-problem/23500 25 | Vue.nextTick(() => { 26 | store.dispatch('errorLog/addErrorLog', { 27 | err, 28 | vm, 29 | info, 30 | url: window.location.href 31 | }) 32 | console.error(err, info) 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /item/src/utils/get-page-title.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | import i18n from '@/lang' 3 | 4 | const title = defaultSettings.title || 'Vue Element Admin' 5 | 6 | export default function getPageTitle(key) { 7 | const hasKey = i18n.te(`route.${key}`) 8 | if (hasKey) { 9 | const pageName = i18n.t(`route.${key}`) 10 | return `${pageName} - ${title}` 11 | } 12 | return `${title}` 13 | } 14 | -------------------------------------------------------------------------------- /item/src/utils/i18n.js: -------------------------------------------------------------------------------- 1 | // translate router.meta.title, be used in breadcrumb sidebar tagsview 2 | export function generateTitle(title) { 3 | const hasKey = this.$te('route.' + title) 4 | 5 | if (hasKey) { 6 | // $t :this method from vue-i18n, inject in @/lang/index.js 7 | const translatedTitle = this.$t('route.' + title) 8 | 9 | return translatedTitle 10 | } 11 | return title 12 | } 13 | -------------------------------------------------------------------------------- /item/src/utils/open-window.js: -------------------------------------------------------------------------------- 1 | /** 2 | *Created by PanJiaChen on 16/11/29. 3 | * @param {Sting} url 4 | * @param {Sting} title 5 | * @param {Number} w 6 | * @param {Number} h 7 | */ 8 | export default function openWindow(url, title, w, h) { 9 | // Fixes dual-screen position Most browsers Firefox 10 | const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left 11 | const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top 12 | 13 | const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width 14 | const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height 15 | 16 | const left = ((width / 2) - (w / 2)) + dualScreenLeft 17 | const top = ((height / 2) - (h / 2)) + dualScreenTop 18 | const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left) 19 | 20 | // Puts focus on the newWindow 21 | if (window.focus) { 22 | newWindow.focus() 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /item/src/utils/permission.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | /** 4 | * 菜单权限字符 5 | * @param {Array} value 6 | * @returns {Boolean} 7 | */ 8 | export function checkPermi(value=[]) { 9 | try { 10 | const hasPermission = value.some(permission => { 11 | return store.getters.getRole.includes(permission) 12 | }); 13 | return hasPermission 14 | }catch (e) { 15 | return false 16 | } 17 | 18 | } 19 | 20 | /** 21 | * 角色权限字符 22 | * @param {Array} value 23 | * @param {Boolean} admin 总管理是否也要遵守 24 | * @returns {Boolean} 25 | */ 26 | export function checkRole(value=[],admin=false) { 27 | try { 28 | if(store.getters.getAdmin&&!admin) return true; 29 | const hasPermission = value.some(permission => { 30 | return store.getters.roles.includes(permission) 31 | }); 32 | return hasPermission 33 | }catch (e) { 34 | return false 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /item/src/utils/scroll-to.js: -------------------------------------------------------------------------------- 1 | Math.easeInOutQuad = function(t, b, c, d) { 2 | t /= d / 2 3 | if (t < 1) { 4 | return c / 2 * t * t + b 5 | } 6 | t-- 7 | return -c / 2 * (t * (t - 2) - 1) + b 8 | } 9 | 10 | // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts 11 | var requestAnimFrame = (function() { 12 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } 13 | })() 14 | 15 | /** 16 | * Because it's so fucking difficult to detect the scrolling element, just move them all 17 | * @param {number} amount 18 | */ 19 | function move(amount) { 20 | document.documentElement.scrollTop = amount 21 | document.body.parentNode.scrollTop = amount 22 | document.body.scrollTop = amount 23 | } 24 | 25 | function position() { 26 | return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop 27 | } 28 | 29 | /** 30 | * @param {number} to 31 | * @param {number} duration 32 | * @param {Function} callback 33 | */ 34 | export function scrollTo(to, duration, callback) { 35 | const start = position() 36 | const change = to - start 37 | const increment = 20 38 | let currentTime = 0 39 | duration = (typeof (duration) === 'undefined') ? 500 : duration 40 | var animateScroll = function() { 41 | // increment the time 42 | currentTime += increment 43 | // find the value with the quadratic in-out easing function 44 | var val = Math.easeInOutQuad(currentTime, start, change, duration) 45 | // move the document.body 46 | move(val) 47 | // do the animation unless its over 48 | if (currentTime < duration) { 49 | requestAnimFrame(animateScroll) 50 | } else { 51 | if (callback && typeof (callback) === 'function') { 52 | // the animation is done so lets callback 53 | callback() 54 | } 55 | } 56 | } 57 | animateScroll() 58 | } 59 | -------------------------------------------------------------------------------- /item/src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by PanJiaChen on 16/11/18. 3 | */ 4 | 5 | /** 6 | * @param {string} path 7 | * @returns {Boolean} 8 | */ 9 | export function isExternal(path) { 10 | return /^(https?:|mailto:|tel:)/.test(path) 11 | } 12 | 13 | /** 14 | * @param {string} str 15 | * @returns {Boolean} 16 | */ 17 | export function validUsername(str) { 18 | const valid_map = ['admin', 'editor'] 19 | return valid_map.indexOf(str.trim()) >= 0 20 | } 21 | 22 | /** 23 | * @param {string} url 24 | * @returns {Boolean} 25 | */ 26 | export function validURL(url) { 27 | const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ 28 | return reg.test(url) 29 | } 30 | 31 | /** 32 | * @param {string} str 33 | * @returns {Boolean} 34 | */ 35 | export function validLowerCase(str) { 36 | const reg = /^[a-z]+$/ 37 | return reg.test(str) 38 | } 39 | 40 | /** 41 | * @param {string} str 42 | * @returns {Boolean} 43 | */ 44 | export function validUpperCase(str) { 45 | const reg = /^[A-Z]+$/ 46 | return reg.test(str) 47 | } 48 | 49 | /** 50 | * @param {string} str 51 | * @returns {Boolean} 52 | */ 53 | export function validAlphabets(str) { 54 | const reg = /^[A-Za-z]+$/ 55 | return reg.test(str) 56 | } 57 | 58 | /** 59 | * @param {string} email 60 | * @returns {Boolean} 61 | */ 62 | export function validEmail(email) { 63 | const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 64 | return reg.test(email) 65 | } 66 | 67 | /** 68 | * @param {string} str 69 | * @returns {Boolean} 70 | */ 71 | export function isString(str) { 72 | if (typeof str === 'string' || str instanceof String) { 73 | return true 74 | } 75 | return false 76 | } 77 | 78 | /** 79 | * @param {Array} arg 80 | * @returns {Boolean} 81 | */ 82 | export function isArray(arg) { 83 | if (typeof Array.isArray === 'undefined') { 84 | return Object.prototype.toString.call(arg) === '[object Array]' 85 | } 86 | return Array.isArray(arg) 87 | } 88 | -------------------------------------------------------------------------------- /item/src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 85 | 86 | -------------------------------------------------------------------------------- /item/src/views/error-log/components/ErrorTestA.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /item/src/views/error-log/components/ErrorTestB.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /item/src/views/error-log/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 28 | 29 | 34 | -------------------------------------------------------------------------------- /item/src/views/error-page/401.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | 30 | 69 | -------------------------------------------------------------------------------- /item/src/views/icons/element-icons.js: -------------------------------------------------------------------------------- 1 | const elementIcons = ['platform-eleme', 'eleme', 'delete-solid', 'delete', 's-tools', 'setting', 'user-solid', 'user', 'phone', 'phone-outline', 'more', 'more-outline', 'star-on', 'star-off', 's-goods', 'goods', 'warning', 'warning-outline', 'question', 'info', 'remove', 'circle-plus', 'success', 'error', 'zoom-in', 'zoom-out', 'remove-outline', 'circle-plus-outline', 'circle-check', 'circle-close', 's-help', 'help', 'minus', 'plus', 'check', 'close', 'picture', 'picture-outline', 'picture-outline-round', 'upload', 'upload2', 'download', 'camera-solid', 'camera', 'video-camera-solid', 'video-camera', 'message-solid', 'bell', 's-cooperation', 's-order', 's-platform', 's-fold', 's-unfold', 's-operation', 's-promotion', 's-home', 's-release', 's-ticket', 's-management', 's-open', 's-shop', 's-marketing', 's-flag', 's-comment', 's-finance', 's-claim', 's-custom', 's-opportunity', 's-data', 's-check', 's-grid', 'menu', 'share', 'd-caret', 'caret-left', 'caret-right', 'caret-bottom', 'caret-top', 'bottom-left', 'bottom-right', 'back', 'right', 'bottom', 'top', 'top-left', 'top-right', 'arrow-left', 'arrow-right', 'arrow-down', 'arrow-up', 'd-arrow-left', 'd-arrow-right', 'video-pause', 'video-play', 'refresh', 'refresh-right', 'refresh-left', 'finished', 'sort', 'sort-up', 'sort-down', 'rank', 'loading', 'view', 'c-scale-to-original', 'date', 'edit', 'edit-outline', 'folder', 'folder-opened', 'folder-add', 'folder-remove', 'folder-delete', 'folder-checked', 'tickets', 'document-remove', 'document-delete', 'document-copy', 'document-checked', 'document', 'document-add', 'printer', 'paperclip', 'takeaway-box', 'search', 'monitor', 'attract', 'mobile', 'scissors', 'umbrella', 'headset', 'brush', 'mouse', 'coordinate', 'magic-stick', 'reading', 'data-line', 'data-board', 'pie-chart', 'data-analysis', 'collection-tag', 'film', 'suitcase', 'suitcase-1', 'receiving', 'collection', 'files', 'notebook-1', 'notebook-2', 'toilet-paper', 'office-building', 'school', 'table-lamp', 'house', 'no-smoking', 'smoking', 'shopping-cart-full', 'shopping-cart-1', 'shopping-cart-2', 'shopping-bag-1', 'shopping-bag-2', 'sold-out', 'sell', 'present', 'box', 'bank-card', 'money', 'coin', 'wallet', 'discount', 'price-tag', 'news', 'guide', 'male', 'female', 'thumb', 'cpu', 'link', 'connection', 'open', 'turn-off', 'set-up', 'chat-round', 'chat-line-round', 'chat-square', 'chat-dot-round', 'chat-dot-square', 'chat-line-square', 'message', 'postcard', 'position', 'turn-off-microphone', 'microphone', 'close-notification', 'bangzhu', 'time', 'odometer', 'crop', 'aim', 'switch-button', 'full-screen', 'copy-document', 'mic', 'stopwatch', 'medal-1', 'medal', 'trophy', 'trophy-1', 'first-aid-kit', 'discover', 'place', 'location', 'location-outline', 'location-information', 'add-location', 'delete-location', 'map-location', 'alarm-clock', 'timer', 'watch-1', 'watch', 'lock', 'unlock', 'key', 'service', 'mobile-phone', 'bicycle', 'truck', 'ship', 'basketball', 'football', 'soccer', 'baseball', 'wind-power', 'light-rain', 'lightning', 'heavy-rain', 'sunrise', 'sunrise-1', 'sunset', 'sunny', 'cloudy', 'partly-cloudy', 'cloudy-and-sunny', 'moon', 'moon-night', 'dish', 'dish-1', 'food', 'chicken', 'fork-spoon', 'knife-fork', 'burger', 'tableware', 'sugar', 'dessert', 'ice-cream', 'hot-water', 'water-cup', 'coffee-cup', 'cold-drink', 'goblet', 'goblet-full', 'goblet-square', 'goblet-square-full', 'refrigerator', 'grape', 'watermelon', 'cherry', 'apple', 'pear', 'orange', 'coffee', 'ice-tea', 'ice-drink', 'milk-tea', 'potato-strips', 'lollipop', 'ice-cream-square', 'ice-cream-round'] 2 | 3 | export default elementIcons 4 | -------------------------------------------------------------------------------- /item/src/views/icons/index.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 71 | 72 | 105 | -------------------------------------------------------------------------------- /item/src/views/icons/svg-icons.js: -------------------------------------------------------------------------------- 1 | const req = require.context('../../icons/svg', false, /\.svg$/) 2 | const requireAll = requireContext => requireContext.keys() 3 | 4 | const re = /\.\/(.*)\.svg/ 5 | 6 | const svgIcons = requireAll(req).map(i => { 7 | return i.match(re)[1] 8 | }) 9 | 10 | export default svgIcons 11 | -------------------------------------------------------------------------------- /item/src/views/login/auth-redirect.vue: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /item/src/views/login/components/SocialSignin.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 39 | 40 | 73 | -------------------------------------------------------------------------------- /item/src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 83 | 84 | 85 | 144 | -------------------------------------------------------------------------------- /item/src/views/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /item/src/views/test/index.vue: -------------------------------------------------------------------------------- 1 | 70 | 153 | 158 | -------------------------------------------------------------------------------- /item/src/views/test/roleApi.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 43 | 44 | 47 | -------------------------------------------------------------------------------- /item/vue.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const defaultSettings = require('./src/settings.js') 4 | 5 | function resolve(dir) { 6 | return path.join(__dirname, dir) 7 | } 8 | 9 | const name = defaultSettings.title || '后台管理' // page title 10 | 11 | // If your port is set to 80, 12 | // use administrator privileges to execute the command line. 13 | // For example, Mac: sudo npm run 14 | // You can change the port by the following method: 15 | // port = 9527 npm run dev OR npm run dev --port = 9527 16 | const port = process.env.port || process.env.npm_config_port || 2023 // dev port 17 | 18 | // All configuration item explanations can be find in https://cli.vuejs.org/config/ 19 | module.exports = { 20 | /** 21 | * You will need to set publicPath if you plan to deploy your site under a sub path, 22 | * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/, 23 | * then publicPath should be set to "/bar/". 24 | * In most cases please use '/' !!! 25 | * Detail: https://cli.vuejs.org/config/#publicpath 26 | */ 27 | publicPath: '/', 28 | outputDir: 'dist', 29 | assetsDir: 'static', 30 | lintOnSave: false, 31 | productionSourceMap: false, 32 | devServer: { 33 | port: port, 34 | open: false, 35 | overlay: { 36 | warnings: false, 37 | errors: true 38 | } 39 | }, 40 | configureWebpack: { 41 | // provide the app's title in webpack's name field, so that 42 | // it can be accessed in index.html to inject the correct title. 43 | name: name, 44 | resolve: { 45 | alias: { 46 | '@': resolve('src') 47 | } 48 | } 49 | }, 50 | chainWebpack(config) { 51 | // it can improve the speed of the first screen, it is recommended to turn on preload 52 | // it can improve the speed of the first screen, it is recommended to turn on preload 53 | config.plugin('preload').tap(() => [ 54 | { 55 | rel: 'preload', 56 | // to ignore runtime.js 57 | // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171 58 | fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/], 59 | include: 'initial' 60 | } 61 | ]) 62 | 63 | // when there are many pages, it will cause too many meaningless requests 64 | config.plugins.delete('prefetch') 65 | 66 | // set svg-sprite-loader 67 | config.module 68 | .rule('svg') 69 | .exclude.add(resolve('src/icons')) 70 | .end() 71 | config.module 72 | .rule('icons') 73 | .test(/\.svg$/) 74 | .include.add(resolve('src/icons')) 75 | .end() 76 | .use('svg-sprite-loader') 77 | .loader('svg-sprite-loader') 78 | .options({ 79 | symbolId: 'icon-[name]' 80 | }) 81 | .end() 82 | 83 | config 84 | .when(process.env.NODE_ENV !== 'development', 85 | config => { 86 | config 87 | .plugin('ScriptExtHtmlWebpackPlugin') 88 | .after('html') 89 | .use('script-ext-html-webpack-plugin', [{ 90 | // `runtime` must same as runtimeChunk name. default is `runtime` 91 | inline: /runtime\..*\.js$/ 92 | }]) 93 | .end() 94 | config 95 | .optimization.splitChunks({ 96 | chunks: 'all', 97 | cacheGroups: { 98 | libs: { 99 | name: 'chunk-libs', 100 | test: /[\\/]node_modules[\\/]/, 101 | priority: 10, 102 | chunks: 'initial' // only package third parties that are initially dependent 103 | }, 104 | elementUI: { 105 | name: 'chunk-elementUI', // split elementUI into a single package 106 | priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app 107 | test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm 108 | }, 109 | commons: { 110 | name: 'chunk-commons', 111 | test: resolve('src/components'), // can customize your rules 112 | minChunks: 3, // minimum common number 113 | priority: 5, 114 | reuseExistingChunk: true 115 | } 116 | } 117 | }) 118 | // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk 119 | config.optimization.runtimeChunk('single') 120 | } 121 | ) 122 | } 123 | } 124 | --------------------------------------------------------------------------------