├── .env.development ├── .env.production ├── .eslintignore ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── api │ ├── money.js │ ├── sales.js │ └── user.js ├── assets │ ├── datas │ │ ├── area.json │ │ └── logs.json │ ├── echarts │ │ ├── china.js │ │ └── roma.min.js │ ├── github │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png │ └── img │ │ ├── 401.png │ │ ├── 404.png │ │ ├── 500.png │ │ ├── america.svg │ │ ├── avatar-2.jpg │ │ ├── avatar-3.png │ │ ├── avatar.png │ │ ├── backTop.png │ │ ├── bg9.jpg │ │ ├── china.svg │ │ ├── logo.png │ │ ├── orange.png │ │ ├── pro_map.gif │ │ ├── qcode.jpg │ │ ├── qq.png │ │ ├── toMPic02.png │ │ ├── wechat.jpg │ │ └── zan.png ├── components │ ├── backTop │ │ └── index.vue │ ├── echarts │ │ ├── barChart.vue │ │ ├── lineChart.vue │ │ ├── orderSource.vue │ │ ├── pieChart.vue │ │ ├── radarChart.vue │ │ └── theme │ │ │ └── westeros.json │ ├── iconSvg │ │ ├── IconSvg.vue │ │ └── index.js │ ├── pagination │ │ └── index.vue │ └── test.vue ├── directive │ └── permission │ │ ├── index.js │ │ └── permission.js ├── lang │ ├── en.js │ ├── index.js │ └── zh.js ├── layout │ ├── bread.vue │ ├── content.vue │ ├── footerNav.vue │ ├── headNav.vue │ ├── home.vue │ ├── index.js │ ├── leftMenu.vue │ └── topMenu.vue ├── main.js ├── mockjs │ ├── index.js │ ├── money.js │ ├── sales.js │ └── user.js ├── page │ ├── errorPage │ │ ├── 401.vue │ │ └── 404.vue │ ├── fundData │ │ ├── fundPosition.vue │ │ ├── incomePayPosition.vue │ │ └── typePosition.vue │ ├── fundList │ │ ├── chinaTabsList.vue │ │ ├── components │ │ │ ├── addFundDialog.vue │ │ │ ├── chinaTabsTable.vue │ │ │ └── searchItem.vue │ │ ├── data │ │ │ └── chinaTabs.json │ │ ├── fundList.vue │ │ └── moneyData │ │ │ └── index.vue │ ├── index │ │ ├── components │ │ │ ├── cardList.vue │ │ │ ├── commentList.vue │ │ │ ├── logList.vue │ │ │ └── salesTable.vue │ │ └── index.vue │ ├── infoManage │ │ ├── infoModify.vue │ │ └── infoShow.vue │ ├── login.vue │ ├── permission │ │ ├── components │ │ │ └── SwitchRoles.vue │ │ ├── directive.vue │ │ └── page.vue │ ├── share │ │ ├── components │ │ │ ├── hengShare.vue │ │ │ ├── index.js │ │ │ ├── infoShare.vue │ │ │ ├── inviteShare.vue │ │ │ ├── jianshuLeftShare.vue │ │ │ ├── jianshuShare.vue │ │ │ ├── juejinShare.vue │ │ │ ├── sinaShare.vue │ │ │ ├── wxCodeModal.vue │ │ │ └── yanShare.vue │ │ └── index.vue │ └── userList │ │ └── userList.vue ├── permission.js ├── router │ ├── index.js │ └── topRouter.js ├── store │ ├── index.js │ └── modules │ │ ├── menu.js │ │ ├── money.js │ │ ├── permission.js │ │ └── user.js ├── style │ ├── common.less │ ├── element-ui.less │ ├── scrollBar.less │ └── variables.less └── utils │ ├── auth.js │ ├── axios.js │ ├── env.js │ ├── mUtils.js │ └── share.js └── vue.config.js /.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV = development 2 | VUE_APP_URL = "" -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV = production 2 | VUE_APP_URL = "" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | src/* 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | <b>小爱Admin</b> 2 | 3 | **A magical vue element touzi admin.** 4 | 5 | **分支说明:** 6 | 7 | **master分支**:前后端统一开发的版本;可以用于学习nodejs+mongodb+express相关知识; 8 | 9 | **dev分支**:进行了前后端分离的版本;用户只关注于前端部分,可忽略服务端;下载下来,即可运行; 10 | 11 | <font color="#00dd00"> 12 | dev-permission分支: 13 | 增加了权限管理(包括页面权限和按钮权限)的功能和顶栏三级菜单显示,完全剥离nodejs后台,使用mockjs模拟数据,让用户只需关注前端,更容易上手学习。目前此分支为正常维护分支。如有需要,请大家clone本分支代码运行。</font> 14 | 15 | 16 | ## 项目博客文档 17 | 18 | 1.[vue-小爱ADMIN系列文章(一):用Vue-cli3+mockjs 实现后台管理权限和三级菜单功能](https://github.com/wdlhao/webBlog/blob/master/readmes/01.md) 19 | 20 | 2.[vue-小爱ADMIN系列文章(二):微信微博等分享,国际化,前端性能优化,nginx服务器部署](https://github.com/wdlhao/webBlog/blob/master/readmes/02.md) 21 | 22 | ## 提示 23 | 24 | 如果你觉得该项目对你有帮忙,记得给我点个赞**star**吧; 25 | 26 | ## About 27 | 28 | 本文主要讲解dev-permission分支内容: 29 | 30 | ```bash 31 | 如果对您对此项目有兴趣,可以点 "Star" 支持一下 谢谢! ^_^ 32 | 33 | 或者您可以 "follow" 一下,我会不断开源更多的有趣的项目 34 | 35 | 开发环境 windows 64 、nodejs 6.11.0 36 | 37 | 如有问题请直接在 Issues 中提,或者您发现问题并有非常好的解决方案,欢迎 PR 38 | ``` 39 | 40 | ## 技术栈 41 | 42 | **前端技术栈:** vue2 + vuex + vue-router + webpack + ES6/7 + less + element-ui 43 | 44 | **服务端技术栈:** mockjs 45 | 46 | ## 参考文档 47 | 48 | * [vue](https://vuejs.bootcss.com/v2/guide/):Vue是一套用于构建用户界面的渐进式框架。 49 | 50 | * [vuex](https://vuex.vuejs.org/zh/):Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。 51 | 52 | * [vue-router](https://router.vuejs.org/zh/):Vue Router 是 Vue.js 官方的路由管理器。 53 | 54 | * [webpack](https://webpack.js.org/concepts/):前端模块打包器。 55 | 56 | * [less](http://lesscss.cn/):Less 是一门 CSS 预处理语言,它扩展了 CSS 语言,增加了变量、Mixin、函数等特性,使 CSS 更易维护和扩展。 57 | 58 | * [element-ui](https://element.eleme.io/):Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。 59 | 60 | * [mockjs](https://github.com/nuysoft/Mock/wiki/Getting-Started):生成随机数据,拦截 Ajax 请求。 61 | 62 | ## 前序准备 63 | 64 | **运行前准备:** 65 | 66 | 由于此项目是基于nodejs的前后端结合项目,你需要进行nodejs的相关准备工作。项目运行之前,请确保系统已经安装以下应用: 67 | 68 | (1)、node (6.0 及以上版本)。使用细节,请参考:[node的下载及安装。](https://nodejs.org/en/download/) 69 | 70 | ## 开发: 71 | 72 | 1. git clone -b dev-permission <https://github.com/wdlhao/vue2-element-touzi-admin> (注意:要从dev-permission分支拉取代码) 73 | 74 | 2. cd vue2-element-touzi-admin 75 | 76 | 3. npm install 77 | 78 | **本地运行:** 79 | 80 | **npm run serve** 运行之后,会默认打开本地访问路径:<http://localhost:8012> 81 | 82 | **发布:** 83 | 84 | **npm run bulid** (生成打包之后的项目文件,此文件主要用于项目部署)。 85 | 86 | ## 演示 87 | 88 | 测试账号: 89 | 90 | 1. username: admin / password: 123456 91 | 2. username: editor / password: 123456 92 | 93 | 注意: 94 | 95 | admin:拥有最高权限,可以查看所有的页面和按钮; 96 | 97 | editor:只有被赋予权限的页面和按钮才可以看到; 98 | 99 | **温馨提示**: 100 | 101 | 1、项目图标来源: 102 | 103 | 本项目图标线上地址为://at.alicdn.com/t/font\_1258069\_e40c6mwl0x8.js, 在public/index.html中引入; 104 | 大家如需更换图标,可直接替换为自己iconfont线上的图标库地址即可; 105 | 106 | 2、执行npm run dev报错处理方案: 107 | 108 | 可能是你的本机安装nodejs版本过高,请卸载nodejs高版本,重新安装较低版本(如v10.9.0); 109 | 或者通过安装nvm来切换到较低版本也可以实现正常启动; 110 | 111 | ## 查看更多demo 112 | 113 |  114 |  115 |  116 |  117 |  118 | 119 | ## 技术答疑,交流QQ群 120 | 121 | 项目说明:小爱ADMIN 是完全开源免费的管理系统集成方案,可以直接应用于相关后台管理系统模板;很多重点地方都做了详细的注释和解释。如果你也一样喜欢前端开发,欢迎加入我们的讨论/学习群,群内可以提问答疑,分享学习资料; 122 | 123 | 欢迎添加群主微信和qq群答疑: 124 | 125 |  126 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ], 5 | sourceType: 'unambiguous' 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "dev": "vue-cli-service serve", 8 | "build": "vue-cli-service build", 9 | "build:prod": "vue-cli-service build --mode production", 10 | "build:dev": "vue-cli-service build --mode development", 11 | "lint": "vue-cli-service lint" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.21.2", 15 | "core-js": "^2.6.9", 16 | "echarts": "^5.4.0", 17 | "element-ui": "^2.11.1", 18 | "express": "^4.17.1", 19 | "js-cookie": "^2.2.1", 20 | "mockjs": "^1.0.1-beta3", 21 | "nprogress": "^0.2.0", 22 | "qrcodejs2": "0.0.2", 23 | "vue": "^2.6.10", 24 | "vue-i18n": "^8.14.0", 25 | "vue-router": "^3.1.2", 26 | "vuex": "^3.1.1" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.5.5", 30 | "@babel/plugin-transform-runtime": "^7.5.5", 31 | "@babel/preset-env": "^7.5.5", 32 | "@vue/cli-plugin-babel": "^3.11.0", 33 | "@vue/cli-plugin-eslint": "^3.11.0", 34 | "@vue/cli-service": "^3.11.0", 35 | "babel-eslint": "^10.0.2", 36 | "compression-webpack-plugin": "^10.0.0", 37 | "image-webpack-loader": "^8.1.0", 38 | "less": "^4.1.3", 39 | "less-loader": "^7.3.0", 40 | "terser-webpack-plugin": "^5.3.6", 41 | "vue-hot-reload-api": "^2.3.3", 42 | "vue-loader": "^15.7.1", 43 | "vue-template-compiler": "^2.6.10", 44 | "webpack-bundle-analyzer": "^4.7.0" 45 | }, 46 | "postcss": { 47 | "plugins": { 48 | "autoprefixer": {} 49 | } 50 | }, 51 | "browserslist": [ 52 | "> 1%", 53 | "last 2 versions" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html> 3 | <head> 4 | <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> 5 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 6 | <meta name="renderer" content="webkit"> 7 | <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> 8 | <!-- <meta HTTP-EQUIV="pragma" CONTENT="no-cache"> 9 | <meta HTTP-EQUIV="Cache-Control" CONTENT="no-cache, must-revalidate"> 10 | <meta HTTP-EQUIV="expires" CONTENT="0"> --> 11 | <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"> 12 | <title>小爱Admin</title> 13 | <!-- 使用CDN加速的CSS文件,配置在vue.config.js下 --> 14 | <% for (var i in 15 | htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %> 16 | <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" /> 17 | <% } %> 18 | </head> 19 | <body> 20 | <div id="app"></div> 21 | 22 | <!-- 使用CDN加速的JS文件,配置在vue.config.js下 --> 23 | <script src="//at.alicdn.com/t/font_1258069_e40c6mwl0x8.js"></script> 24 | <% for (var i in 25 | htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> 26 | <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script> 27 | <% } %> 28 | </body> 29 | </html> 30 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div id="app"> 3 | <router-view></router-view> 4 | </div> 5 | </template> 6 | 7 | <script> 8 | export default { 9 | name:'App' 10 | } 11 | </script> 12 | 13 | <style lang="less"> 14 | @import './style/variables'; 15 | @import './style/element-ui'; 16 | @import './style/common'; 17 | @import './style/scrollBar'; 18 | </style> 19 | -------------------------------------------------------------------------------- /src/api/money.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/axios' 2 | 3 | export function getMoneyIncomePay(params) { 4 | return request({ 5 | url: '/money/get', 6 | method: 'get', 7 | params: params 8 | }) 9 | } 10 | 11 | export function addMoney(params) { 12 | return request({ 13 | url: '/money/add', 14 | method: 'get', 15 | params: params 16 | }) 17 | } 18 | 19 | export function removeMoney(params) { 20 | return request({ 21 | url: '/money/remove', 22 | method: 'get', 23 | params: params 24 | }) 25 | } 26 | 27 | 28 | export function batchremoveMoney(params) { 29 | return request({ 30 | url: '/money/batchremove', 31 | method: 'get', 32 | params: params 33 | }) 34 | } 35 | 36 | export function updateMoney(params) { 37 | return request({ 38 | url: '/money/edit', 39 | method: 'get', 40 | params: params 41 | }) 42 | } 43 | 44 | // export const addUser = params => { return axios.get(`${base}/user/add`, { params: params }) } 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/api/sales.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/axios' 2 | 3 | export function getSalesTableList(params) { 4 | return request({ 5 | url: '/sales/get', 6 | method: 'get', 7 | params: params 8 | }) 9 | } -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/axios' 2 | 3 | 4 | export function login(params) { 5 | return request({ 6 | url: '/user/login', 7 | method: 'get', 8 | data:params 9 | }) 10 | } 11 | export function logout(params) { 12 | return request({ 13 | url: '/user/logout', 14 | method: 'get', 15 | data:params 16 | }) 17 | } 18 | 19 | 20 | export function getUserInfo(params) { 21 | return request({ 22 | url: '/user/info/get', 23 | method: 'get', 24 | data:params 25 | }) 26 | } 27 | 28 | export function getUserList(reqData) { 29 | return request({ 30 | url:'/user/list/get', 31 | method: 'get', 32 | data: reqData 33 | }) 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/assets/datas/logs.json: -------------------------------------------------------------------------------- 1 | { 2 | "success":true, 3 | "data": 4 | [ 5 | { "createTime":"2019-08-20","data":["从以下方面对前端性能展开优化:","使用插件TerserPlugin,去除所有的console信息","使用CompressionPlugin,开启gzip压缩","忽略生产环境打包文件,使用外链cdn","图片压缩","路由按需加载","使用optimization.minimize(true):压缩代码和optimization.splitChunks():分割代码"]}, 6 | { "createTime":"2019-08-14","data":["新增中英文切换功能","公共组件footerNav布局优化","顶部用户信息优化","新增回到顶部功能"]}, 7 | { "createTime":"2019-08-10","data":["项目布局更改为左右布局,右侧内容超出窗口可视范围,出现滚动条","项目401页面和404页面重新改版"]}, 8 | { "createTime":"2019-08-05","data":["项目401页面和404页面样式更新","剔除项目easy-mock模拟数据功能","项目登录之后的业务逻辑修改"]}, 9 | { "createTime":"2019-07-10","data":["首页数据导航区域重新改版及ui优化","首页新增项目更新日志列表展示","首页新增访问量和下载量柱状图","首页新增用户投资区域雷达图","首页新增用户价格列表","首页新增用户评论列表","首页新增本项目技术栈progress"]}, 10 | { "createTime":"2019-07-03","data":["新增分享功能组件","分享功能组件包含4组横向排列","完成放简书网站分享组件","完成仿掘金网站分享组件","完成仿新浪分享组件"]}, 11 | { "createTime":"2019-06-22","data":["项目logo的设计和使用","项目添加微信和加入qq群宣传图的设计"]} 12 | ] 13 | } -------------------------------------------------------------------------------- /src/assets/echarts/roma.min.js: -------------------------------------------------------------------------------- 1 | !function(e,o){"function"==typeof define&&define.amd?define(["exports","echarts"],o):"object"==typeof exports&&"string"!=typeof exports.nodeName?o(0,require("echarts/lib/echarts")):o(0,e.echarts)}(this,function(e,o){o?o.registerTheme("roma",{color:["#E01F54","#001852","#f5e8c8","#b8d2c7","#c6b38e","#a4d8c2","#f3d999","#d3758f","#dcc392","#2e4783","#82b6e9","#ff6347","#a092f1","#0a915d","#eaf889","#6699FF","#ff6666","#3cb371","#d5b158","#38b6b6"],visualMap:{color:["#e01f54","#e7dbc3"],textStyle:{color:"#333"}},candlestick:{itemStyle:{color:"#e01f54",color0:"#001852"},lineStyle:{width:1,color:"#f5e8c8",color0:"#b8d2c7"},areaStyle:{color:"#a4d8c2",color0:"#f3d999"}},graph:{itemStyle:{color:"#a4d8c2"},linkStyle:{color:"#f3d999"}},gauge:{axisLine:{lineStyle:{color:[[.2,"#E01F54"],[.8,"#b8d2c7"],[1,"#001852"]],width:8}}}}):"undefined"!=typeof console&&console&&console.error&&console.error("ECharts is not Loaded")}); -------------------------------------------------------------------------------- /src/assets/github/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/github/1.png -------------------------------------------------------------------------------- /src/assets/github/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/github/2.png -------------------------------------------------------------------------------- /src/assets/github/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/github/3.png -------------------------------------------------------------------------------- /src/assets/github/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/github/4.png -------------------------------------------------------------------------------- /src/assets/github/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/github/5.png -------------------------------------------------------------------------------- /src/assets/img/401.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/401.png -------------------------------------------------------------------------------- /src/assets/img/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/404.png -------------------------------------------------------------------------------- /src/assets/img/500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/500.png -------------------------------------------------------------------------------- /src/assets/img/america.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="iso-8859-1"?> 2 | <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> 3 | <svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" 4 | viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"> 5 | <rect style="fill:#F0F0F0;" width="512" height="512"/> 6 | <g> 7 | <rect y="64" style="fill:#D80027;" width="512" height="64"/> 8 | <rect y="192" style="fill:#D80027;" width="512" height="64"/> 9 | <rect y="320" style="fill:#D80027;" width="512" height="64"/> 10 | <rect y="448" style="fill:#D80027;" width="512" height="64"/> 11 | </g> 12 | <rect style="fill:#2E52B2;" width="256" height="275.69"/> 13 | <g> 14 | <polygon style="fill:#F0F0F0;" points="51.518,115.318 45.924,132.529 27.826,132.529 42.469,143.163 36.875,160.375 15 | 51.518,149.741 66.155,160.375 60.56,143.163 75.203,132.529 57.106,132.529 "/> 16 | <polygon style="fill:#F0F0F0;" points="57.106,194.645 51.518,177.434 45.924,194.645 27.826,194.645 42.469,205.279 17 | 36.875,222.49 51.518,211.857 66.155,222.49 60.56,205.279 75.203,194.645 "/> 18 | <polygon style="fill:#F0F0F0;" points="51.518,53.202 45.924,70.414 27.826,70.414 42.469,81.047 36.875,98.259 51.518,87.625 19 | 66.155,98.259 60.56,81.047 75.203,70.414 57.106,70.414 "/> 20 | <polygon style="fill:#F0F0F0;" points="128.003,115.318 122.409,132.529 104.311,132.529 118.954,143.163 113.36,160.375 21 | 128.003,149.741 142.64,160.375 137.045,143.163 151.689,132.529 133.591,132.529 "/> 22 | <polygon style="fill:#F0F0F0;" points="133.591,194.645 128.003,177.434 122.409,194.645 104.311,194.645 118.954,205.279 23 | 113.36,222.49 128.003,211.857 142.64,222.49 137.045,205.279 151.689,194.645 "/> 24 | <polygon style="fill:#F0F0F0;" points="210.076,194.645 204.489,177.434 198.894,194.645 180.797,194.645 195.44,205.279 25 | 189.845,222.49 204.489,211.857 219.125,222.49 213.531,205.279 228.174,194.645 "/> 26 | <polygon style="fill:#F0F0F0;" points="204.489,115.318 198.894,132.529 180.797,132.529 195.44,143.163 189.845,160.375 27 | 204.489,149.741 219.125,160.375 213.531,143.163 228.174,132.529 210.076,132.529 "/> 28 | <polygon style="fill:#F0F0F0;" points="128.003,53.202 122.409,70.414 104.311,70.414 118.954,81.047 113.36,98.259 29 | 128.003,87.625 142.64,98.259 137.045,81.047 151.689,70.414 133.591,70.414 "/> 30 | <polygon style="fill:#F0F0F0;" points="204.489,53.202 198.894,70.414 180.797,70.414 195.44,81.047 189.845,98.259 31 | 204.489,87.625 219.125,98.259 213.531,81.047 228.174,70.414 210.076,70.414 "/> 32 | </g> 33 | <g> 34 | </g> 35 | <g> 36 | </g> 37 | <g> 38 | </g> 39 | <g> 40 | </g> 41 | <g> 42 | </g> 43 | <g> 44 | </g> 45 | <g> 46 | </g> 47 | <g> 48 | </g> 49 | <g> 50 | </g> 51 | <g> 52 | </g> 53 | <g> 54 | </g> 55 | <g> 56 | </g> 57 | <g> 58 | </g> 59 | <g> 60 | </g> 61 | <g> 62 | </g> 63 | </svg> 64 | -------------------------------------------------------------------------------- /src/assets/img/avatar-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/avatar-2.jpg -------------------------------------------------------------------------------- /src/assets/img/avatar-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/avatar-3.png -------------------------------------------------------------------------------- /src/assets/img/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/avatar.png -------------------------------------------------------------------------------- /src/assets/img/backTop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/backTop.png -------------------------------------------------------------------------------- /src/assets/img/bg9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/bg9.jpg -------------------------------------------------------------------------------- /src/assets/img/china.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> 3 | <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" 4 | viewBox="-49 141 512 512" style="enable-background:new -49 141 512 512;" xml:space="preserve"> 5 | <style type="text/css"> 6 | .st0{fill:#D80027;} 7 | .st1{fill:#FFDA44;} 8 | </style> 9 | <rect x="-49" y="141" class="st0" width="512" height="512"/> 10 | 11 | <g> 12 | <polygon class="st1" points="91.1,296.8 113.2,364.8 184.7,364.8 126.9,406.9 149,474.9 91.1,432.9 33.2,474.9 55.4,406.9 13 | -2.5,364.8 69,364.8 "/> 14 | <polygon class="st1" points="254.5,537.5 237.6,516.7 212.6,526.4 227.1,503.9 210.2,483 236.1,489.9 250.7,467.4 252.1,494.2 15 | 278.1,501.1 253,510.7 "/> 16 | <polygon class="st1" points="288.1,476.5 296.1,450.9 274.2,435.4 301,435 308.9,409.4 317.6,434.8 344.4,434.5 322.9,450.5 17 | 331.5,475.9 309.6,460.4 "/> 18 | <polygon class="st1" points="333.4,328.9 321.6,353 340.8,371.7 314.3,367.9 302.5,391.9 297.9,365.5 271.3,361.7 295.1,349.2 19 | 290.5,322.7 309.7,341.4 "/> 20 | <polygon class="st1" points="255.2,255.9 253.2,282.6 278.1,292.7 252,299.1 250.1,325.9 236,303.1 209.9,309.5 227.2,289 21 | 213,266.3 237.9,276.4 "/> 22 | </g> 23 | </svg> 24 | -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/logo.png -------------------------------------------------------------------------------- /src/assets/img/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/orange.png -------------------------------------------------------------------------------- /src/assets/img/pro_map.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/pro_map.gif -------------------------------------------------------------------------------- /src/assets/img/qcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/qcode.jpg -------------------------------------------------------------------------------- /src/assets/img/qq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/qq.png -------------------------------------------------------------------------------- /src/assets/img/toMPic02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/toMPic02.png -------------------------------------------------------------------------------- /src/assets/img/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/wechat.jpg -------------------------------------------------------------------------------- /src/assets/img/zan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wdlhao/vue2-element-touzi-admin/7a73cd93e5e3b22590c650cd99a33a4bc430e3a7/src/assets/img/zan.png -------------------------------------------------------------------------------- /src/components/backTop/index.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div 3 | class="backTop" 4 | :style="{'opacity':opacity}" 5 | v-show="gotop" 6 | @mouseenter="enterBackTop" 7 | @mouseout="outBackTop" 8 | @click="handleScrollTop" 9 | > 10 | <div class="back"></div> 11 | </div> 12 | </template> 13 | 14 | 15 | <script> 16 | export default { 17 | name:'backTop', 18 | data(){ 19 | return { 20 | opacity:'.3', 21 | gotop:false, 22 | scrollHeight:100 23 | } 24 | }, 25 | props:['ele'], 26 | created(){ 27 | 28 | }, 29 | mounted(){ 30 | // 此处true需要加上,不加滚动事件可能绑定不成功 31 | window.addEventListener("scroll", this.handleScroll, true); 32 | }, 33 | methods:{ 34 | enterBackTop(){ 35 | this.opacity = '1'; 36 | }, 37 | outBackTop(){ 38 | this.opacity = '.3'; 39 | }, 40 | handleScroll(e) { 41 | let scrolltop = e.target.scrollTop; 42 | scrolltop > this.scrollHeight ? (this.gotop = true) : (this.gotop = false); 43 | }, 44 | handleScrollTop() { 45 | this.$nextTick(()=>{ 46 | this.ele.scrollTop = 0; 47 | }) 48 | } 49 | } 50 | } 51 | </script> 52 | 53 | <style lang="less" scoped> 54 | .backTop{ 55 | position: fixed; 56 | right: 50px; 57 | bottom: 50px; 58 | z-index: 10; 59 | cursor: pointer; 60 | .back{ 61 | background: url('../../assets/img/backTop.png') no-repeat; 62 | background-position: center; 63 | background-size: 16px; 64 | background-color: #1890ff; 65 | width: 40px; 66 | height: 40px; 67 | overflow: hidden; 68 | color: #fff; 69 | text-align: center; 70 | border-radius: 50%; 71 | transition: all .3s; 72 | box-shadow: 0 0 15px 1px rgba(69,65,78,.1) 73 | } 74 | } 75 | </style> 76 | -------------------------------------------------------------------------------- /src/components/echarts/barChart.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div :id="id" class="orderArea orderbarArea"></div> 3 | </template> 4 | 5 | <script> 6 | import echarts from 'echarts' 7 | import echartsTheme from "cps/echarts/theme/westeros.json"; 8 | export default { 9 | name:'barChat', 10 | data(){ 11 | return { 12 | id:'barChart', 13 | myChart:null, 14 | } 15 | }, 16 | mounted(){ 17 | this.loadChart(); 18 | }, 19 | beforeDestroy() { 20 | if (!this.myChart) { 21 | return 22 | } 23 | this.myChart.dispose(); 24 | this.myChart = null; 25 | }, 26 | methods: { 27 | loadChart(){ 28 | this.$nextTick(() => { 29 | echarts.registerTheme('westeros', echartsTheme) 30 | this.myChart = echarts.init(document.getElementById(this.id),'westeros'); 31 | this.myChart.setOption(this.initOption()); 32 | }) 33 | }, 34 | initOption(){ 35 | let option = { 36 | tooltip : { 37 | trigger: 'axis' 38 | }, 39 | legend: { 40 | data:['访问量','下载量'] 41 | }, 42 | xAxis : [ 43 | { 44 | type : 'category', 45 | data : ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'] 46 | } 47 | ], 48 | yAxis : [ 49 | { 50 | type : 'value' 51 | } 52 | ], 53 | series : [ 54 | { 55 | name:'访问量', 56 | type:'bar', 57 | data:[2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], 58 | markPoint : { 59 | data : [ 60 | {type : 'max', name: '最大值'}, 61 | {type : 'min', name: '最小值'} 62 | ] 63 | } 64 | }, 65 | { 66 | name:'下载量', 67 | type:'bar', 68 | data:[2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], 69 | markPoint : { 70 | data : [ 71 | {name : '年最高', value : 182.2, xAxis: 7, yAxis: 183}, 72 | {name : '年最低', value : 2.3, xAxis: 11, yAxis: 3} 73 | ] 74 | } 75 | } 76 | ] 77 | }; 78 | return option; 79 | }, 80 | }, 81 | watch: { 82 | } 83 | } 84 | </script> 85 | 86 | <style lang="less"> 87 | 88 | </style> 89 | -------------------------------------------------------------------------------- /src/components/echarts/lineChart.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div :id="id" class="orderArea"></div> 3 | </template> 4 | 5 | <script> 6 | import echarts from 'echarts' 7 | import echartsTheme from "cps/echarts/theme/westeros.json"; 8 | 9 | export default { 10 | data(){ 11 | return { 12 | id:"lineChart", 13 | myChart:null, 14 | } 15 | }, 16 | mounted(){ 17 | this.loadChart(); 18 | }, 19 | beforeDestroy() { 20 | if (!this.myChart) { 21 | return 22 | } 23 | this.myChart.dispose(); 24 | this.myChart = null; 25 | }, 26 | methods: { 27 | loadChart(){ 28 | this.$nextTick(() => { 29 | echarts.registerTheme('westeros', echartsTheme) 30 | this.myChart = echarts.init(document.getElementById(this.id),'westeros'); 31 | this.myChart.setOption(this.initOption()); 32 | }) 33 | }, 34 | initOption(){ 35 | let data = { 36 | title: { 37 | text: '' 38 | }, 39 | tooltip : { 40 | trigger: 'axis', 41 | axisPointer: { 42 | type: 'cross', 43 | label: { 44 | backgroundColor: '#6a7985' 45 | } 46 | } 47 | }, 48 | legend: { 49 | data:['股票','基金','债券','储蓄','期货'] 50 | }, 51 | grid: { 52 | left: '3%', 53 | right: '4%', 54 | bottom: '3%', 55 | containLabel: true 56 | }, 57 | xAxis : [ 58 | { 59 | type : 'category', 60 | boundaryGap : false, 61 | data : ['周一','周二','周三','周四','周五','周六','周日'] 62 | } 63 | ], 64 | yAxis : [ 65 | { 66 | type : 'value' 67 | } 68 | ], 69 | series : [ 70 | { 71 | name:'股票', 72 | type:'line', 73 | stack: '总量', 74 | areaStyle: {normal: {}}, 75 | data:[120, 132, 101, 134, 90, 230, 210] 76 | }, 77 | { 78 | name:'基金', 79 | type:'line', 80 | stack: '总量', 81 | areaStyle: {normal: {}}, 82 | data:[220, 182, 191, 234, 290, 330, 310] 83 | }, 84 | { 85 | name:'债券', 86 | type:'line', 87 | stack: '总量', 88 | areaStyle: {normal: {}}, 89 | data:[150, 232, 201, 154, 190, 330, 410] 90 | }, 91 | { 92 | name:'期货', 93 | type:'line', 94 | stack: '总量', 95 | areaStyle: {normal: {}}, 96 | data:[320, 332, 301, 334, 390, 330, 320] 97 | }, 98 | { 99 | name:'储蓄', 100 | type:'line', 101 | stack: '总量', 102 | label: { 103 | normal: { 104 | show: true, 105 | position: 'top' 106 | } 107 | }, 108 | areaStyle: {normal: {}}, 109 | data:[820, 932, 901, 934, 1290, 1330, 1320] 110 | } 111 | ] 112 | } 113 | return data; 114 | }, 115 | }, 116 | watch: { 117 | // id:()=>{ 118 | // this.initOption() 119 | // } 120 | } 121 | } 122 | </script> 123 | 124 | <style lang="less"> 125 | 126 | </style> 127 | -------------------------------------------------------------------------------- /src/components/echarts/orderSource.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div :id="id" style="width:100%;height:280px;"></div> 3 | </template> 4 | 5 | <script> 6 | import echarts from 'echarts' 7 | 8 | export default { 9 | data(){ 10 | return { 11 | myChart:null, 12 | } 13 | }, 14 | mounted(){ 15 | this.myChart = echarts.init(document.getElementById(this.id)); 16 | this.myChart.setOption(this.redyBin2Option(this.type)); 17 | }, 18 | props: ['id','type'], 19 | methods: { 20 | redyBin2Option(type){ 21 | let text,legend_data,series_data,inner_data; 22 | if(type == "ordersource"){ 23 | text = "用户投资途径"; 24 | legend_data = ['直达','营销广告','搜索引擎','邮件营销','联盟广告','谷歌'] 25 | inner_data = [ 26 | {value:335, name:'直达', selected:true}, 27 | {value:679, name:'营销广告'}, 28 | {value:1548, name:'搜索引擎'} 29 | ] 30 | series_data = [ 31 | {value:310, name:'邮件营销'}, 32 | {value:234, name:'联盟广告'}, 33 | {value:634, name:'谷歌'} 34 | ] 35 | }else{ 36 | text = "用户投资职业"; 37 | legend_data = ['金融人员','IT人员','保险人员','理财师','律师']; 38 | inner_data = [ 39 | {value:335, name:'金融人员', selected:true}, 40 | {value:679, name:'IT人员'}, 41 | {value:1548, name:'保险人员'} 42 | ] 43 | series_data = [ 44 | {value:310, name:'理财师'}, 45 | {value:528, name:'律师'}, 46 | ] 47 | } 48 | let data = { 49 | title : { 50 | text: text, 51 | subtext: '纯属虚构', 52 | x:'center' 53 | }, 54 | tooltip: { 55 | trigger: 'item', 56 | formatter: "{a} <br/>{b}: {c} ({d}%)" 57 | }, 58 | legend: { 59 | orient: 'vertical', 60 | x: 'left', 61 | data:legend_data 62 | }, 63 | series: [ 64 | { 65 | name:'访问来源', 66 | type:'pie', 67 | selectedMode: 'single', 68 | radius: [0, '30%'], 69 | center: ['50%', '60%'], 70 | label: { 71 | normal: { 72 | position: 'inner' 73 | } 74 | }, 75 | labelLine: { 76 | normal: { 77 | show: false 78 | } 79 | }, 80 | data:inner_data 81 | }, 82 | { 83 | name:'访问来源', 84 | type:'pie', 85 | radius: ['40%', '55%'], 86 | center: ['50%', '60%'], 87 | label: { 88 | normal: { 89 | backgroundColor: '#eee', 90 | borderColor: '#aaa', 91 | borderWidth: 1, 92 | borderRadius: 4, 93 | rich: { 94 | a: { 95 | color: '#999', 96 | lineHeight: 22, 97 | align: 'center' 98 | }, 99 | hr: { 100 | borderColor: '#aaa', 101 | width: '100%', 102 | borderWidth: 0.5, 103 | height: 0 104 | }, 105 | b: { 106 | fontSize: 16, 107 | lineHeight: 33 108 | }, 109 | per: { 110 | color: '#eee', 111 | backgroundColor: '#334455', 112 | padding: [2, 4], 113 | borderRadius: 2 114 | } 115 | } 116 | } 117 | }, 118 | data:series_data 119 | } 120 | ] 121 | } 122 | return data; 123 | } 124 | }, 125 | watch: { 126 | type:(v)=>{ 127 | this.redyBin2Option(v) 128 | } 129 | } 130 | } 131 | </script> 132 | 133 | <style lang="less"> 134 | 135 | </style> 136 | -------------------------------------------------------------------------------- /src/components/echarts/pieChart.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div :id="id" class="orderArea"></div> 3 | </template> 4 | 5 | <script> 6 | import echarts from 'echarts' 7 | import echartsTheme from "cps/echarts/theme/westeros.json"; 8 | 9 | export default { 10 | data(){ 11 | return { 12 | id:'ordertype', 13 | myChart:null, 14 | } 15 | }, 16 | props: ['type'], 17 | mounted(){ 18 | this.loadChart(); 19 | }, 20 | beforeDestroy() { 21 | if (!this.myChart) { 22 | return 23 | } 24 | this.myChart.dispose(); 25 | this.myChart = null; 26 | }, 27 | methods: { 28 | loadChart(){ 29 | this.$nextTick(() => { 30 | echarts.registerTheme('westeros', echartsTheme) 31 | this.myChart = echarts.init(document.getElementById(this.id),'westeros'); 32 | this.myChart.setOption(this.initOption(this.type)); 33 | }) 34 | }, 35 | initOption(type){ 36 | let text,legend_data,series_data; 37 | if(type == "ordertype"){ 38 | text = "用户投资类型"; 39 | legend_data = ['基金','股票','债券','储蓄','期货']; 40 | series_data = [ 41 | {value:335, name:'基金'}, 42 | {value:310, name:'股票'}, 43 | {value:234, name:'债券'}, 44 | {value:135, name:'储蓄'}, 45 | {value:1548, name:'期货'} 46 | ] 47 | }else{ 48 | text = "用户投资区域"; 49 | legend_data = ['华东区','华南区','华中区','华北区','西南区','东北区','台港澳']; 50 | series_data = [ 51 | {value:335, name:'华东区'}, 52 | {value:310, name:'华南区'}, 53 | {value:234, name:'华中区'}, 54 | {value:835, name:'华北区'}, 55 | {value:1548, name:'西南区'}, 56 | {value:335, name:'东北区'}, 57 | {value:454, name:'台港澳'} 58 | ] 59 | } 60 | let data = { 61 | title : { 62 | text: text, 63 | x:'center' 64 | }, 65 | tooltip : { 66 | trigger: 'item', 67 | formatter: "{a} <br/>{b} : {c} ({d}%)" 68 | }, 69 | legend: { 70 | orient: 'vertical', 71 | left: 'left', 72 | data: legend_data 73 | }, 74 | series : [ 75 | { 76 | name: '访问来源', 77 | type: 'pie', 78 | radius : '50%', 79 | center: ['50%', '60%'], 80 | data:series_data, 81 | itemStyle: { 82 | emphasis: { 83 | shadowBlur: 10, 84 | shadowOffsetX: 0, 85 | shadowColor: 'rgba(0, 0, 0, 0.5)' 86 | } 87 | } 88 | } 89 | ] 90 | } 91 | return data; 92 | }, 93 | }, 94 | watch: { 95 | type:(v)=>{ 96 | this.initOption(v) 97 | } 98 | } 99 | } 100 | </script> 101 | 102 | <style lang="less"> 103 | 104 | </style> 105 | -------------------------------------------------------------------------------- /src/components/echarts/radarChart.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div :id="id" class="orderArea"></div> 3 | </template> 4 | 5 | <script> 6 | import echarts from 'echarts' 7 | import echartsTheme from "cps/echarts/theme/westeros.json"; 8 | 9 | export default { 10 | name:'radarChart', 11 | data(){ 12 | return { 13 | id:"radarChart", 14 | myChart:null, 15 | } 16 | }, 17 | mounted(){ 18 | this.loadChart(); 19 | }, 20 | beforeDestroy() { 21 | if (!this.myChart) { 22 | return 23 | } 24 | this.myChart.dispose(); 25 | this.myChart = null; 26 | }, 27 | methods: { 28 | loadChart(){ 29 | this.$nextTick(() => { 30 | this.myChart = echarts.init(document.getElementById(this.id)); 31 | this.myChart.setOption(this.initOption()); 32 | }) 33 | }, 34 | initOption(){ 35 | let option = { 36 | radar: [{ 37 | name: { 38 | fontSize: 11 // 顶点字体大小 不能设置 radio 否则不能生效 39 | }, 40 | splitLine: { 41 | lineStyle: { 42 | color: '#00aaff' // 每一圈的边界颜色 43 | } 44 | }, 45 | axisLabel: { 46 | inside: true 47 | }, 48 | axisLine: { 49 | lineStyle: { 50 | color: '#00aaff' // 半径线颜色 51 | } 52 | }, 53 | splitArea: { 54 | areaStyle: { 55 | // color: ['#00aaff', '#0099ff', '#00aaff', '#0099ff', '#00aaff'] // 每一圈的颜色 56 | } 57 | }, 58 | indicator: [ 59 | {text: '东北区域', max: 100, color: '#87DE75'}, // 选中颜色 60 | {text: '华南区域', max: 100,color: '#FFA3A1'}, 61 | {text: '华中区域', max: 100,color: '#FF9900'}, 62 | {text: '华北区域', max: 100,color: '#5FB4FA'}, 63 | {text: '西北区域', max: 100,color: '#a9d86e'}, 64 | {text: '西南区域', max: 100,color: '#FF6C60'}, 65 | {text: '东北区域', max: 100,color: '#18a689'}, 66 | {text: '港澳台', max: 100,color: '#3b5999'} 67 | ] 68 | }], 69 | series: [{ 70 | type: 'radar', 71 | data: [{ 72 | value: [60, 73, 85, 40, 50, 100], 73 | areaStyle: { 74 | normal: { 75 | opacity: 0.8, // 图表透明度 76 | color: '#87DE75' // 图表颜色 77 | } 78 | }, 79 | lineStyle: { 80 | color: '#6397ff' // 图表边框颜色 81 | }, 82 | label: { 83 | normal: { 84 | show: true, 85 | color: '#6397ff', // 顶点数字颜色 86 | fontSize: 16, // 顶点数字大小 87 | formatter: function (params) { 88 | return params.value 89 | } 90 | } 91 | } 92 | }] 93 | }] 94 | } 95 | return option; 96 | }, 97 | }, 98 | watch: { 99 | } 100 | } 101 | </script> 102 | 103 | <style lang="less"> 104 | 105 | </style> 106 | -------------------------------------------------------------------------------- /src/components/iconSvg/IconSvg.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <svg class="svg-icon" aria-hidden="true"> 3 | <use :xlink:href="iconName"></use> 4 | </svg> 5 | </template> 6 | 7 | <script> 8 | export default { 9 | name: 'icon-svg', 10 | props: { 11 | iconClass: { 12 | type: String, 13 | required: true 14 | } 15 | }, 16 | computed: { 17 | iconName() { 18 | return `#${this.iconClass}` 19 | } 20 | } 21 | } 22 | </script> 23 | 24 | <style> 25 | .svg-icon { 26 | width: 1em; 27 | height: 1em; 28 | font-size: 18px; 29 | vertical-align: -0.15em; 30 | fill: currentColor; 31 | overflow: hidden; 32 | } 33 | </style> 34 | -------------------------------------------------------------------------------- /src/components/iconSvg/index.js: -------------------------------------------------------------------------------- 1 | // 存放一些全局的组件 2 | import Vue from 'vue' 3 | import IconSvg from './IconSvg' 4 | //全局注册icon-svg 5 | Vue.component('icon-svg', IconSvg) 6 | -------------------------------------------------------------------------------- /src/components/pagination/index.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="pagination"> 3 | <el-pagination 4 | v-if='pageTotal > 0' 5 | background 6 | :page-sizes="paginations.pageSizes" 7 | :page-size="paginations.pageSize" 8 | :layout="paginations.layout" 9 | :total="pageTotal" 10 | :current-page='paginations.pageIndex' 11 | @current-change='handleCurrentChange' 12 | @size-change='handleSizeChange'> 13 | </el-pagination> 14 | </div> 15 | </template> 16 | 17 | 18 | <script> 19 | export default { 20 | name:'pagination', 21 | data(){ 22 | return { 23 | paginations: { 24 | pageIndex: 1, // 当前位于哪页 25 | pageSize: 20, // 1页显示多少条 26 | pageSizes: [5, 10, 15, 20], //每页显示多少条 27 | layout: "total, sizes, prev, pager, next, jumper" // 翻页属性 28 | }, 29 | } 30 | }, 31 | props:{ 32 | pageTotal:Number 33 | }, 34 | created(){ 35 | 36 | }, 37 | mounted(){ 38 | 39 | }, 40 | methods:{ 41 | // 上下分页 pageIndex 42 | handleCurrentChange(page){ 43 | this.$emit('handleCurrentChange',page); 44 | }, 45 | // 每页多少条切换 pageSize 46 | handleSizeChange(page_size){ 47 | this.$emit('handleSizeChange',page_size); 48 | } 49 | } 50 | } 51 | </script> 52 | 53 | <style lang="less" scoped> 54 | .pagination{ 55 | text-align: right; 56 | padding: 10px 18px; 57 | } 58 | </style> 59 | -------------------------------------------------------------------------------- /src/components/test.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div> 3 | <el-dialog v-bind="$attrs" v-on="$listeners" @open="onOpen" @close="onClose" title="Dialog Title"> 4 | <el-form ref="elForm" :model="formData" :rules="rules" size="medium" label-width="100px"> 5 | <el-form-item label="手机号" prop="mobile"> 6 | <el-input v-model="formData.mobile" placeholder="请输入手机号" :maxlength="11" show-word-limit clearable 7 | prefix-icon='el-icon-mobile' :style="{width: '100%'}"></el-input> 8 | </el-form-item> 9 | <el-form-item label="多行文本" prop="field101"> 10 | <el-input v-model="formData.field101" type="textarea" placeholder="请输入多行文本" 11 | :autosize="{minRows: 4, maxRows: 4}" :style="{width: '100%'}"></el-input> 12 | </el-form-item> 13 | <el-form-item label="计数器" prop="field102"> 14 | <el-input-number v-model="formData.field102" placeholder="计数器"></el-input-number> 15 | </el-form-item> 16 | <el-form-item label="下拉选择" prop="field103"> 17 | <el-select v-model="formData.field103" placeholder="请选择下拉选择" clearable :style="{width: '100%'}"> 18 | <el-option v-for="(item, index) in field103Options" :key="index" :label="item.label" 19 | :value="item.value" :disabled="item.disabled"></el-option> 20 | </el-select> 21 | </el-form-item> 22 | <el-form-item label="日期范围" prop="field105"> 23 | <el-date-picker type="daterange" v-model="formData.field105" format="yyyy-MM-dd" 24 | value-format="yyyy-MM-dd" :style="{width: '100%'}" start-placeholder="开始日期" end-placeholder="结束日期" 25 | range-separator="至" clearable></el-date-picker> 26 | </el-form-item> 27 | <el-form-item label="评分" prop="field106"> 28 | <el-rate v-model="formData.field106" allow-half show-score :disabled='true'></el-rate> 29 | </el-form-item> 30 | <el-row gutter="15"> 31 | </el-row> 32 | <el-form-item label="多选框组" prop="field104"> 33 | <el-checkbox-group v-model="formData.field104" size="medium"> 34 | <el-checkbox v-for="(item, index) in field104Options" :key="index" :label="item.value" 35 | :disabled="item.disabled">{{item.label}}</el-checkbox> 36 | </el-checkbox-group> 37 | </el-form-item> 38 | <el-form-item label="下拉选择" prop="field108"> 39 | <el-select v-model="formData.field108" placeholder="请选择下拉选择" clearable :style="{width: '100%'}"> 40 | <el-option v-for="(item, index) in field108Options" :key="index" :label="item.label" 41 | :value="item.value" :disabled="item.disabled"></el-option> 42 | </el-select> 43 | </el-form-item> 44 | </el-form> 45 | <div slot="footer"> 46 | <el-button @click="close">取消</el-button> 47 | <el-button type="primary" @click="handleConfirm">确定</el-button> 48 | </div> 49 | </el-dialog> 50 | </div> 51 | </template> 52 | <script> 53 | export default { 54 | inheritAttrs: false, 55 | components: {}, 56 | props: [], 57 | data() { 58 | return { 59 | formData: { 60 | mobile: '', 61 | field101: undefined, 62 | field102: undefined, 63 | field103: undefined, 64 | field105: null, 65 | field106: 0, 66 | field104: [], 67 | field108: undefined, 68 | }, 69 | rules: { 70 | mobile: [{ 71 | required: true, 72 | message: '请输入手机号', 73 | trigger: 'blur' 74 | }, { 75 | pattern: /^1(3|4|5|7|8|9)\d{9}$/, 76 | message: '手机号格式错误', 77 | trigger: 'blur' 78 | }], 79 | field101: [{ 80 | required: true, 81 | message: '请输入多行文本', 82 | trigger: 'blur' 83 | }], 84 | field102: [{ 85 | required: true, 86 | message: '计数器', 87 | trigger: 'blur' 88 | }], 89 | field103: [{ 90 | required: true, 91 | message: '请选择下拉选择', 92 | trigger: 'change' 93 | }], 94 | field105: [{ 95 | required: true, 96 | message: '日期范围不能为空', 97 | trigger: 'change' 98 | }], 99 | field106: [{ 100 | required: true, 101 | message: '评分不能为空', 102 | trigger: 'change' 103 | }], 104 | field104: [{ 105 | required: true, 106 | type: 'array', 107 | message: '请至少选择一个field104', 108 | trigger: 'change' 109 | }], 110 | field108: [{ 111 | required: true, 112 | message: '请选择下拉选择', 113 | trigger: 'change' 114 | }], 115 | }, 116 | field103Options: [{ 117 | "label": "选项一", 118 | "value": 1 119 | }, { 120 | "label": "选项二", 121 | "value": 2 122 | }], 123 | field104Options: [{ 124 | "label": "选项一", 125 | "value": 1 126 | }, { 127 | "label": "选项二", 128 | "value": 2 129 | }], 130 | field108Options: [{ 131 | "label": "选项一", 132 | "value": 1 133 | }, { 134 | "label": "选项二", 135 | "value": 2 136 | }], 137 | } 138 | }, 139 | computed: {}, 140 | watch: {}, 141 | created() {}, 142 | mounted() {}, 143 | methods: { 144 | onOpen() {}, 145 | onClose() { 146 | this.$refs['elForm'].resetFields() 147 | }, 148 | close() { 149 | this.$emit('update:visible', false) 150 | }, 151 | handleConfirm() { 152 | this.$refs['elForm'].validate(valid => { 153 | if (!valid) return 154 | this.close() 155 | }) 156 | }, 157 | } 158 | } 159 | 160 | </script> 161 | <style> 162 | .el-rate { 163 | display: inline-block; 164 | vertical-align: text-top; 165 | } 166 | 167 | </style> 168 | -------------------------------------------------------------------------------- /src/directive/permission/index.js: -------------------------------------------------------------------------------- 1 | import permission from './permission' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('permission', permission) 5 | } 6 | 7 | if (window.Vue) { 8 | window['permission'] = permission 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | permission.install = install 13 | export default permission 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/directive/permission/permission.js: -------------------------------------------------------------------------------- 1 | 2 | import store from '@/store' 3 | 4 | export default{ 5 | // inserted函数:当被绑定的元素插入到 DOM 中时…… 6 | inserted(el, binding, vnode) { 7 | const { value } = binding // 获取指令绑定的值; 8 | const roles = store.getters && store.getters.roles //用户本身的roles信息,arr; 9 | 10 | if (value && value instanceof Array && value.length > 0) { 11 | const permissionRoles = value 12 | 13 | const hasPermission = roles.some(role => { // 只要有一个满足即返回true 14 | return permissionRoles.includes(role) 15 | }) 16 | // 没有该指令,直接删除掉该指令元素;即页面不显示没有指令权限的按钮; 17 | if (!hasPermission) { 18 | el.parentNode && el.parentNode.removeChild(el) 19 | // 因项目需要,本指令remove其父元素;一般情况下,只隐藏其本身; 20 | } 21 | } else { 22 | throw new Error(`need roles! Like v-permission="['admin','editor']"`) 23 | } 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/lang/en.js: -------------------------------------------------------------------------------- 1 | const zh = { 2 | // layout 3 | commons: { 4 | xiaoai: 'Ai.', 5 | admin: 'Admin', 6 | editor: 'Editor', 7 | quit: 'Sign Out', 8 | hi: 'Hi', 9 | index: 'Dashboard', 10 | userManage: 'Users', 11 | share: 'Share', 12 | infoManage: 'Infos', 13 | infoShow: 'InfoShow', 14 | infoShow1: 'InfoShow1', 15 | infoShow2: 'InfoShow2', 16 | infoShow3: 'InfoShow3', 17 | infoShow4: 'InfoShow4', 18 | infoShow5: 'InfoShow5', 19 | infoModify: 'InfoModify', 20 | infoModify1:'InfoModify1', 21 | infoModify2:'InfoModify2', 22 | infoModify3:'InfoModify3', 23 | fundManage: 'Money', 24 | fundList: 'MoneyList', 25 | chinaTabsList: 'AreaList', 26 | fundData: 'FundData', 27 | fundPosition: 'FundPosition', 28 | typePosition: 'TypePosition', 29 | incomePayPosition: 'IncomePayPosition', 30 | permission: 'Permission', 31 | pagePer: 'PagePermission', 32 | directivePer: 'DirectivePermission', 33 | errorPage: 'ErrorPage', 34 | page401:'401', 35 | page404:'404', 36 | wechatNumber: 'wechat' 37 | }, 38 | index:{ 39 | yearLoss:'Year Loss', 40 | yearProfit:'Year Profit', 41 | potentialInvestor:'Potential Investor', 42 | intentionInvestor:'Intention Investor', 43 | waitExamineInvestor:'Wait Examine Investor', 44 | examiningInvestor:'Examining Investor', 45 | tenMillion:'Ten Million', 46 | person:'P' 47 | } 48 | } 49 | 50 | export default zh; -------------------------------------------------------------------------------- /src/lang/index.js: -------------------------------------------------------------------------------- 1 | 2 | // 引入i18n国际化插件 3 | import { getToken} from '@/utils/auth' 4 | import Vue from 'vue' 5 | import VueI18n from 'vue-i18n' 6 | process.env.NODE_ENV === "development" ? Vue.use(VueI18n) : null; 7 | 8 | import enLocale from './en' 9 | import zhLocale from './zh' 10 | 11 | // 注册i18n实例并引入语言文件,文件格式等下解析 12 | const i18n = new VueI18n({ 13 | locale: getToken('lang') || 'en', 14 | messages: { 15 | zh: { 16 | ...zhLocale 17 | }, 18 | en: { 19 | ...enLocale 20 | }, 21 | } 22 | }); 23 | 24 | export default i18n; -------------------------------------------------------------------------------- /src/lang/zh.js: -------------------------------------------------------------------------------- 1 | const zh = { 2 | // layout 3 | commons: { 4 | xiaoai: '小爱', 5 | admin: '管理员', 6 | editor: '赵晓编', 7 | quit: '退出', 8 | hi: '您好', 9 | index: '首页', 10 | userManage: '用户管理', 11 | share: '分享功能', 12 | infoManage: '信息管理', 13 | infoShow: '个人信息', 14 | infoShow1: '个人信息子菜单1', 15 | infoShow2: '个人信息子菜单2', 16 | infoShow3: '个人信息子菜单3', 17 | infoShow4: '个人信息子菜单4', 18 | infoShow5: '个人信息子菜单5', 19 | infoModify: '修改信息', 20 | infoModify1:'修改信息子菜单1', 21 | infoModify2:'修改信息子菜单2', 22 | infoModify3:'修改信息子菜单3', 23 | fundManage: '资金管理', 24 | fundList: '资金流水', 25 | chinaTabsList: '区域投资', 26 | fundData: '资金数据', 27 | fundPosition: '投资分布', 28 | typePosition: '项目分布', 29 | incomePayPosition: '收支分布', 30 | permission: '权限设置', 31 | pagePer: '页面权限', 32 | directivePer: '按钮权限', 33 | errorPage: '错误页面', 34 | page401:'401', 35 | page404:'404', 36 | wechatNumber: '微信号' 37 | }, 38 | index:{ 39 | yearLoss:'年度总盈亏', 40 | yearProfit:'年度收益率', 41 | potentialInvestor:'潜在投资人', 42 | intentionInvestor:'意向投资人', 43 | waitExamineInvestor:'待审投资人', 44 | examiningInvestor:'审核中投资人', 45 | tenMillion:'千万元', 46 | person:'人' 47 | } 48 | } 49 | 50 | export default zh; -------------------------------------------------------------------------------- /src/layout/bread.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class='bread_container' id="bread_container"> 3 | <span @click="handleLefeMenu" class="bars"> 4 | <icon-svg icon-class="iconmenu-fold" :class="{isactive:changeBarDirection}" /> 5 | </span> 6 | <el-breadcrumb class="breadcrumb" separator="/"> 7 | <el-breadcrumb-item 8 | v-for='(name,index) in matchedArr' 9 | :key='index' 10 | > 11 | {{ $t(`commons.${name}`)}} 12 | </el-breadcrumb-item> 13 | </el-breadcrumb> 14 | </div> 15 | </template> 16 | 17 | 18 | <script> 19 | 20 | export default { 21 | data(){ 22 | return { 23 | changeBarDirection:false, 24 | } 25 | }, 26 | created() { 27 | }, 28 | computed:{ 29 | matchedArr(){ 30 | let temp = [],temps = []; 31 | this.$route.matched.filter((item,index,self) => { 32 | // if(item.meta.title){ 33 | // const title = item.meta.title; 34 | // temp.push(title); 35 | // } 36 | if(item.name){ 37 | const name = item.name; 38 | temp.push(name); 39 | } 40 | }); 41 | temp.filter((item,index,self) => { 42 | if(!temps.includes(item)){ 43 | temps.push(item); 44 | } 45 | }) 46 | return temps; 47 | } 48 | }, 49 | mounted(){ 50 | }, 51 | methods:{ 52 | handleLefeMenu(){ 53 | this.$store.dispatch('setLeftCollapse'); // 折叠菜单 54 | this.$store.dispatch('handleLeftMenu'); // 改变菜单宽度 180->35/35-180 55 | this.changeBarDirection = !this.changeBarDirection; 56 | } 57 | }, 58 | watch: { 59 | 60 | } 61 | } 62 | 63 | 64 | 65 | </script> 66 | 67 | <style lang="less"> 68 | .bread_container{ 69 | background-color: #eaebec; 70 | width: 100%; 71 | .bars{ 72 | float: left; 73 | margin: 4px 10px; 74 | cursor: pointer; 75 | .isactive{ 76 | -webkit-transform: rotate(90deg); 77 | transform: rotate(90deg); 78 | transition: .38s; 79 | -webkit-transform-origin: 50% 50%; 80 | transform-origin: 50% 50%; 81 | } 82 | } 83 | .breadcrumb{ 84 | height: 30px; 85 | line-height: 30px; 86 | .breadbutton{ 87 | float:left; 88 | margin:4px 5px 0 0; 89 | 90 | } 91 | } 92 | } 93 | </style> 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/layout/content.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class=""> 3 | <transition name="fade"> 4 | <router-view></router-view> 5 | </transition> 6 | </div> 7 | </template> 8 | <script> 9 | export default { 10 | name: '', 11 | data () { 12 | return { 13 | 14 | } 15 | }, 16 | methods:{ 17 | 18 | }, 19 | created(){ 20 | 21 | }, 22 | mounted(){ 23 | 24 | } 25 | } 26 | </script> 27 | 28 | <style scoped lang='less'> 29 | .fade-enter-active, 30 | .fade-leave-active { 31 | transition: opacity .3s 32 | } 33 | 34 | .fade-enter, 35 | .fade-leave-active { 36 | opacity: 0 37 | } 38 | </style> 39 | -------------------------------------------------------------------------------- /src/layout/footerNav.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class='footer'> 3 | <p class="intro rflex"> 4 | <span>{{ $t('commons.xiaoai') }}Admin</span> 5 | <a :href='github' target="_blank"> 6 | <icon-svg icon-class="iconGithub" /> 7 | </a> 8 | <span>wdlhao2013({{ $t('commons.wechatNumber') }})</span> 9 | </p> 10 | <p class="beian">鄂ICP备18001612号</p> 11 | </div> 12 | </template> 13 | 14 | 15 | <script> 16 | import { github } from "@/utils/env"; 17 | 18 | export default { 19 | name: "footerNav", 20 | data(){ 21 | return { 22 | github:github 23 | } 24 | }, 25 | methods:{ 26 | 27 | } 28 | } 29 | </script> 30 | 31 | <style lang="less"> 32 | .footer{ 33 | width: 100%; 34 | padding: 10px 0; 35 | font-size:12px; 36 | text-align: center; 37 | background: #fff; 38 | p{ 39 | line-height: 30px; 40 | } 41 | .intro{ 42 | width: 240px; 43 | margin: 0 auto; 44 | justify-content: space-between; 45 | align-items: center; 46 | } 47 | } 48 | </style> 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/layout/home.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="home rflex"> 3 | <left-menu></left-menu> 4 | <div class="menu_right wflex el-scrollbar" ref="menu_right" :style="{left:sidebar.width+'px'}"> 5 | <head-nav></head-nav> 6 | <div class="menu_content" ref="menu_content"> 7 | <bread></bread> 8 | <router-view></router-view><!--页面渲染入口--> 9 | </div> 10 | <footerNav></footerNav> 11 | <backTop :ele="$refs.menu_right"></backTop> 12 | </div> 13 | </div> 14 | </template> 15 | <script> 16 | import { mapState, mapGetters } from 'vuex' 17 | 18 | import HeadNav from './headNav.vue'; 19 | import LeftMenu from './leftMenu.vue'; 20 | import Bread from './bread.vue'; 21 | import FooterNav from './footerNav.vue'; 22 | import backTop from 'cps/backTop'; 23 | 24 | export default { 25 | name: 'home', 26 | data(){ 27 | return { 28 | } 29 | }, 30 | computed:{ 31 | ...mapGetters(['sidebar']), 32 | }, 33 | components:{ 34 | HeadNav, 35 | LeftMenu, 36 | Bread, 37 | FooterNav, 38 | backTop 39 | }, 40 | created() { 41 | }, 42 | mounted (){ 43 | }, 44 | watch:{ 45 | 46 | } 47 | } 48 | </script> 49 | <style scoped lang='less'> 50 | .home{ 51 | .menu_right{ 52 | overflow: auto; 53 | position: absolute; 54 | right:0; 55 | top:0; 56 | bottom:0; 57 | background:#F6F7FC; 58 | .menu_content{ 59 | position: relative; 60 | margin-top:60px; 61 | width:100%; 62 | background:#f0f2f5; 63 | } 64 | 65 | } 66 | } 67 | </style> 68 | -------------------------------------------------------------------------------- /src/layout/index.js: -------------------------------------------------------------------------------- 1 | import bread from './bread.vue' 2 | import headNav from './headNav.vue' 3 | import leftMenu from './leftMenu.vue' 4 | import Layout from './home.vue' 5 | import Content from './content.vue' 6 | import footerNav from './footerNav.vue' 7 | import topMenu from './topMenu.vue' 8 | 9 | export { 10 | Layout, 11 | Content, 12 | bread, 13 | headNav, 14 | leftMenu, 15 | footerNav, 16 | topMenu 17 | } 18 | -------------------------------------------------------------------------------- /src/layout/leftMenu.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="menu_left cflex" :style="{width:sidebar.width+'px'}"> 3 | <div class="menu_page_top rflex"> 4 | <img :class='["logo",{"closeLogo":!sidebar.opened}]' :src="logo" alt="小爱admin" > 5 | <span class='title' v-show="sidebar.opened">{{$t('commons.xiaoai')}}<i>Admin</i></span> 6 | </div> 7 | <div class="menu_page_bottom is-scroll-left"> 8 | <el-menu 9 | mode="vertical" 10 | theme="dark" 11 | :show-timeout="200" 12 | :default-active="$route.path" 13 | :collapse="isCollapse" 14 | :background-color="menuObj.bgColor" 15 | :text-color="menuObj.textColor" 16 | :active-text-color="menuObj.activeTextColor" 17 | :style="{width:sidebar.width+'px'}" 18 | > 19 | <template v-for="(item,index) in permission_routers"> 20 | <!--表示 有一级菜单--> 21 | <router-link v-if="!item.hidden && item.noDropdown" :to="item.path+'/'+item.children[0].path" :key="index"> 22 | <el-menu-item class="dropItem" 23 | :index="item.path+'/'+item.children[0].path" 24 | > 25 | <icon-svg v-if="item.meta.icon" :icon-class="item.meta.icon" /> 26 | <span v-if="item.meta.title" slot="title">{{ $t(`commons.${item.name}`)}}</span> 27 | </el-menu-item> 28 | </router-link> 29 | 30 | <!--表示 有二级或者多级菜单 --> 31 | <el-submenu v-if="item.children && item.children.length >= 1 && !item.hidden && !item.noDropdown" :index="item.path" :key="index"> 32 | <template slot="title"> 33 | <icon-svg v-if="item.meta.icon" :icon-class="item.meta.icon" /> 34 | <span v-if="item.meta.title" slot="title">{{ $t(`commons.${item.name}`)}}</span> 35 | </template> 36 | <!--直接定位到子路由上,子路由也可以实现导航功能--> 37 | <router-link v-for="(citem,cindex) in item.children" :to="getIindex(citem,item,cindex)" :key="cindex"> 38 | <el-menu-item 39 | v-if="citem.meta.routerType != 'topmenu' && citem.meta.title" 40 | :index="getIindex(citem,item,cindex)"> 41 | <span slot="title"> {{ $t(`commons.${citem.name}`)}}</span> 42 | </el-menu-item> 43 | </router-link> 44 | </el-submenu> 45 | </template> 46 | </el-menu> 47 | </div> 48 | </div> 49 | </template> 50 | 51 | <script> 52 | import { mapGetters } from 'vuex' 53 | import * as mUtils from "@/utils/mUtils"; 54 | import logoImg from "@/assets/img/logo.png"; 55 | 56 | 57 | export default { 58 | name: "left-Menu", 59 | data() { 60 | return { 61 | menuObj:{ 62 | bgColor:'#fff', 63 | textColor:'#666', 64 | activeTextColor:'#ff6428', 65 | }, 66 | logo:logoImg 67 | }; 68 | }, 69 | computed:{ 70 | ...mapGetters([ 71 | 'permission_routers', 72 | 'isCollapse', 73 | 'sidebar', 74 | 'menuIndex' 75 | ]), 76 | }, 77 | created(){ 78 | }, 79 | mounted(){ 80 | }, 81 | methods: { 82 | getIindex(citem,item,cindex){ 83 | return (citem.meta.titleList)?item.path+'/'+citem.path+'/'+citem.meta.titleList[0].path:item.path+'/'+citem.path; 84 | } 85 | } 86 | }; 87 | </script> 88 | 89 | 90 | <style lang="less" scoped> 91 | @left-bgColor:#fff; // 左侧菜单背景颜色; 92 | @icon-link:#FF6C60; 93 | .menu_left{ 94 | position: absolute; 95 | top:0; 96 | left:0; 97 | bottom:0; 98 | } 99 | .menu_page_top{ 100 | width:100%; 101 | height: 60px; 102 | align-items: center; 103 | justify-content: space-around; 104 | text-transform: uppercase; 105 | box-sizing: border-box; 106 | box-shadow:0px 2px 5px 0px rgba(230,224,224,0.5); 107 | .logo { 108 | height: 36px; 109 | width: 36px; 110 | vertical-align: middle; 111 | display: inline-block; 112 | } 113 | .closeLogo{ 114 | width:30px; 115 | height:30px; 116 | } 117 | .title{ 118 | font-size: 22px; 119 | i{ 120 | color:#FF6C60; 121 | } 122 | } 123 | } 124 | .menu_page_bottom { 125 | width:100%; 126 | overflow-y: scroll; 127 | overflow-x: hidden; 128 | flex:1; 129 | margin-top:3px; 130 | z-index: 10; 131 | box-shadow: 0 0 10px 0 rgba(230, 224, 224, 0.5) 132 | } 133 | </style> 134 | -------------------------------------------------------------------------------- /src/layout/topMenu.vue: -------------------------------------------------------------------------------- 1 | 2 | <template> 3 | <div class="menu_top wflex rflex" ref="menuTop"> 4 | <el-menu 5 | mode="horizontal" 6 | class="el-menu-demo rflex el-scrollbar2 top-scrollbar2" 7 | :background-color="menuObj.bgColor" 8 | :text-color="menuObj.textColor" 9 | :active-text-color="menuObj.activeTextColor" 10 | :default-active="$route.path" 11 | > 12 | <template v-for="(item,index) in topRouters"> 13 | <router-link :to="$route.matched[1].path+'/'+item.path" :key="index"> 14 | <el-menu-item :index="$route.matched[1].path+'/'+item.path"> 15 | {{ $t(`commons.${item.path}`) }} 16 | </el-menu-item> 17 | </router-link> 18 | </template> 19 | </el-menu> 20 | </div> 21 | </template> 22 | 23 | <script> 24 | import { mapGetters } from 'vuex' 25 | 26 | export default { 27 | name:'top-menu', 28 | data(){ 29 | return { 30 | menuObj:{ 31 | bgColor:'', 32 | textColor:'#303133', 33 | activeTextColor:'#ff6428', 34 | }, 35 | } 36 | }, 37 | computed:{ 38 | ...mapGetters(['topRouters']) 39 | }, 40 | created(){ 41 | this.setLeftInnerMenu(); // 针对刷新页面时,也需要加载顶部菜单 42 | }, 43 | mounted(){ 44 | }, 45 | methods:{ 46 | setLeftInnerMenu(){ 47 | const titleList = this.$route.matched[1].meta.titleList; 48 | const currentTitle = titleList && this.$route.matched[2].meta.title; 49 | if( titleList && this.$route.matched[1].meta.routerType === 'leftmenu'){ // 点击的为 左侧的2级菜单 50 | this.$store.dispatch('ClickLeftInnerMenu',{'titleList':titleList}); 51 | this.$store.dispatch('ClickTopMenu',{'title':currentTitle}); 52 | }else{ // 点击左侧1级菜单 53 | this.$store.dispatch('ClickLeftInnerMenu',{'titleList':[]}); 54 | this.$store.dispatch('ClickTopMenu',{'title':''}); 55 | } 56 | }, 57 | getPath(){ 58 | this.setLeftInnerMenu(); 59 | }, 60 | }, 61 | watch:{ 62 | "$route":"getPath" 63 | } 64 | } 65 | 66 | </script> 67 | 68 | <style lang="less" scoped> 69 | .menu_top{ 70 | // width:calc(100% - 350px); 71 | .el-menu-demo{ 72 | overflow-y:hidden; 73 | flex:1; 74 | } 75 | .el-menu-item:focus, .el-menu-item:hover { 76 | outline: 0; 77 | background-color: #ceeda8; 78 | } 79 | .router-link-active{ 80 | 81 | } 82 | } 83 | </style> -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | import router from './router' 4 | import store from './store/' 5 | // 'development',use package;'production':use cdn; 6 | import ElementUI from 'element-ui' 7 | Vue.use(ElementUI, { size: 'mini'}); 8 | import('element-ui/lib/theme-chalk/index.css') 9 | 10 | import './components/iconSvg' 11 | 12 | import '@/permission' // permission control 13 | 14 | import '@/mockjs'; // mock数据 15 | 16 | // i18n国际化 17 | import i18n from "@/lang"; 18 | 19 | // 分享功能集合 20 | import { shareConfig } from './utils/share'; 21 | Vue.prototype.shareConfig = shareConfig; 22 | 23 | 24 | Vue.config.productionTip = false; 25 | 26 | // 字体图标线上地址,在index中使用cdn载入; 27 | //at.alicdn.com/t/font_1258069_e40c6mwl0x8.js 28 | 29 | new Vue({ 30 | router, 31 | store, 32 | i18n, // 便于可以直接在组件中通过this.$i18n使用,也可以按需引用 33 | render: h => h(App), 34 | }).$mount('#app') 35 | -------------------------------------------------------------------------------- /src/mockjs/index.js: -------------------------------------------------------------------------------- 1 | // import Vue from 'vue' 2 | import Mock from 'mockjs' 3 | // process.env.NODE_ENV === "development" ? Vue.use(Mock) : null; 4 | 5 | import tableAPI from './money' 6 | import salesAPI from './sales' 7 | import userAPI from './user' 8 | 9 | // 设置全局延时 没有延时的话有时候会检测不到数据变化 建议保留 10 | Mock.setup({ 11 | timeout: '300-600' 12 | }) 13 | 14 | // 资金相关 15 | Mock.mock(/\/money\/get/, 'get', tableAPI.getMoneyList) 16 | Mock.mock(/\/money\/remove/, 'get', tableAPI.deleteMoney) 17 | Mock.mock(/\/money\/batchremove/, 'get', tableAPI.batchremoveMoney) 18 | Mock.mock(/\/money\/add/, 'get', tableAPI.createMoney) 19 | Mock.mock(/\/money\/edit/, 'get', tableAPI.updateMoney) 20 | // sales相关 21 | Mock.mock(/\/sales\/get/, 'get', salesAPI.getSalesList) 22 | // user相关 23 | Mock.mock(/\/user\/login/, 'get', userAPI.login) 24 | Mock.mock(/\/user\/logout/, 'get', userAPI.logout) 25 | Mock.mock(/\/user\/info\/get/, 'get', userAPI.getUserInfo) 26 | Mock.mock(/\/user\/list\/get/, 'get', userAPI.getUserList) 27 | 28 | export default Mock; -------------------------------------------------------------------------------- /src/mockjs/money.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | 3 | import * as mUtils from '@/utils/mUtils' 4 | 5 | 6 | let List = [] 7 | const count = 60 8 | let typelist = [1, 2, 3, 4, 5, 6, 7, 8] 9 | 10 | for (let i = 0; i < count; i++) { 11 | List.push(Mock.mock({ 12 | id: Mock.Random.guid(), 13 | username: Mock.Random.cname(), 14 | address: Mock.mock('@county(true)'), 15 | createTime: Mock.Random.datetime(), 16 | income: Mock.Random.integer(0, 9999), 17 | pay: Mock.Random.integer(0, 9999), 18 | accoutCash: Mock.Random.integer(0, 9999), 19 | 'incomePayType|1': typelist 20 | })) 21 | } 22 | 23 | export default { 24 | /** 25 | * 获取列表 26 | * 要带参数 name, page, limt; name可以不填, page,limit有默认值。 27 | * @param name, page, limit 28 | * @return {{code: number, count: number, data: *[]}} 29 | */ 30 | getMoneyList: config => { 31 | const { name, page = 1, limit = 20 } = mUtils.param2Obj(config.url) 32 | const mockList = List.filter(user => { 33 | if (name && user.username.indexOf(name) === -1) return false 34 | return true 35 | }) 36 | const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1)) 37 | return { 38 | code: 200, 39 | data: { 40 | total: mockList.length, 41 | moneyList: pageList 42 | } 43 | } 44 | }, 45 | /** 46 | * 增加资金信息 47 | * @param username, address, createTime, income, pay , accoutCash, incomePayType 48 | * @return {{code: number, data: {message: string}}} 49 | */ 50 | createMoney: config => { 51 | const { username, address, income, pay , accoutCash, incomePayType,tableAddress } = mUtils.param2Obj(config.url) 52 | List.unshift({ 53 | id: Mock.Random.guid(), 54 | username: username, 55 | address: address, 56 | tableAddress:tableAddress, 57 | createTime: Mock.Random.now(), 58 | income: income, 59 | pay: pay, 60 | accoutCash: accoutCash, 61 | incomePayType: incomePayType 62 | }) 63 | return { 64 | code: 200, 65 | data: { 66 | message: '添加成功' 67 | } 68 | } 69 | }, 70 | /** 71 | * 删除用户 72 | * @param id 73 | * @return {*} 74 | */ 75 | deleteMoney: config => { 76 | const { id } = mUtils.param2Obj(config.url) 77 | if (!id) { 78 | return { 79 | code: -999, 80 | message: '参数不正确' 81 | } 82 | } else { 83 | List = List.filter(u => u.id !== id) 84 | return { 85 | code: 200, 86 | data: { 87 | message: '删除成功' 88 | } 89 | } 90 | } 91 | }, 92 | /** 93 | * 批量删除 94 | * @param config 95 | * @return {{code: number, data: {message: string}}} 96 | */ 97 | batchremoveMoney: config => { 98 | console.log(config); 99 | // console.log(mUtils.param2Obj(config.url)); 100 | let { ids } = mUtils.param2Obj(config.url) 101 | console.log(ids); 102 | ids = ids.split(',') 103 | List = List.filter(u => !ids.includes(u.id)) 104 | return { 105 | code: 200, 106 | data: { 107 | message: '批量删除成功' 108 | } 109 | } 110 | }, 111 | /** 112 | * 修改用户 113 | * @param id, name, addr, age, birth, sex 114 | * @return {{code: number, data: {message: string}}} 115 | */ 116 | updateMoney: config => { 117 | const { id,username, address, income, pay , accoutCash, incomePayType } = mUtils.param2Obj(config.url) 118 | List.some(u => { 119 | if (u.id === id) { 120 | u.username = username 121 | u.address = address 122 | u.income = income 123 | u.pay = pay 124 | u.accoutCash = accoutCash 125 | u.incomePayType = incomePayType 126 | return true 127 | } 128 | }) 129 | return { 130 | code: 200, 131 | data: { 132 | message: '编辑成功' 133 | } 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /src/mockjs/sales.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | 3 | import * as mUtils from '@/utils/mUtils' 4 | 5 | 6 | let List = [] 7 | const count = 7 8 | 9 | 10 | for (let i = 0; i < count; i++) { 11 | List.push(Mock.mock({ 12 | id: Mock.Random.guid(), 13 | userImg:Mock.Random.image(), 14 | username: Mock.Random.name(), 15 | date: Mock.Random.datetime(), 16 | price: Mock.Random.float(0, 9999, 2,2), 17 | status:Mock.Random.natural( 1,4 ), 18 | commentContent:Mock.Random.paragraph() 19 | })) 20 | } 21 | 22 | export default { 23 | /** 24 | * 获取sales列表数据 25 | * 要带参数 name, page, limt; name可以不填, page,limit有默认值。 26 | * @param name, page, limit 27 | * @return {{code: number, count: number, data: *[]}} 28 | */ 29 | getSalesList: config => { 30 | const { name, page = 1, limit = 7 } = mUtils.param2Obj(config.url) 31 | const mockList = List.filter(user => { 32 | if (name && user.username.indexOf(name) === -1) return false 33 | return true 34 | }) 35 | const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1)) 36 | return { 37 | code: 200, 38 | data: { 39 | total: mockList.length, 40 | list: pageList 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/mockjs/user.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import * as mUtils from '@/utils/mUtils' 3 | 4 | 5 | let List = [] 6 | const count = 1000 7 | let typelist = ['联通', '移动', '电信', '铁通'] 8 | 9 | for (let i = 0; i < count; i++) { 10 | List.push(Mock.mock({ 11 | id: Mock.Random.guid(), 12 | sortnum: i + 1, 13 | username: Mock.Random.cname(), 14 | address: Mock.mock('@county(true)'), 15 | createTime: Mock.Random.datetime(), 16 | updateTime: Mock.Random.now(), 17 | ip:Mock.mock('@ip'), 18 | region:Mock.mock('@region'), 19 | areaId:/\d{7}/, 20 | email: Mock.Random.email(), 21 | 'isp|1': typelist 22 | })) 23 | } 24 | 25 | export default { 26 | // 用户登录 27 | login: config => { 28 | let data = JSON.parse(config.body); 29 | let userList = {}; 30 | if(data.username === 'admin'){ 31 | userList = { 32 | token:'admin', 33 | name:'管理员', 34 | } 35 | }else if(data.username === 'editor'){ 36 | userList = { 37 | token:'editor', 38 | name:'赵晓编', 39 | } 40 | }else{ 41 | return { 42 | code: -1, 43 | data: { 44 | msg: "用户名错误", 45 | status:'fail' 46 | } 47 | } 48 | } 49 | return { 50 | code: 200, 51 | data: { 52 | userList: userList 53 | } 54 | } 55 | }, 56 | // 用户登出 57 | logout: config => { 58 | return { 59 | code: 200, 60 | data: { 61 | userList: "" 62 | } 63 | } 64 | }, 65 | // 获取登录用户信息 66 | getUserInfo:config => { 67 | let data = JSON.parse(config.body); 68 | let userList = {}; 69 | if(data.token === 'admin'){ 70 | userList = { 71 | roles: ['admin'], 72 | name:'admin', 73 | avatar:'https://wx.qlogo.cn/mmopen/vi_32/un2HbJJc6eiaviaibvMgiasFNlVDlNOb9E6WCpCrsO4wMMhHIbsvTkAbIehLwROVFlu8dLMcg00t3ZtOcgCCdcxlZA/132' 74 | } 75 | }else if(data.token === 'editor'){ 76 | userList = { 77 | roles: ['editor'], 78 | name:'editor', 79 | avatar:'https://mirror-gold-cdn.xitu.io/168e088859e325b9d85?imageView2/1/w/100/h/100/q/85/format/webp/interlace/1' 80 | } 81 | }else{ 82 | userList = {} 83 | } 84 | return { 85 | code: 200, 86 | data: { 87 | userList: userList 88 | } 89 | } 90 | }, 91 | /** 92 | * 获取用户列表 93 | * 要带参数 name, page, limt; name可以不填, page,limit有默认值。 94 | * @param name, page, limit 95 | * @return {{code: number, count: number, data: *[]}} 96 | */ 97 | getUserList:config => { 98 | const { limit , page } = JSON.parse(config.body); 99 | let mockList = List; 100 | const userList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1)) 101 | return { 102 | code: 200, 103 | data: { 104 | total: mockList.length, 105 | userList: userList 106 | } 107 | } 108 | } 109 | 110 | 111 | } -------------------------------------------------------------------------------- /src/page/errorPage/401.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="fillcontain" > 3 | <div class="contain" ref="contain"> 4 | <div class="errPage-container cflex wflex"> 5 | <img class="errorImg" :src="errorImg" /> 6 | <p class="errorTitle">401</p> 7 | <p class="errorTro">抱歉,您没有权限访问该页面。</p> 8 | <router-link :to="{path: '/'}"> 9 | <el-button size="mini" type="primary">返回首页</el-button> 10 | </router-link> 11 | </div> 12 | </div> 13 | </div> 14 | </template> 15 | 16 | <script> 17 | import errorImg from "@/assets/img/401.png" 18 | import * as mutils from '@/utils/mUtils' 19 | 20 | export default { 21 | name: 'page401', 22 | data() { 23 | return { 24 | errorImg:errorImg 25 | } 26 | }, 27 | mounted(){ 28 | mutils.setContentHeight(this,this.$refs.contain,250); 29 | 30 | }, 31 | methods: { 32 | } 33 | } 34 | </script> 35 | 36 | <style lang="less" scoped> 37 | .contain{ 38 | display: flex; 39 | justify-content: center; 40 | padding: 20px; 41 | .errPage-container { 42 | align-items: center; 43 | p{ 44 | line-height: 30px; 45 | } 46 | .errorImg{ 47 | width:286px; 48 | height: 325px; 49 | margin-bottom: 10px; 50 | } 51 | .errorTitle{ 52 | color: rgba(0,0,0,.85); 53 | font-size: 24px; 54 | } 55 | .errorTro{ 56 | color: rgba(0,0,0,.45); 57 | font-size: 14px; 58 | } 59 | a{ 60 | text-decoration: underline; 61 | } 62 | .rows{ 63 | height: 100%; 64 | .el-col{ 65 | text-align: center; 66 | height: 100%; 67 | } 68 | .title{ 69 | font-size: 170px; 70 | line-height: 1.2; 71 | color: #a9d86e; 72 | } 73 | .neirongItem{ 74 | height: 100%; 75 | display: flex; 76 | justify-content: center; 77 | flex-direction: column; 78 | .tip{ 79 | font-size: 30px; 80 | font-weight: bold; 81 | text-align: left; 82 | } 83 | .neirong{ 84 | font-size: 20px; 85 | text-align: left; 86 | } 87 | } 88 | } 89 | .home{ 90 | text-align: center; 91 | } 92 | .router-link-active:hover{ 93 | color:#a9d86e; 94 | text-decoration: underline; 95 | } 96 | } 97 | } 98 | 99 | </style> 100 | -------------------------------------------------------------------------------- /src/page/errorPage/404.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="fillcontain"> 3 | <div class="contain" ref="contain"> 4 | <div class="errPage-container cflex wflex"> 5 | <img class="errorImg" :src="errorImg" /> 6 | <p class="errorTitle">404</p> 7 | <p class="errorTro">抱歉,你访问的页面不存在。</p> 8 | <router-link :to="{path: '/'}"> 9 | <el-button size="mini" type="primary">返回首页</el-button> 10 | </router-link> 11 | </div> 12 | </div> 13 | </div> 14 | </template> 15 | 16 | <script> 17 | import errorImg from "@/assets/img/404.png" 18 | import * as mutils from '@/utils/mUtils' 19 | 20 | 21 | export default { 22 | name: 'page401', 23 | data() { 24 | return { 25 | errorImg:errorImg 26 | } 27 | }, 28 | mounted(){ 29 | mutils.setContentHeight(this,this.$refs.contain,250); 30 | }, 31 | methods: { 32 | } 33 | } 34 | </script> 35 | 36 | <style lang="less" scoped> 37 | .contain{ 38 | display: flex; 39 | justify-content: center; 40 | padding: 20px; 41 | .errPage-container { 42 | align-items: center; 43 | p{ 44 | line-height: 30px; 45 | } 46 | .errorImg{ 47 | width:286px; 48 | height: 325px; 49 | margin-bottom: 10px; 50 | } 51 | .errorTitle{ 52 | color: rgba(0,0,0,.85); 53 | font-size: 24px; 54 | } 55 | .errorTro{ 56 | color: rgba(0,0,0,.45); 57 | font-size: 14px; 58 | } 59 | a{ 60 | text-decoration: underline; 61 | } 62 | .rows{ 63 | height: 100%; 64 | .el-col{ 65 | text-align: center; 66 | height: 100%; 67 | } 68 | .title{ 69 | font-size: 170px; 70 | line-height: 1.2; 71 | color: #a9d86e; 72 | } 73 | .neirongItem{ 74 | height: 100%; 75 | display: flex; 76 | justify-content: center; 77 | flex-direction: column; 78 | .tip{ 79 | font-size: 30px; 80 | font-weight: bold; 81 | text-align: left; 82 | } 83 | .neirong{ 84 | font-size: 20px; 85 | text-align: left; 86 | } 87 | } 88 | } 89 | .home{ 90 | text-align: center; 91 | } 92 | .router-link-active:hover{ 93 | color:#a9d86e; 94 | text-decoration: underline; 95 | } 96 | } 97 | } 98 | 99 | </style> 100 | -------------------------------------------------------------------------------- /src/page/fundData/fundPosition.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="fillcontain"> 3 | <div class="fillcontainer" ref="fillcontainer"> 4 | <div id="fundPosition" class="echartsPosition"></div> 5 | </div> 6 | </div> 7 | </template> 8 | 9 | <script> 10 | import echarts from 'echarts' 11 | import '@/assets/echarts/china.js'; 12 | import '@/assets/echarts/roma.min.js'; 13 | 14 | export default { 15 | data(){ 16 | return { 17 | chart:null 18 | } 19 | }, 20 | methods: { 21 | randomData() { 22 | return Math.round(Math.random()*1000000); 23 | }, 24 | drawMap (id) { 25 | // echarts.init(),初始化数据 26 | if ( this.chart && this.chart.dispose) { 27 | this.chart.dispose(); 28 | } 29 | this.chart = echarts.init(document.getElementById(id),'roma'); 30 | this.chart.setOption({ 31 | title: { 32 | text: '2019年全国各省份投资情况', 33 | subtext: '单位/万元', 34 | left: 'center' 35 | }, 36 | tooltip: { 37 | trigger: 'item' 38 | }, 39 | legend: { 40 | orient: 'vertical', 41 | left: 'left', 42 | data:['总投资额'] 43 | }, 44 | visualMap: { 45 | min: 0, 46 | max: 1200000, 47 | left: 'left', 48 | top: 'bottom', 49 | text: ['高','低'], // 文本,默认为数值文本 50 | calculable: true 51 | }, 52 | toolbox: { 53 | show: true, 54 | orient: 'vertical', 55 | left: 'right', 56 | top: 'center', 57 | feature: { 58 | dataView: {readOnly: false}, 59 | restore: {}, 60 | saveAsImage: {} 61 | } 62 | }, 63 | series: [ 64 | { 65 | name: '总投资额', 66 | type: 'map', 67 | mapType: 'china', 68 | roam: false, 69 | label: { 70 | normal: { 71 | show: true 72 | }, 73 | emphasis: { 74 | show: true 75 | } 76 | }, 77 | data:[ 78 | {name: '北京',value: this.randomData() }, 79 | {name: '天津',value: this.randomData() }, 80 | {name: '上海',value: this.randomData() }, 81 | {name: '重庆',value: this.randomData() }, 82 | {name: '河北',value: this.randomData() }, 83 | {name: '河南',value: this.randomData() }, 84 | {name: '云南',value: this.randomData() }, 85 | {name: '辽宁',value: this.randomData() }, 86 | {name: '黑龙江',value: this.randomData() }, 87 | {name: '湖南',value: this.randomData() }, 88 | {name: '安徽',value: this.randomData() }, 89 | {name: '山东',value: this.randomData() }, 90 | {name: '新疆',value: this.randomData() }, 91 | {name: '江苏',value: this.randomData() }, 92 | {name: '浙江',value: this.randomData() }, 93 | {name: '江西',value: this.randomData() }, 94 | {name: '湖北',value: this.randomData() }, 95 | {name: '广西',value: this.randomData() }, 96 | {name: '甘肃',value: this.randomData() }, 97 | {name: '山西',value: this.randomData() }, 98 | {name: '内蒙古',value: this.randomData() }, 99 | {name: '陕西',value: this.randomData() }, 100 | {name: '吉林',value: this.randomData() }, 101 | {name: '福建',value: this.randomData() }, 102 | {name: '贵州',value: this.randomData() }, 103 | {name: '广东',value: this.randomData() }, 104 | {name: '青海',value: this.randomData() }, 105 | {name: '西藏',value: this.randomData() }, 106 | {name: '四川',value: this.randomData() }, 107 | {name: '宁夏',value: this.randomData() }, 108 | {name: '海南',value: this.randomData() }, 109 | {name: '台湾',value: this.randomData() }, 110 | {name: '香港',value: this.randomData() }, 111 | {name: '澳门',value: this.randomData() } 112 | ] 113 | } 114 | ] 115 | }); 116 | } 117 | }, 118 | mounted(){ 119 | this.$nextTick(function() { 120 | this.$refs.fillcontainer.style.height = (document.body.clientHeight - 160)+'px' 121 | this.drawMap('fundPosition'); 122 | var that = this; 123 | var resizeTimer = null; 124 | // 保证页面在放大或缩小时,也能够动态的显示数据 125 | window.onresize = function() { 126 | if (resizeTimer) clearTimeout(resizeTimer); 127 | resizeTimer = setTimeout(function() { 128 | that.$refs.fillcontainer.style.height = (document.body.clientHeight - 160)+'px' 129 | that.drawMap('fundPosition'); 130 | }, 100); 131 | } 132 | }) 133 | } 134 | 135 | } 136 | </script> 137 | 138 | <style lang="less" scoped> 139 | 140 | </style> 141 | 142 | 143 | -------------------------------------------------------------------------------- /src/page/fundData/typePosition.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="fillcontain"> 3 | <div class="fillcontainer" ref="fillcontainer"> 4 | <el-row :gutter="10"> 5 | <el-col :span="12" style="height:100%;"> 6 | <div id="typePosition"></div> 7 | </el-col> 8 | <el-col :span="12" style="height:100%;"> 9 | <div id="typePosition2"></div> 10 | </el-col> 11 | </el-row> 12 | </div> 13 | </div> 14 | </template> 15 | 16 | <script> 17 | import echarts from 'echarts'; 18 | import '../../../node_modules/echarts/theme/vintage.js'; 19 | 20 | export default { 21 | data(){ 22 | return { 23 | chart:null, 24 | chart_bar:null, 25 | data:{ 26 | legendData: ['储蓄','基金','股票','债券','期货'], 27 | seriesData:[ 28 | {value:this.randomData(), name:'储蓄'}, 29 | {value:this.randomData(), name:'基金'}, 30 | {value:this.randomData(), name:'股票'}, 31 | {value:this.randomData(), name:'债券'}, 32 | {value:this.randomData(), name:'期货'} 33 | ] 34 | } 35 | } 36 | }, 37 | methods: { 38 | randomData() { 39 | return Math.round(Math.random()*1000000); 40 | }, 41 | drawpie(id, radius, centery) { 42 | if ( this.chart && this.chart.dispose) { 43 | this.chart.dispose(); 44 | } 45 | this.chart = echarts.init(document.getElementById(id), 'vintage'); 46 | this.chart.setOption({ 47 | angleAxis: {}, 48 | radiusAxis: { 49 | type: 'category', 50 | data: ['2011年', '2012年', '2013年', '2014年', '2015年', '2016年', '2017年'], 51 | z: 10 52 | }, 53 | polar: {}, 54 | series: [{ 55 | type: 'bar', 56 | data: [80, 20, 30, 40, 70, 50, 10], 57 | coordinateSystem: 'polar', 58 | name: '储蓄', 59 | stack: 'a' 60 | }, { 61 | type: 'bar', 62 | data: [60, 40, 60, 10, 30, 20, 10], 63 | coordinateSystem: 'polar', 64 | name: '基金', 65 | stack: 'a' 66 | }, { 67 | type: 'bar', 68 | data: [10, 80, 30, 40, 50, 20, 50], 69 | coordinateSystem: 'polar', 70 | name: '股票', 71 | stack: 'a' 72 | },{ 73 | type: 'bar', 74 | data: [60, 20, 10, 80, 30, 20, 50], 75 | coordinateSystem: 'polar', 76 | name: '债券', 77 | stack: 'a' 78 | }, { 79 | type: 'bar', 80 | data: [90, 20, 10, 40, 10, 20, 50], 81 | coordinateSystem: 'polar', 82 | name: '期货', 83 | stack: 'a' 84 | }], 85 | legend: { 86 | show: true, 87 | data: ['储蓄', '基金', '股票','债券','期货'] 88 | } 89 | }); 90 | }, 91 | drawbar(id){ 92 | if ( this.chart_bar && this.chart_bar.dispose) { 93 | this.chart_bar.dispose(); 94 | } 95 | this.chart_bar = echarts.init(document.getElementById(id),'vintage'); 96 | this.chart_bar.setOption({ 97 | angleAxis: { 98 | type: 'category', 99 | data: ['2011年', '2012年', '2013年', '2014年', '2015年', '2016年', '2017年'], 100 | z: 10 101 | }, 102 | radiusAxis: {}, 103 | polar: {}, 104 | series: [{ 105 | type: 'bar', 106 | data: [80, 20, 30, 40, 70, 50, 10], 107 | coordinateSystem: 'polar', 108 | name: '储蓄', 109 | stack: 'a' 110 | }, { 111 | type: 'bar', 112 | data: [60, 40, 60, 10, 30, 20, 10], 113 | coordinateSystem: 'polar', 114 | name: '基金', 115 | stack: 'a' 116 | }, { 117 | type: 'bar', 118 | data: [10, 80, 30, 40, 50, 20, 50], 119 | coordinateSystem: 'polar', 120 | name: '股票', 121 | stack: 'a' 122 | },{ 123 | type: 'bar', 124 | data: [60, 20, 10, 80, 30, 20, 50], 125 | coordinateSystem: 'polar', 126 | name: '债券', 127 | stack: 'a' 128 | }, { 129 | type: 'bar', 130 | data: [90, 20, 10, 40, 10, 20, 50], 131 | coordinateSystem: 'polar', 132 | name: '期货', 133 | stack: 'a' 134 | }], 135 | legend: { 136 | show: true, 137 | data: ['储蓄', '基金', '股票','债券','期货'] 138 | } 139 | }) 140 | } 141 | }, 142 | mounted() { 143 | this.$nextTick(function() { 144 | // this.$refs.fillcontainer.style.height = (document.body.clientHeight - 160)+'px' 145 | this.drawpie('typePosition'); 146 | this.drawbar('typePosition2'); 147 | // 保证页面在放大或缩小时,也能够动态的显示数据 148 | window.onresize = () => { 149 | // this.$refs.fillcontainer.style.height = (document.body.clientHeight - 160)+'px' 150 | this.drawpie('typePosition'); 151 | this.drawbar('typePosition2'); 152 | } 153 | 154 | }) 155 | } 156 | } 157 | </script> 158 | 159 | <style lang="less" scoped> 160 | #typePosition,#typePosition2 { 161 | position: relative; 162 | width: 96%; 163 | height: 530px; 164 | padding: 10px; 165 | border-radius: 10px; 166 | } 167 | </style> 168 | 169 | 170 | -------------------------------------------------------------------------------- /src/page/fundList/chinaTabsList.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="fillcontain"> 3 | <div class="tabContainer" ref="tabContainer"> 4 | <el-tabs type="border-card"> 5 | <el-tab-pane> 6 | <span slot="label" @click="toggleTabs('eastChina')"><icon-svg icon-class="icondashboard" />华东区域</span> 7 | <china-tabs-table :toggleData="toggleData"></china-tabs-table> 8 | </el-tab-pane> 9 | <el-tab-pane> 10 | <span slot="label" @click="toggleTabs('southChina')"><icon-svg icon-class="iconecharts" />华南区域</span> 11 | <china-tabs-table :toggleData="toggleData"></china-tabs-table> 12 | </el-tab-pane> 13 | <el-tab-pane> 14 | <span slot="label" @click="toggleTabs('centralChina')"><icon-svg icon-class="iconinfo" />华中区域</span> 15 | <china-tabs-table :toggleData="toggleData"></china-tabs-table> 16 | </el-tab-pane> 17 | <el-tab-pane> 18 | <span slot="label" @click="toggleTabs('northChina')"><icon-svg icon-class="iconpermission" />华北区域</span> 19 | <china-tabs-table :toggleData="toggleData"></china-tabs-table> 20 | </el-tab-pane> 21 | <el-tab-pane> 22 | <span slot="label" @click="toggleTabs('northwestChina')"><icon-svg icon-class="iconuser" />西北区域</span> 23 | <china-tabs-table :toggleData="toggleData"></china-tabs-table> 24 | </el-tab-pane> 25 | <el-tab-pane> 26 | <span slot="label" @click="toggleTabs('southwestChina')"><icon-svg icon-class="iconError" />西南地区</span> 27 | <china-tabs-table :toggleData="toggleData"></china-tabs-table> 28 | </el-tab-pane> 29 | <el-tab-pane> 30 | <span slot="label" @click="toggleTabs('northeastChina')"><icon-svg icon-class="iconfufei0" />东北地区</span> 31 | <china-tabs-table :toggleData="toggleData"></china-tabs-table> 32 | </el-tab-pane> 33 | <el-tab-pane> 34 | <span slot="label" @click="toggleTabs('specialareaChina')"><icon-svg icon-class="iconpay1" />台港澳地区</span> 35 | <china-tabs-table :toggleData="toggleData"></china-tabs-table> 36 | </el-tab-pane> 37 | </el-tabs> 38 | <pagination :pageTotal="pageTotal"></pagination> 39 | </div> 40 | </div> 41 | </template> 42 | 43 | <script> 44 | import chinaTabsTable from './components/chinaTabsTable' 45 | import data from './data/chinaTabs.json'; 46 | import Pagination from "@/components/pagination"; 47 | 48 | export default { 49 | data(){ 50 | return { 51 | toggleData:'', 52 | pageTotal:60, 53 | } 54 | }, 55 | components: { 56 | chinaTabsTable, 57 | Pagination 58 | }, 59 | mounted(){ 60 | // this.setTabHeight(); 61 | // window.onresize = () => { 62 | // this.setTabHeight(); 63 | // } 64 | this.toggleTabs('eastChina'); 65 | }, 66 | methods: { 67 | setTabHeight(){ 68 | this.$nextTick(() => { 69 | this.$refs.tabContainer.style.height = (document.body.clientHeight - 160)+'px' 70 | }) 71 | }, 72 | toggleTabs(item){ 73 | this.toggleData = item; 74 | } 75 | } 76 | } 77 | </script> 78 | 79 | <style lang="less" scoped> 80 | 81 | </style> 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/page/fundList/components/chinaTabsTable.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="chinaTabsTable"> 3 | <el-table 4 | :data="tableData" 5 | style="width: 100%" align='center'> 6 | <el-table-column 7 | prop="ID" 8 | label="序号" 9 | align='center' 10 | width="80"> 11 | <template slot-scope="scope"> 12 | {{scope.$index+1}} 13 | </template> 14 | </el-table-column> 15 | <el-table-column 16 | prop="provinces" 17 | label="省份" 18 | align='center' 19 | width="140"> 20 | </el-table-column> 21 | <el-table-column 22 | prop="orderMoney" 23 | label="投资总额" 24 | align='center' 25 | width="120" 26 | sortable> 27 | <template slot-scope="scope"> 28 | <span style="color:#CC0033">{{ scope.row.orderMoney }}</span> 29 | </template> 30 | </el-table-column> 31 | <el-table-column 32 | prop="incomeMoney" 33 | label="收益金额" 34 | align='center' 35 | width="120" 36 | sortable> 37 | <template slot-scope="scope"> 38 | <span style="color:#00d053;">+{{ scope.row.incomeMoney }}</span> 39 | </template> 40 | </el-table-column> 41 | <el-table-column 42 | prop="payType" 43 | label="主要投资项目" 44 | align='center' 45 | width="120"> 46 | <template slot-scope="scope"> 47 | <el-tag 48 | type="info" 49 | close-transition> 50 | {{scope.row.payType}} 51 | </el-tag> 52 | </template> 53 | </el-table-column> 54 | <el-table-column 55 | prop="orderPeriod" 56 | label="投资周期" 57 | align='center' 58 | width="120"> 59 | </el-table-column> 60 | <el-table-column 61 | prop="orderPersonConunt" 62 | label="投资人数" 63 | align='center' 64 | width="120"> 65 | </el-table-column> 66 | <el-table-column 67 | prop="orderYearRate" 68 | label="投资年变化率" 69 | align='center' 70 | width='120' 71 | > 72 | </el-table-column> 73 | <el-table-column 74 | prop="remarks" 75 | label="备注" 76 | align='left' 77 | > 78 | <template slot-scope="scope"> 79 | <span style="color:#3366CC">{{scope.row.remarks}}</span> 80 | </template> 81 | </el-table-column> 82 | </el-table> 83 | </div> 84 | </template> 85 | 86 | <script> 87 | import data from '../data/chinaTabs.json'; 88 | 89 | export default { 90 | data(){ 91 | return { 92 | tableData:[], 93 | tableHeight:0, 94 | } 95 | }, 96 | props:{ 97 | toggleData:[String] 98 | }, 99 | mounted(){ 100 | this.setTableHeight(); 101 | window.onresize = () => { 102 | this.setTableHeight(); 103 | } 104 | 105 | }, 106 | methods:{ 107 | setTableHeight(){ 108 | this.$nextTick(() => { 109 | this.tableHeight = document.body.clientHeight - 280 110 | }) 111 | }, 112 | showTableData(item){ 113 | switch(item){ 114 | case 'eastChina': 115 | this.tableData = data.china.eastChina; 116 | break; 117 | case 'southChina': 118 | this.tableData = data.china.southChina; 119 | break; 120 | case 'centralChina': 121 | this.tableData = data.china.centralChina; 122 | break; 123 | case 'northChina': 124 | this.tableData = data.china.northChina; 125 | break; 126 | case 'northwestChina': 127 | this.tableData = data.china.northwestChina; 128 | break; 129 | case 'southwestChina': 130 | this.tableData = data.china.southwestChina; 131 | break; 132 | case 'northeastChina': 133 | this.tableData = data.china.northeastChina; 134 | break; 135 | case 'specialareaChina': 136 | this.tableData = data.china.specialareaChina; 137 | break; 138 | } 139 | } 140 | }, 141 | watch: { 142 | // 监听属性的变化,可以接收参数; 143 | toggleData(v) { 144 | this.showTableData(v); 145 | }, 146 | } 147 | } 148 | </script> 149 | 150 | <style lang="less"> 151 | 152 | </style> 153 | -------------------------------------------------------------------------------- /src/page/fundList/components/searchItem.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="search_container searchArea"> 3 | <el-form 4 | :inline="true" 5 | :model='search_data' 6 | :rules="rules" 7 | ref="search_data" 8 | class="demo-form-inline search-form"> 9 | <el-form-item label=""> 10 | <el-input v-model="search_data.name" placeholder="用户名" @keyup.enter.native='onScreeoutMoney("search_data")'></el-input> 11 | </el-form-item> 12 | <el-form-item> 13 | <el-button type="primary" size ="mini" icon="search" @click='onScreeoutMoney("search_data")'>筛选</el-button> 14 | </el-form-item> 15 | 16 | <el-form-item class="btnRight"> 17 | <el-button type="primary" size ="mini" icon="view" @click='onBatchDelMoney()' :disabled="searchBtnDisabled">批量删除</el-button> 18 | <!-- <el-button type="success" size ="mini" icon="view">导出Elcel</el-button> --> 19 | <el-button type="primary" size ="mini" icon="view" @click='onAddMoney()'>添加</el-button> 20 | </el-form-item> 21 | </el-form> 22 | </div> 23 | </template> 24 | 25 | <script> 26 | import { mapGetters } from 'vuex' 27 | 28 | export default { 29 | name:'searchItem', 30 | data(){ 31 | return { 32 | search_data:{ 33 | startTime:'', 34 | endTime:'', 35 | name:'' 36 | }, 37 | rules: { 38 | name: [ 39 | { required: true, message: '请输入用户名', trigger: 'blur' }, 40 | ] 41 | } 42 | } 43 | }, 44 | computed:{ 45 | ...mapGetters(['searchBtnDisabled']), 46 | 47 | }, 48 | created(){ 49 | }, 50 | methods:{ 51 | onScreeoutMoney(searchForm){ 52 | this.$refs[searchForm].validate((valid) => { 53 | if (valid) { 54 | this.$store.commit('SET_SEARCH',this.search_data); 55 | this.$emit("searchList"); 56 | } 57 | }) 58 | }, 59 | onAddMoney(){ 60 | this.$emit("showDialog",'add'); 61 | }, 62 | onBatchDelMoney(){ 63 | this.$emit("onBatchDelMoney"); 64 | } 65 | } 66 | } 67 | </script> 68 | 69 | <style lang="less" scoped> 70 | .search_container{ 71 | margin-bottom: 20px; 72 | } 73 | .btnRight{ 74 | float: right; 75 | margin-right: 0px !important; 76 | } 77 | .searchArea{ 78 | background:rgba(255,255,255,1); 79 | border-radius:2px; 80 | padding: 18px 18px 0; 81 | } 82 | </style> 83 | -------------------------------------------------------------------------------- /src/page/fundList/moneyData/index.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="fillcontain"> 3 | <p>{{topTitle}}</p> 4 | </div> 5 | </template> 6 | 7 | <script> 8 | import { mapGetters } from "vuex"; 9 | export default { 10 | name:'moneyData', 11 | data(){ 12 | return { 13 | 14 | } 15 | }, 16 | computed:{ 17 | ...mapGetters(['topTitle']) 18 | }, 19 | created(){ 20 | 21 | }, 22 | methods:{ 23 | 24 | } 25 | } 26 | 27 | </script> 28 | 29 | <style lang="less" scoped> 30 | 31 | </style> -------------------------------------------------------------------------------- /src/page/index/components/cardList.vue: -------------------------------------------------------------------------------- 1 | 2 | <template> 3 | <div class="cardContainer"> 4 | <el-row> 5 | <el-col> 6 | <el-card :body-style="{ padding: '0px' }" class="cardBody"> 7 | <a :href="github" target="_blank"> 8 | <img :src="userImg" class="userImg" alt=""> 9 | </a> 10 | <div class="progress wflex"> 11 | <div class="rflex"> 12 | <span class="title">vue:</span><el-progress :percentage="100" status="success"></el-progress> 13 | </div> 14 | <div class="rflex"> 15 | <span class="title">js:</span><el-progress :percentage="80"></el-progress> 16 | </div> 17 | <div class="rflex"> 18 | <span class="title">css:</span><el-progress :percentage="60"></el-progress> 19 | </div> 20 | <div class="rflex"> 21 | <span class="title">html:</span><el-progress :percentage="90"></el-progress> 22 | </div> 23 | <div class="rflex"> 24 | <span class="title">react:</span><el-progress :percentage="0"></el-progress> 25 | </div> 26 | <div class="rflex"> 27 | <span class="title">angular:</span><el-progress :percentage="0"></el-progress> 28 | </div> 29 | </div> 30 | </el-card> 31 | </el-col> 32 | </el-row> 33 | </div> 34 | </template> 35 | 36 | <script> 37 | import userImg from "@/assets/img/avatar-3.png"; 38 | import { github } from "@/utils/env"; 39 | 40 | export default { 41 | name:'cardList', 42 | data() { 43 | return { 44 | userImg:userImg, 45 | github:github 46 | }; 47 | } 48 | } 49 | </script> 50 | 51 | <style lang="less" scoped> 52 | .cardContainer{ 53 | padding: 10px; 54 | background: #fff; 55 | box-sizing: border-box; 56 | height:407px; 57 | max-height: 407px; 58 | overflow: hidden; 59 | border-radius: 6px; 60 | .userImg{ 61 | width: 100%; 62 | height: 236px; 63 | } 64 | .progress { 65 | padding: 10px; 66 | .rflex{ 67 | justify-content: space-between; 68 | align-items: center; 69 | .title{ 70 | width:65px; 71 | } 72 | .el-progress { 73 | flex: 1; 74 | } 75 | } 76 | } 77 | } 78 | .time { 79 | font-size: 13px; 80 | color: #999; 81 | } 82 | 83 | .bottom { 84 | margin-top: 13px; 85 | line-height: 12px; 86 | } 87 | 88 | .button { 89 | padding: 0; 90 | float: right; 91 | } 92 | 93 | .image { 94 | width: 100%; 95 | display: block; 96 | } 97 | 98 | .clearfix:before, 99 | .clearfix:after { 100 | display: table; 101 | content: ""; 102 | } 103 | 104 | .clearfix:after { 105 | clear: both 106 | } 107 | </style> 108 | 109 | -------------------------------------------------------------------------------- /src/page/index/components/commentList.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="commentContainer cflex"> 3 | <div class="comment rflex" v-for="(item,index) in commentData" :key="index"> 4 | <div class="left"> 5 | <img class="userImg" :src="userImg" alt="img" /> 6 | </div> 7 | <div class="right cflex wflex"> 8 | <p class="username">{{item.username}}</p> 9 | <p class="content">{{item.commentContent.substring(0,100)}}</p> 10 | <p class="dateTime"><icon-svg icon-class="icontime" />{{item.date}}</p> 11 | </div> 12 | </div> 13 | </div> 14 | </template> 15 | 16 | <script> 17 | import { getSalesTableList } from "@/api/sales"; 18 | import userImg from "@/assets/img/avatar-3.png"; 19 | 20 | export default { 21 | name:'commentList', 22 | data() { 23 | return { 24 | commentData: [], 25 | userImg:userImg 26 | } 27 | }, 28 | mounted(){ 29 | this.getSalesList(); 30 | }, 31 | methods:{ 32 | // 获取列表数据 33 | getSalesList(){ 34 | getSalesTableList({}).then(res => { 35 | let list = res.data.list 36 | this.commentData = list.slice(0,3); 37 | }) 38 | }, 39 | } 40 | } 41 | </script> 42 | 43 | <style lang="less" scoped> 44 | .commentContainer{ 45 | background: #fff; 46 | padding: 10px; 47 | box-sizing: border-box; 48 | height: 407px; 49 | max-height: 407px; 50 | overflow: hidden; 51 | border-radius: 6px; 52 | justify-content: space-between; 53 | .comment{ 54 | border-bottom: 1px solid #e8e8e8; 55 | padding-bottom: 5px; 56 | .left{ 57 | width:80px; 58 | text-align: center; 59 | .userImg{ 60 | width:50px; 61 | border-radius: 50%; 62 | } 63 | } 64 | .right{ 65 | justify-content: space-between; 66 | height: 120px; 67 | .username{ 68 | font-size: 14px; 69 | font-weight: bold; 70 | } 71 | .content{ 72 | font-size: 13px; 73 | line-height: 20px; 74 | } 75 | .dateTime{ 76 | text-align: right; 77 | font-size: 13px; 78 | color:#87DE75; 79 | .svg-icon{ 80 | margin-right: 5px; 81 | } 82 | } 83 | 84 | } 85 | } 86 | } 87 | </style> -------------------------------------------------------------------------------- /src/page/index/components/logList.vue: -------------------------------------------------------------------------------- 1 | 2 | <template> 3 | <div class="logContainer"> 4 | <el-card class="box-card"> 5 | <div slot="header" class="clearfix"> 6 | <a :href="github" target="_blank"> 7 | <icon-svg icon-class="iconGithub" /> 8 | </a> 9 | <span>项目更新日志:</span> 10 | </div> 11 | <div class="logArea el-scrollbar"> 12 | <div class="item" v-for="(item,index) in logsData" :key="index"> 13 | <p class="timeArea"> 14 | <span class="title">日期:</span> 15 | <span class="title time">{{item.createTime}}</span> 16 | </p> 17 | <div class="logContent"> 18 | <p class="title">更新内容:</p> 19 | <ul class="logUl"> 20 | <li v-for="(citem,cindex) in item.data" :key="cindex">{{citem}}</li> 21 | </ul> 22 | </div> 23 | </div> 24 | </div> 25 | 26 | </el-card> 27 | </div> 28 | </template> 29 | 30 | <script> 31 | import logsData from "@/assets/datas/logs.json"; 32 | import { github } from "@/utils/env"; 33 | 34 | export default { 35 | name:'logList', 36 | data() { 37 | return { 38 | logsData:logsData.data, 39 | github:github 40 | }; 41 | } 42 | } 43 | </script> 44 | 45 | <style lang="less" scoped> 46 | .logContainer{ 47 | padding: 10px; 48 | background: #fff; 49 | box-sizing: border-box; 50 | height:370px; 51 | max-height: 370px; 52 | overflow: hidden; 53 | border-radius: 6px; 54 | .logArea{ 55 | overflow: auto; 56 | height: 100%; 57 | } 58 | .item{ 59 | .title{ 60 | font-size: 13px; 61 | } 62 | .time{ 63 | color:#87DE75; 64 | } 65 | .logContent{ 66 | .logUl{ 67 | padding-left: 30px; 68 | li{ 69 | font-size: 12px; 70 | list-style: disc; 71 | line-height: 20px; 72 | } 73 | } 74 | } 75 | } 76 | } 77 | .clearfix:before, 78 | .clearfix:after { 79 | display: table; 80 | content: ""; 81 | } 82 | 83 | .clearfix:after { 84 | clear: both 85 | } 86 | </style> 87 | 88 | -------------------------------------------------------------------------------- /src/page/index/components/salesTable.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="salesTable"> 3 | <el-table 4 | :data="tableData" 5 | stripe 6 | height="424" 7 | style="width: 100%"> 8 | <el-table-column 9 | class-name="salesUsername" 10 | prop="username" 11 | label="USERNAME" 12 | width="150" 13 | > 14 | <template slot-scope="scope"> 15 | <img class="userImg" :src="userImg" alt="tuxiang"/> 16 | {{(scope.row.username).substring(0,12)}} 17 | </template> 18 | </el-table-column> 19 | <el-table-column 20 | class-name="salesPrice" 21 | prop="price" 22 | label="PRICE" 23 | width="80" 24 | > 25 | <template slot-scope="scope"> 26 | <span v-if="scope.row.status === 1" class="saleColor">$ {{scope.row.price}}</span> 27 | <span v-if="scope.row.status === 2" class="taxColor">$ {{scope.row.price}}</span> 28 | <span v-if="scope.row.status === 3" class="extenedColor">$ {{scope.row.price}}</span> 29 | <span v-if="scope.row.status0 === 4" class="likeColor">$ {{scope.row.price}}</span> 30 | </template> 31 | </el-table-column> 32 | <el-table-column 33 | prop="date" 34 | label="DATE" 35 | width="160" 36 | > 37 | <template slot-scope="scope"> 38 | <icon-svg icon-class="icontime" /> 39 | {{scope.row.date}} 40 | </template> 41 | </el-table-column> 42 | <el-table-column 43 | class-name="salesStatus" 44 | prop="status" 45 | label="STATUS" 46 | > 47 | <template slot-scope="scope"> 48 | <span v-if="scope.row.status === 1" class="saleBgcolor">SALE</span> 49 | <span v-if="scope.row.status === 2" class="taxBgcolor">TAX</span> 50 | <span v-if="scope.row.status === 3" class="extenedBgcolor">EXTENDED</span> 51 | <span v-if="scope.row.status === 4" class="likeBgcolor">LIKE</span> 52 | </template> 53 | </el-table-column> 54 | 55 | </el-table> 56 | </div> 57 | </template> 58 | 59 | <script> 60 | import { getSalesTableList } from "@/api/sales"; 61 | import userImg from "@/assets/img/avatar-3.png"; 62 | 63 | export default { 64 | data() { 65 | return { 66 | tableData: [], 67 | userImg:userImg 68 | } 69 | }, 70 | mounted(){ 71 | this.getSalesList(); 72 | }, 73 | methods:{ 74 | // 获取列表数据 75 | getSalesList(){ 76 | getSalesTableList({}).then(res => { 77 | console.log(res); 78 | this.pageTotal = res.data.total 79 | this.tableData = res.data.list 80 | }) 81 | }, 82 | } 83 | } 84 | </script> 85 | 86 | <style lang="less" scoped> 87 | 88 | </style> -------------------------------------------------------------------------------- /src/page/login.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="login_page"> 3 | <transition name="form-fade" mode="in-out"> 4 | <section class="form_contianer"> 5 | <div class='titleArea rflex'> 6 | <img class="logo" :src="logo" alt="小爱admin"> 7 | <span class='title'>小爱<i>Admin</i></span> 8 | </div> 9 | <el-form :model="loginForm" :rules="rules" ref="loginForm" class="loginForm"> 10 | <el-form-item prop="username" class="login-item"> 11 | <span class="loginTips"><icon-svg icon-class="iconuser" /></span> 12 | <el-input @keyup.enter.native ="submitForm('loginForm')" class="area" type="text" placeholder="用户名" v-model="loginForm.username" ></el-input> 13 | </el-form-item> 14 | <el-form-item prop="password" class="login-item"> 15 | <span class="loginTips"><icon-svg icon-class="iconLock" /></span> 16 | <el-input @keyup.enter.native ="submitForm('loginForm')" class="area" type="password" placeholder="密码" v-model="loginForm.password"></el-input> 17 | </el-form-item> 18 | <el-form-item> 19 | <el-button type="primary" @click="submitForm('loginForm')" class="submit_btn">SIGN IN</el-button> 20 | </el-form-item> 21 | <div class="tiparea"> 22 | <p class="wxtip">温馨提示:</p> 23 | <p class="tip">用户名为:admin/editor<span>(可用于切换权限)</span></p> 24 | <p class="tip">密码为:123456</p> 25 | </div> 26 | <div class="sanFangArea"> 27 | <p class="title">第三方账号登录</p> 28 | <ul class="rflex"> 29 | <li @click="loginByWechat"> 30 | <icon-svg icon-class="iconwechat" /> 31 | </li> 32 | <li> 33 | <icon-svg icon-class="iconweibo" /> 34 | </li> 35 | <li> 36 | <icon-svg icon-class="iconGithub" /> 37 | </li> 38 | </ul> 39 | </div> 40 | </el-form> 41 | </section> 42 | </transition> 43 | </div> 44 | </template> 45 | 46 | <script> 47 | import logoImg from "@/assets/img/logo.png"; 48 | import { login } from "@/api/user"; 49 | import { setToken } from '@/utils/auth' 50 | 51 | export default { 52 | data(){ 53 | return { 54 | logo:logoImg, 55 | loginForm: { 56 | username: 'admin', 57 | password: '123456' 58 | }, 59 | rules: { 60 | username: [ 61 | { required: true, message: '请输入用户名', trigger: 'blur' }, 62 | { min: 2, max: 8, message: '长度在 2 到 8 个字符', trigger: 'blur' } 63 | ], 64 | password: [ 65 | { required: true, message: '请输入密码', trigger: 'blur' } 66 | ], 67 | } 68 | } 69 | }, 70 | mounted(){ 71 | }, 72 | methods: { 73 | loginByWechat(){ 74 | }, 75 | showMessage(type,message){ 76 | this.$message({ 77 | type: type, 78 | message: message 79 | }); 80 | }, 81 | submitForm(loginForm) { 82 | this.$refs[loginForm].validate((valid) => { 83 | if (valid) { 84 | let userinfo = this.loginForm; 85 | login(userinfo).then(res => { 86 | let userList = res.data.userList; 87 | setToken("Token",userList.token) 88 | this.$router.push({ path: '/' }) 89 | this.$store.dispatch('initLeftMenu'); //设置左边菜单始终为展开状态 90 | }) 91 | } 92 | }); 93 | } 94 | } 95 | } 96 | </script> 97 | 98 | <style lang="less" scoped> 99 | .login_page{ 100 | position: absolute; 101 | width: 100%; 102 | height: 100%; 103 | background: url(../assets/img/bg9.jpg) no-repeat center center; 104 | background-size: 100% 100%; 105 | } 106 | .form_contianer{ 107 | position: absolute; 108 | top: 50%; 109 | left: 50%; 110 | transform: translate(-50%,-50%); 111 | background: #fff; 112 | width:370px; 113 | border-radius: 5px; 114 | padding: 25px; 115 | text-align: center; 116 | .titleArea{ 117 | justify-content: center; 118 | align-items: center; 119 | text-transform: uppercase; 120 | font-size: 22px; 121 | width: 100%; 122 | padding-bottom: 20px; 123 | .logo{ 124 | width: 40px; 125 | margin-right: 10px; 126 | } 127 | .title{ 128 | i{ 129 | color: #FF6C60; 130 | } 131 | } 132 | } 133 | 134 | .loginForm{ 135 | .submit_btn{ 136 | width: 100%; 137 | padding:13px 0; 138 | font-size: 16px; 139 | } 140 | .loginTips{ 141 | position: absolute; 142 | left: 10px; 143 | z-index: 20; 144 | // color: #FF7C1A; 145 | font-size: 18px; 146 | top: 50%; 147 | transform: translateY(-50%); 148 | } 149 | } 150 | } 151 | 152 | .manage_tip{ 153 | margin-bottom:20px; 154 | .title{ 155 | font-family: cursive; 156 | font-weight: bold; 157 | font-size: 26px; 158 | color:#fff; 159 | } 160 | .logo{ 161 | width:60px; 162 | height:60px; 163 | } 164 | } 165 | 166 | .tiparea{ 167 | text-align:left; 168 | font-size: 12px; 169 | color: #4cbb15; 170 | padding: 10px 0; 171 | .tip{ 172 | margin-left: 54px; 173 | } 174 | } 175 | 176 | .form-fade-enter-active, .form-fade-leave-active { 177 | transition: all 1s; 178 | } 179 | .form-fade-enter, .form-fade-leave-active { 180 | transform: translate3d(0, -50px, 0); 181 | opacity: 0; 182 | } 183 | .loginForm{ 184 | .el-button--primary{ 185 | background-color:#FF7C1A; 186 | border:1px solid #FF7C1A; 187 | } 188 | } 189 | .sanFangArea{ 190 | border-top: 1px solid #DCDFE6; 191 | padding: 10px 0; 192 | display: none; 193 | .title{ 194 | font-size: 14px; 195 | color: #8b9196; 196 | margin-bottom: 10px; 197 | } 198 | ul{ 199 | li{ 200 | flex:1; 201 | display: flex; 202 | align-items: center; 203 | justify-content: center; 204 | cursor: pointer; 205 | .svg-icon{ 206 | font-size: 24px; 207 | } 208 | } 209 | } 210 | } 211 | </style> 212 | -------------------------------------------------------------------------------- /src/page/permission/components/SwitchRoles.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div> 3 | <!-- <div style="margin-bottom:15px;">{{$t('permission.roles')}}: {{roles}}</div> 4 | {{$t('permission.switchRoles')}}: --> 5 | <div class="ownPer">我的权限:{{roles}}</div> 6 | <div class="rflex"> 7 | <p>切换权限:</p> 8 | <el-radio-group v-model="switchRoles"> 9 | <el-radio-button label="editor"></el-radio-button> 10 | <el-radio-button label="admin"></el-radio-button> 11 | </el-radio-group> 12 | </div> 13 | </div> 14 | </template> 15 | 16 | <script> 17 | export default { 18 | computed: { 19 | roles() { 20 | return this.$store.getters.roles 21 | }, 22 | switchRoles: { 23 | get() { 24 | return this.roles[0] 25 | }, 26 | set(val) { 27 | this.$store.dispatch('ChangeRoles', val).then(() => { 28 | this.$emit('change') 29 | }) 30 | } 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * 如何获取到我的权限: 37 | * 1、用户登录成功之后,根据token调取接口getInfo()获取到用户roles并存入vuex; 38 | * 39 | * 40 | */ 41 | 42 | </script> 43 | 44 | <style lang="less" scoped> 45 | .ownPer{ 46 | margin-bottom: 20px; 47 | } 48 | .rflex{ 49 | align-items: center; 50 | margin-bottom: 20px; 51 | } 52 | </style> 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/page/permission/directive.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="fillcontain"> 3 | <div class="contain" ref="contain"> 4 | <switch-roles @change="handleRolesChange" /> 5 | <div :key="key" class="cflex"> 6 | <span v-permission="['admin']" class="permission-alert"> 7 | Only admin can see this 8 | <el-button type="warning">admin</el-button> 9 | <el-button type="danger">危险按钮</el-button> 10 | </span> 11 | 12 | <span v-permission="['editor']" class="permission-alert"> 13 | Only editor can see this 14 | <el-button type="success">编辑</el-button> 15 | <el-button type="info">信息按钮</el-button> 16 | </span> 17 | 18 | <span v-permission="['admin','editor']" class="permission-alert"> 19 | Both adminand editor can see this 20 | <el-button type="primary">主要按钮</el-button> 21 | <el-button type="success">成功按钮</el-button> 22 | <el-button type="info">信息按钮</el-button> 23 | <el-button type="warning">警告按钮</el-button> 24 | <el-button type="danger">危险按钮</el-button> 25 | </span> 26 | </div> 27 | </div> 28 | </div> 29 | </template> 30 | 31 | <script> 32 | import permission from '@/directive/permission/index.js' // 权限判断指令 33 | import SwitchRoles from './components/SwitchRoles' 34 | import * as mutils from '@/utils/mUtils' 35 | 36 | export default { 37 | name: 'directivePermission', 38 | components: { SwitchRoles }, 39 | directives: { permission }, 40 | data() { 41 | return { 42 | key: 1 // 为了能每次切换权限的时候重新初始化指令 43 | } 44 | }, 45 | mounted(){ 46 | mutils.setContentHeight(this,this.$refs.contain,210); 47 | }, 48 | methods: { 49 | handleRolesChange() { 50 | this.key++ 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * 添加按钮权限的业务逻辑: 57 | * 1、在需要添加权限的按钮上或按钮区域内,注册全局指令v-permission="['admin']",接收的值为数组形式; 58 | * 2、指令内部的计算逻辑(参考directive/permission/permisson.js): 59 | * 通过inserted函数,当被绑定的元素插入到DOM中时,如果指令的value为数组形式并传入roles信息时, 60 | * 用户当前roles与指令roles,进行循环匹配,只要能匹配到就返回true;不能匹配,则隐藏该元素; 61 | * 3、全局注册指令:Vue.directive('permission', permission) 62 | * 4、Vue.use(install); 63 | * 64 | */ 65 | </script> 66 | 67 | <style lang="less" scoped> 68 | .fillcontain { 69 | .contain{ 70 | background: #fff; 71 | padding: 20px; 72 | box-sizing: border-box; 73 | } 74 | .cflex{ 75 | .permission-alert{ 76 | margin-bottom: 20px; 77 | } 78 | } 79 | } 80 | </style> 81 | 82 | -------------------------------------------------------------------------------- /src/page/permission/page.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="fillcontain" > 3 | <div class="contain" ref="contain"> 4 | <switch-roles @change="handleRolesChange" /> 5 | </div> 6 | </div> 7 | </template> 8 | 9 | <script> 10 | import SwitchRoles from './components/SwitchRoles' 11 | import * as mutils from '@/utils/mUtils' 12 | 13 | export default { 14 | name: 'pagePermission', 15 | components: { SwitchRoles }, 16 | mounted(){ 17 | mutils.setContentHeight(this,this.$refs.contain,210); 18 | }, 19 | methods: { 20 | handleRolesChange() { 21 | // 没有这个可以匹配的路由"/permission/index",则会定位到404页面 22 | this.$router.push({ path: '/permission/index?time=' + +new Date() }) 23 | }, 24 | } 25 | } 26 | </script> 27 | 28 | <style lang="less" scoped> 29 | .contain{ 30 | background: #fff; 31 | padding: 20px; 32 | box-sizing: border-box; 33 | } 34 | </style> 35 | -------------------------------------------------------------------------------- /src/page/share/components/hengShare.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="shareArea cflex"> 3 | <p class="shareTitle">分享组件一:横向排列</p> 4 | <div class="bottom rflex"> 5 | <span class="toTitle">分享到:</span> 6 | <ul class="shareUl rflex wflex"> 7 | <li> 8 | <div class="item" @mouseover="showqrcode()" @mouseout="hideqrcode()"> 9 | <icon-svg icon-class="iconwechat" /> 10 | </div> 11 | <div class="qrcodeArea" v-show="qrcode.show"> 12 | <p class="saoTitle">扫一扫</p> 13 | <div class="qrcode" id="qrCodeUrl"></div> 14 | </div> 15 | </li> 16 | <li> 17 | <div class="item" @click="shareToWeibo()"> 18 | <icon-svg icon-class="iconweibo" /> 19 | </div> 20 | </li> 21 | <li> 22 | <div class="item" @click="shareToQQ()"> 23 | <icon-svg icon-class="iconqq" /> 24 | </div> 25 | </li> 26 | <li> 27 | <div class="item" @click="shareToQQzone()"> 28 | <icon-svg icon-class="iconqq_zone" /> 29 | </div> 30 | </li> 31 | 32 | <li> 33 | <div class="item" @click="shareToDouban()"> 34 | <icon-svg icon-class="icondouban" /> 35 | </div> 36 | </li> 37 | </ul> 38 | </div> 39 | </div> 40 | </template> 41 | 42 | <script> 43 | import QRCode from 'qrcodejs2' 44 | import { shareUrl } from "@/utils/env"; 45 | 46 | export default { 47 | name:'YanShare', 48 | data(){ 49 | return { 50 | qrcode:{ 51 | show:false 52 | }, 53 | qrcodeObj:{ 54 | text:shareUrl, // 要分享的网页路径 55 | width:80, 56 | height:80, 57 | colorDark: '#000000', 58 | colorLight: '#ffffff', 59 | correctLevel: QRCode.CorrectLevel.H 60 | } 61 | 62 | } 63 | }, 64 | mounted(){ 65 | this.creatQrCode(); 66 | }, 67 | methods: { 68 | showqrcode(){ 69 | this.qrcode.show = true; 70 | }, 71 | hideqrcode(){ 72 | this.qrcode.show = false; 73 | }, 74 | creatQrCode() { 75 | this.$nextTick(() => { 76 | new QRCode(document.getElementById('qrCodeUrl'), this.qrcodeObj) 77 | }); 78 | }, 79 | shareToQQ(){ 80 | this.$emit('shareToQQ'); 81 | }, 82 | shareToQQzone(){ 83 | this.$emit('shareToQQzone'); 84 | }, 85 | shareToWeibo(){ 86 | this.$emit('shareToWeibo'); 87 | }, 88 | shareToDouban(){ 89 | this.$emit('shareToDouban'); 90 | } 91 | 92 | } 93 | } 94 | </script> 95 | 96 | <style lang="less" scoped> 97 | .shareArea{ 98 | width: 340px; 99 | align-items: center; 100 | background: #fff; 101 | border-radius: 4px; 102 | .shareTitle{ 103 | border-bottom: 1px solid #e8e8e8; 104 | padding: 10px; 105 | box-sizing: border-box; 106 | width: 100%; 107 | font-size:14px; 108 | } 109 | .bottom{ 110 | align-items: center; 111 | padding: 20px 8px; 112 | width: 100%; 113 | height: 100%; 114 | box-sizing: border-box; 115 | .toTitle{ 116 | font-size: 13px; 117 | } 118 | .shareUl{ 119 | justify-content: space-between; 120 | li{ 121 | display: flex; 122 | flex-direction: column; 123 | align-items: center; 124 | position: relative; 125 | cursor: pointer; 126 | .title{ 127 | margin-bottom: 10px; 128 | font-size: 13px; 129 | color:#a9d86e; 130 | } 131 | .item{ 132 | background: #EFF2F7; 133 | width: 40px; 134 | height: 40px; 135 | border-radius: 50%; 136 | display: flex; 137 | justify-content: center; 138 | align-items: center; 139 | .svg-icon{ 140 | font-size: 24px; 141 | } 142 | .active{ 143 | color:#FF6C60; 144 | } 145 | } 146 | .qrcodeArea{ 147 | position: absolute; 148 | top: 50px; 149 | left: -30px; 150 | text-align: center; 151 | border: 1px solid #a9d86e; 152 | border-radius: 4px; 153 | padding: 10px; 154 | z-index: 99; 155 | background: #fff; 156 | .saoTitle{ 157 | font-size: 14px; 158 | color:#a9d86e; 159 | margin-bottom: 5px; 160 | } 161 | } 162 | } 163 | } 164 | } 165 | 166 | } 167 | 168 | </style> 169 | -------------------------------------------------------------------------------- /src/page/share/components/index.js: -------------------------------------------------------------------------------- 1 | import HengShare from "./hengShare"; 2 | import InviteShare from "./inviteShare"; 3 | import JianshuShare from "./jianshuShare"; 4 | import JianshuLeftShare from "./jianshuLeftShare"; 5 | import WxCodeModal from "./wxCodeModal"; 6 | import JuejinShare from "./juejinShare"; 7 | import InfoShare from "./infoShare"; 8 | import SinaShare from "./sinaShare"; 9 | import YanShare from "./yanShare"; 10 | 11 | export { 12 | HengShare, 13 | InviteShare, 14 | JianshuShare, 15 | JianshuLeftShare, 16 | WxCodeModal, 17 | JuejinShare, 18 | InfoShare, 19 | SinaShare, 20 | YanShare 21 | } -------------------------------------------------------------------------------- /src/page/share/components/infoShare.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="shareArea cflex"> 3 | <p class="shareTitle">分享组件六:横向排列</p> 4 | <div class="bottom cflex"> 5 | <div class="lineArea rflex"> 6 | <div class="line"></div> 7 | <span class="lineTitle">分享到</span> 8 | <div class="line"></div> 9 | </div> 10 | <ul class="shareUl rflex wflex"> 11 | <li> 12 | <div class="item" @mouseover="showqrcode()" @mouseout="hideqrcode()"> 13 | <icon-svg icon-class="iconwechat" /> 14 | </div> 15 | <div class="qrcodeArea" v-show="qrcode.show"> 16 | <p class="saoTitle">扫一扫</p> 17 | <div class="qrcode" ref='qrCodeUrl1'></div> 18 | </div> 19 | </li> 20 | <li> 21 | <div class="item" @click="shareToWeibo()"> 22 | <icon-svg icon-class="iconweibo" /> 23 | </div> 24 | </li> 25 | <li> 26 | <div class="item" @click="shareToQQ()"> 27 | <icon-svg icon-class="iconqq" /> 28 | </div> 29 | </li> 30 | <li> 31 | <div class="item" @click="shareToQQzone()"> 32 | <icon-svg icon-class="iconqq_zone" /> 33 | </div> 34 | </li> 35 | 36 | <li> 37 | <div class="item" @click="shareToDouban()"> 38 | <icon-svg icon-class="icondouban" /> 39 | </div> 40 | </li> 41 | </ul> 42 | </div> 43 | </div> 44 | </template> 45 | 46 | <script> 47 | import QRCode from 'qrcodejs2' 48 | import { shareUrl } from "@/utils/env"; 49 | 50 | export default { 51 | name:'infoShare', 52 | data(){ 53 | return { 54 | qrcode:{ 55 | show:false 56 | }, 57 | qrcodeObj:{ 58 | text:shareUrl, // 要分享的网页路径 59 | width:80, 60 | height:80, 61 | colorDark: '#000000', 62 | colorLight: '#ffffff', 63 | correctLevel: QRCode.CorrectLevel.H 64 | } 65 | 66 | } 67 | }, 68 | mounted(){ 69 | this.creatQrCode(); 70 | }, 71 | methods: { 72 | showqrcode(){ 73 | this.qrcode.show = true; 74 | }, 75 | hideqrcode(){ 76 | this.qrcode.show = false; 77 | }, 78 | creatQrCode() { 79 | const qrcode = new QRCode(this.$refs.qrCodeUrl1, this.qrcodeObj) 80 | }, 81 | shareToQQ(){ 82 | this.$emit('shareToQQ'); 83 | }, 84 | shareToQQzone(){ 85 | this.$emit('shareToQQzone'); 86 | }, 87 | shareToWeibo(){ 88 | this.$emit('shareToWeibo'); 89 | }, 90 | shareToDouban(){ 91 | this.$emit('shareToDouban'); 92 | } 93 | 94 | } 95 | } 96 | </script> 97 | 98 | <style lang="less" scoped> 99 | .shareArea{ 100 | width: 340px; 101 | align-items: center; 102 | background: #fff; 103 | border-radius: 4px; 104 | .shareTitle{ 105 | border-bottom: 1px solid #e8e8e8; 106 | padding: 10px; 107 | box-sizing: border-box; 108 | width: 100%; 109 | font-size:14px; 110 | } 111 | .bottom{ 112 | align-items: center; 113 | padding: 20px; 114 | width: 100%; 115 | height: 100%; 116 | box-sizing: border-box; 117 | .lineArea{ 118 | padding: 10px; 119 | width:100%; 120 | text-align: center; 121 | align-items: center; 122 | justify-content: space-between; 123 | .lineTitle{ 124 | font-size: 13px; 125 | margin: 0 5px; 126 | } 127 | .line{ 128 | border-bottom:1px solid gold; 129 | flex:0.5; 130 | } 131 | } 132 | .shareUl{ 133 | width: 100%; 134 | justify-content: space-between; 135 | li{ 136 | display: flex; 137 | flex-direction: column; 138 | align-items: center; 139 | position: relative; 140 | cursor: pointer; 141 | .title{ 142 | margin-bottom: 10px; 143 | font-size: 13px; 144 | color:#a9d86e; 145 | } 146 | .item{ 147 | background: #EFF2F7; 148 | width: 40px; 149 | height: 40px; 150 | border-radius: 50%; 151 | display: flex; 152 | justify-content: center; 153 | align-items: center; 154 | .svg-icon{ 155 | font-size: 24px; 156 | } 157 | .active{ 158 | color:#FF6C60; 159 | } 160 | } 161 | .qrcodeArea{ 162 | position: absolute; 163 | top: 50px; 164 | left: -30px; 165 | text-align: center; 166 | border: 1px solid #a9d86e; 167 | border-radius: 4px; 168 | padding: 10px; 169 | z-index: 99; 170 | background: #fff; 171 | .saoTitle{ 172 | font-size: 14px; 173 | color:#a9d86e; 174 | margin-bottom: 5px; 175 | } 176 | } 177 | } 178 | } 179 | } 180 | 181 | } 182 | 183 | </style> 184 | -------------------------------------------------------------------------------- /src/page/share/components/inviteShare.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="shareArea cflex"> 3 | <p class="shareTitle">分享组件二:横向排列</p> 4 | <div class="bottom cflex"> 5 | <p class="title">邀请好友加入 >></p> 6 | <ul class="shareUl rflex wflex"> 7 | <li> 8 | <div class="item" @mouseover="showqrcode()" @mouseout="hideqrcode()"> 9 | <icon-svg icon-class="iconwechat" /> 10 | </div> 11 | <div class="qrcodeArea" v-show="qrcode.show"> 12 | <p class="saoTitle">扫一扫</p> 13 | <div class="qrcode" ref="qrCodeUrl2"></div> 14 | </div> 15 | </li> 16 | <li> 17 | <div class="item" @click="shareToWeibo()"> 18 | <icon-svg icon-class="iconweibo" /> 19 | </div> 20 | </li> 21 | <li> 22 | <div class="item" @click="shareToQQ()"> 23 | <icon-svg icon-class="iconqq" /> 24 | </div> 25 | </li> 26 | <li> 27 | <div class="item" @click="shareToQQzone()"> 28 | <icon-svg icon-class="iconqq_zone" /> 29 | </div> 30 | </li> 31 | 32 | <li> 33 | <div class="item" @click="shareToDouban()"> 34 | <icon-svg icon-class="icondouban" /> 35 | </div> 36 | </li> 37 | </ul> 38 | <p class="shareIntro">朋友注册后,你会获得书币,书币可以用来去市集兑换物品。</p> 39 | </div> 40 | </div> 41 | </template> 42 | 43 | <script> 44 | import QRCode from 'qrcodejs2' 45 | import { shareUrl } from "@/utils/env"; 46 | 47 | export default { 48 | name:'inviteShare', 49 | data(){ 50 | return { 51 | qrcode:{ 52 | show:false 53 | }, 54 | qrcodeObj:{ 55 | text:shareUrl, // 要分享的网页路径 56 | width:80, 57 | height:80, 58 | colorDark: '#000000', 59 | colorLight: '#ffffff', 60 | correctLevel: QRCode.CorrectLevel.H 61 | } 62 | 63 | } 64 | }, 65 | mounted(){ 66 | this.creatQrCode(); 67 | }, 68 | methods: { 69 | showqrcode(){ 70 | this.qrcode.show = true; 71 | }, 72 | hideqrcode(){ 73 | this.qrcode.show = false; 74 | }, 75 | creatQrCode() { 76 | const qrcode = new QRCode(this.$refs.qrCodeUrl2, this.qrcodeObj) 77 | }, 78 | shareToQQ(){ 79 | this.$emit('shareToQQ'); 80 | }, 81 | shareToQQzone(){ 82 | this.$emit('shareToQQzone'); 83 | }, 84 | shareToWeibo(){ 85 | this.$emit('shareToWeibo'); 86 | }, 87 | shareToDouban(){ 88 | this.$emit('shareToDouban'); 89 | } 90 | 91 | } 92 | } 93 | </script> 94 | 95 | <style lang="less" scoped> 96 | .shareArea{ 97 | width: 300px; 98 | align-items: center; 99 | background: #fff; 100 | border-radius: 4px; 101 | .shareTitle{ 102 | border-bottom: 1px solid #e8e8e8; 103 | padding: 10px; 104 | box-sizing: border-box; 105 | width: 100%; 106 | font-size:14px; 107 | } 108 | .bottom{ 109 | align-items: center; 110 | width: 100%; 111 | box-sizing: border-box; 112 | padding: 20px; 113 | .title{ 114 | padding: 10px; 115 | box-sizing: border-box; 116 | width: 100%; 117 | font-size:14px; 118 | } 119 | .shareUl{ 120 | justify-content: space-between; 121 | width: 100%; 122 | padding: 0 10px; 123 | box-sizing: border-box; 124 | li{ 125 | display: flex; 126 | flex-direction: column; 127 | align-items: center; 128 | position: relative; 129 | cursor: pointer; 130 | .item{ 131 | background: #EFF2F7; 132 | width: 40px; 133 | height: 40px; 134 | border-radius: 50%; 135 | display: flex; 136 | justify-content: center; 137 | align-items: center; 138 | .svg-icon{ 139 | font-size: 24px; 140 | } 141 | .active{ 142 | color:#FF6C60; 143 | } 144 | } 145 | .qrcodeArea{ 146 | position: absolute; 147 | top: 50px; 148 | left: -30px; 149 | text-align: center; 150 | border: 1px solid #a9d86e; 151 | border-radius: 4px; 152 | padding: 10px; 153 | background: #fff; 154 | z-index: 99; 155 | .saoTitle{ 156 | font-size: 14px; 157 | color:#a9d86e; 158 | margin-bottom: 5px; 159 | } 160 | } 161 | } 162 | } 163 | .shareIntro{ 164 | padding: 10px; 165 | box-sizing: border-box; 166 | width: 100%; 167 | font-size:12px; 168 | } 169 | } 170 | 171 | } 172 | 173 | </style> 174 | -------------------------------------------------------------------------------- /src/page/share/components/jianshuLeftShare.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="shareArea cflex sharelast"> 3 | <p class="shareTitle">分享组件三:仿简书侧栏分享</p> 4 | <div class="bottom"> 5 | <el-tooltip class="item" effect="dark" content="分享文章" placement="left"> 6 | <el-button class="shareItem" v-popover:leftShareList> 7 | <icon-svg icon-class="iconshare" /> 8 | <el-popover 9 | ref="leftShareList" 10 | popper-class="moreShareList leftShareList" 11 | placement="left" 12 | trigger="click"> 13 | <div> 14 | <ul class="cflex"> 15 | <a href="#" @click="shareToWeixin()"> 16 | <li> 17 | <div class="item"> 18 | <icon-svg icon-class="iconwechat" /> 19 | <span>分享到微信</span> 20 | </div> 21 | </li> 22 | </a> 23 | <a href="#" @click="shareToWeibo()"> 24 | <li> 25 | <icon-svg icon-class="iconweibo" /> 26 | <span>分享到微博</span> 27 | </li> 28 | </a> 29 | <a href="#" @click="shareToQQ()"> 30 | <li> 31 | <icon-svg icon-class="iconqq" /> 32 | <span>分享到QQ</span> 33 | </li> 34 | </a> 35 | <a href="#" @click="shareToQQzone()"> 36 | <li> 37 | <icon-svg icon-class="iconqq_zone" /> 38 | <span>分享到QQ空间</span> 39 | </li> 40 | </a> 41 | <a href="#" @click="shareToDouban()"> 42 | <li> 43 | <icon-svg icon-class="icondouban" /> 44 | <span>分享到豆瓣</span> 45 | </li> 46 | </a> 47 | <a href="#"> 48 | <li> 49 | <icon-svg icon-class="icontwitter" /> 50 | <span>分享到Witter</span> 51 | </li> 52 | </a> 53 | <a href="#"> 54 | <li> 55 | <icon-svg icon-class="iconfacebook" /> 56 | <span>分享到Facebook</span> 57 | </li> 58 | </a> 59 | <a href="#"> 60 | <li> 61 | <icon-svg icon-class="icongoogle" /> 62 | <span>分享到Google</span> 63 | </li> 64 | </a> 65 | </ul> 66 | </div> 67 | </el-popover> 68 | </el-button> 69 | </el-tooltip> 70 | </div> 71 | </div> 72 | </template> 73 | 74 | <script> 75 | export default { 76 | name:'jianshuLeftShare', 77 | data(){ 78 | return { 79 | 80 | } 81 | }, 82 | mounted(){ 83 | 84 | }, 85 | methods: { 86 | shareToWeixin(){ 87 | this.$emit('shareToWeixin'); 88 | }, 89 | shareToQQ(){ 90 | this.$emit('shareToQQ'); 91 | }, 92 | shareToQQzone(){ 93 | this.$emit('shareToQQzone'); 94 | }, 95 | shareToWeibo(){ 96 | this.$emit('shareToWeibo'); 97 | }, 98 | shareToDouban(){ 99 | this.$emit('shareToDouban'); 100 | } 101 | 102 | } 103 | } 104 | </script> 105 | 106 | <style lang="less" scoped> 107 | .shareArea{ 108 | width: 340px; 109 | align-items: center; 110 | background: #fff; 111 | border-radius: 4px; 112 | .shareTitle{ 113 | border-bottom: 1px solid #e8e8e8; 114 | padding: 10px; 115 | box-sizing: border-box; 116 | width: 100%; 117 | font-size:14px; 118 | } 119 | .bottom{ 120 | align-items: center; 121 | width: 100%; 122 | height: 100%; 123 | box-sizing: border-box; 124 | display: flex; 125 | .shareItem{ 126 | width: 50px; 127 | height: 50px; 128 | border: 1px solid #dcdcdc; 129 | display: flex; 130 | align-items: center; 131 | justify-content: center; 132 | margin: 0 auto; 133 | } 134 | .title{ 135 | padding: 10px; 136 | box-sizing: border-box; 137 | width: 100%; 138 | font-size:14px; 139 | } 140 | .shareUl{ 141 | justify-content: space-between; 142 | align-items: center; 143 | width: 100%; 144 | padding: 0 10px; 145 | box-sizing: border-box; 146 | li{ 147 | display: flex; 148 | flex-direction: column; 149 | align-items: center; 150 | position: relative; 151 | cursor: pointer; 152 | .item{ 153 | background: #EFF2F7; 154 | width: 40px; 155 | height: 40px; 156 | border-radius: 50%; 157 | display: flex; 158 | justify-content: center; 159 | align-items: center; 160 | .svg-icon{ 161 | font-size: 24px; 162 | } 163 | .active{ 164 | color:#FF6C60; 165 | } 166 | } 167 | .moreBtn{ 168 | position: relative; 169 | font-size:14px; 170 | width:auto; 171 | height:40px; 172 | text-align: center; 173 | padding: 4px 18px; 174 | border-radius:50px; 175 | border: 1px solid #dcdcdc; 176 | } 177 | } 178 | } 179 | } 180 | 181 | } 182 | 183 | </style> 184 | -------------------------------------------------------------------------------- /src/page/share/components/jianshuShare.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="shareArea cflex"> 3 | <p class="shareTitle">分享组件三:仿简书底部分享</p> 4 | <div class="bottom cflex"> 5 | <ul class="shareUl rflex wflex"> 6 | <li> 7 | <div class="item" @click="shareToWeixin()"> 8 | <icon-svg icon-class="iconwechat" /> 9 | </div> 10 | </li> 11 | <li> 12 | <div class="item" @click="shareToWeibo()"> 13 | <icon-svg icon-class="iconweibo" /> 14 | </div> 15 | </li> 16 | <li> 17 | <div class="item" @click="shareToQQ()"> 18 | <icon-svg icon-class="iconqq" /> 19 | </div> 20 | </li> 21 | <li> 22 | <el-button class="moreBtn" v-popover:moreShareList> 23 | 更多分享 24 | <el-popover 25 | ref="moreShareList" 26 | popper-class="moreShareList" 27 | placement="top" 28 | trigger="click"> 29 | <div> 30 | <ul class="cflex"> 31 | <a href="#" @click="shareToQQzone()"> 32 | <li> 33 | <icon-svg icon-class="iconqq_zone" /> 34 | <span>分享到QQ空间</span> 35 | </li> 36 | </a> 37 | <a href="#" @click="shareToDouban()"> 38 | <li> 39 | <icon-svg icon-class="icondouban" /> 40 | <span>分享到豆瓣</span> 41 | </li> 42 | </a> 43 | <a href="#"> 44 | <li> 45 | <icon-svg icon-class="icontwitter" /> 46 | <span>分享到Witter</span> 47 | </li> 48 | </a> 49 | <a href="#"> 50 | <li> 51 | <icon-svg icon-class="iconfacebook" /> 52 | <span>分享到Facebook</span> 53 | </li> 54 | </a> 55 | <a href="#"> 56 | <li> 57 | <icon-svg icon-class="icongoogle" /> 58 | <span>分享到Google</span> 59 | </li> 60 | </a> 61 | </ul> 62 | </div> 63 | </el-popover> 64 | </el-button> 65 | </li> 66 | </ul> 67 | </div> 68 | </div> 69 | </template> 70 | 71 | <script> 72 | export default { 73 | name:'jianshuShare', 74 | data(){ 75 | return { 76 | 77 | } 78 | }, 79 | mounted(){ 80 | }, 81 | methods: { 82 | shareToWeixin(){ 83 | this.$emit('shareToWeixin'); 84 | }, 85 | shareToQQ(){ 86 | this.$emit('shareToQQ'); 87 | }, 88 | shareToQQzone(){ 89 | this.$emit('shareToQQzone'); 90 | }, 91 | shareToWeibo(){ 92 | this.$emit('shareToWeibo'); 93 | }, 94 | shareToDouban(){ 95 | this.$emit('shareToDouban'); 96 | } 97 | 98 | } 99 | } 100 | </script> 101 | 102 | <style lang="less" scoped> 103 | .shareArea{ 104 | width: 320px; 105 | align-items: center; 106 | background: #fff; 107 | border-radius: 4px; 108 | .shareTitle{ 109 | border-bottom: 1px solid #e8e8e8; 110 | padding: 10px; 111 | box-sizing: border-box; 112 | width: 100%; 113 | font-size:14px; 114 | } 115 | .bottom{ 116 | align-items: center; 117 | width: 100%; 118 | height: 100%; 119 | box-sizing: border-box; 120 | padding: 20px 10px; 121 | .title{ 122 | padding: 10px; 123 | box-sizing: border-box; 124 | width: 100%; 125 | font-size:14px; 126 | } 127 | .shareUl{ 128 | justify-content: space-between; 129 | align-items: center; 130 | width: 100%; 131 | padding: 0 10px; 132 | box-sizing: border-box; 133 | li{ 134 | display: flex; 135 | flex-direction: column; 136 | align-items: center; 137 | position: relative; 138 | cursor: pointer; 139 | .item{ 140 | background: #EFF2F7; 141 | width: 40px; 142 | height: 40px; 143 | border-radius: 50%; 144 | display: flex; 145 | justify-content: center; 146 | align-items: center; 147 | .svg-icon{ 148 | font-size: 24px; 149 | } 150 | .active{ 151 | color:#FF6C60; 152 | } 153 | } 154 | .moreBtn{ 155 | position: relative; 156 | font-size:14px; 157 | width:auto; 158 | height:40px; 159 | text-align: center; 160 | padding: 4px 18px; 161 | border-radius:50px; 162 | border: 1px solid #dcdcdc; 163 | } 164 | .qrcodeArea{ 165 | position: absolute; 166 | top: 50px; 167 | left: -30px; 168 | text-align: center; 169 | border: 1px solid #a9d86e; 170 | border-radius: 4px; 171 | padding: 10px; 172 | background: #fff; 173 | .saoTitle{ 174 | font-size: 14px; 175 | color:#a9d86e; 176 | margin-bottom: 5px; 177 | } 178 | } 179 | } 180 | } 181 | .shareIntro{ 182 | padding: 10px; 183 | box-sizing: border-box; 184 | width: 100%; 185 | font-size:12px; 186 | } 187 | } 188 | 189 | } 190 | 191 | </style> 192 | -------------------------------------------------------------------------------- /src/page/share/components/juejinShare.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="shareArea cflex"> 3 | <p class="shareTitle">分享组件五:仿掘金网站分享</p> 4 | <div class="bottom"> 5 | <ul class="shareUl cflex wflex"> 6 | <li> 7 | <span class="jueTitle">分享</span> 8 | </li> 9 | <li> 10 | <div class="item" @mouseover="showqrcode()" @mouseout="hideqrcode()"> 11 | <icon-svg icon-class="iconwechat" /> 12 | </div> 13 | <div class="qrcodeArea" v-show="qrcode.show"> 14 | <p class="saoTitle">扫一扫</p> 15 | <div class="qrcode" ref="qrCodeUrl3" ></div> 16 | </div> 17 | </li> 18 | <li> 19 | <div class="item" @click="shareToWeibo()"> 20 | <icon-svg icon-class="iconweibo" /> 21 | </div> 22 | </li> 23 | <li> 24 | <div class="item" @click="shareToQQ()"> 25 | <icon-svg icon-class="iconqq" /> 26 | </div> 27 | </li> 28 | 29 | </ul> 30 | </div> 31 | </div> 32 | </template> 33 | 34 | <script> 35 | import QRCode from 'qrcodejs2' 36 | import { shareUrl } from "@/utils/env"; 37 | 38 | export default { 39 | name:'juejinShare', 40 | data(){ 41 | return { 42 | qrcode:{ 43 | show:false 44 | }, 45 | qrcodeObj:{ 46 | text:shareUrl, // 要分享的网页路径 47 | width:80, 48 | height:80, 49 | colorDark: '#000000', 50 | colorLight: '#ffffff', 51 | correctLevel: QRCode.CorrectLevel.H 52 | } 53 | 54 | } 55 | }, 56 | mounted(){ 57 | this.creatQrCode(); 58 | }, 59 | methods: { 60 | showqrcode(){ 61 | this.qrcode.show = true; 62 | }, 63 | hideqrcode(){ 64 | this.qrcode.show = false; 65 | }, 66 | creatQrCode() { 67 | const qrcode = new QRCode(this.$refs.qrCodeUrl3, this.qrcodeObj) 68 | }, 69 | shareToQQ(){ 70 | this.$emit('shareToQQ'); 71 | }, 72 | shareToQQzone(){ 73 | this.$emit('shareToQQzone'); 74 | }, 75 | shareToWeibo(){ 76 | this.$emit('shareToWeibo'); 77 | }, 78 | shareToDouban(){ 79 | this.$emit('shareToDouban'); 80 | } 81 | 82 | } 83 | } 84 | </script> 85 | 86 | <style lang="less" scoped> 87 | .shareArea{ 88 | width: 300px; 89 | align-items: center; 90 | background: #fff; 91 | border-radius: 4px; 92 | .shareTitle{ 93 | border-bottom: 1px solid #e8e8e8; 94 | padding: 10px; 95 | box-sizing: border-box; 96 | width: 100%; 97 | font-size:14px; 98 | } 99 | .bottom{ 100 | align-items: center; 101 | padding: 0 20px; 102 | width: 100%; 103 | height: 100%; 104 | box-sizing: border-box; 105 | .shareUl{ 106 | justify-content: space-between; 107 | li{ 108 | display: flex; 109 | flex-direction: column; 110 | align-items: center; 111 | position: relative; 112 | cursor: pointer; 113 | margin-bottom: 10px; 114 | .jueTitle{ 115 | color: #c6c6c6; 116 | font-size: 14px; 117 | } 118 | .title{ 119 | margin-bottom: 10px; 120 | font-size: 13px; 121 | color:#a9d86e; 122 | } 123 | .item{ 124 | background: #EFF2F7; 125 | width: 40px; 126 | height: 40px; 127 | border-radius: 50%; 128 | display: flex; 129 | justify-content: center; 130 | align-items: center; 131 | .svg-icon{ 132 | font-size: 24px; 133 | } 134 | .active{ 135 | color:#FF6C60; 136 | } 137 | } 138 | .qrcodeArea{ 139 | position: absolute; 140 | z-index: 2; 141 | top: 50px; 142 | text-align: center; 143 | border: 1px solid #a9d86e; 144 | background: #fff; 145 | border-radius: 4px; 146 | padding: 10px; 147 | .saoTitle{ 148 | font-size: 14px; 149 | color:#a9d86e; 150 | margin-bottom: 5px; 151 | } 152 | } 153 | } 154 | } 155 | } 156 | 157 | } 158 | 159 | </style> 160 | -------------------------------------------------------------------------------- /src/page/share/components/sinaShare.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="shareArea cflex"> 3 | <p class="shareTitle">分享组件六:仿新浪分享</p> 4 | <div class="bottom rflex"> 5 | <ul class="shareUl rflex wflex"> 6 | <li> 7 | <div class="item" @mouseover="showqrcode()" @mouseout="hideqrcode()"> 8 | <icon-svg icon-class="iconwechat" /> 9 | </div> 10 | <div class="qrcodeArea" v-show="qrcode.show"> 11 | <p class="saoTitle">扫一扫</p> 12 | <div class="qrcode" ref="qrCodeUrl4"></div> 13 | </div> 14 | </li> 15 | <li> 16 | <div class="item" @click="shareToWeibo()"> 17 | <icon-svg icon-class="iconweibo" /> 18 | </div> 19 | </li> 20 | <li> 21 | <div class="item shareTu" v-popover:moreShareList> 22 | <icon-svg icon-class="iconshare" /> 23 | <el-popover 24 | ref="moreShareList" 25 | popper-class="moreShareList" 26 | placement="bottom" 27 | trigger="hover"> 28 | <div class="shareOther"> 29 | <ul class="cflex wflex"> 30 | <a href="#"> 31 | <li> 32 | <div class="item" @click="shareToQQ()"> 33 | <icon-svg icon-class="iconqq" /> 34 | <span>腾讯QQ</span> 35 | </div> 36 | </li> 37 | </a> 38 | <a href="#"> 39 | <li> 40 | <div class="item" @click="shareToQQzone()"> 41 | <icon-svg icon-class="iconqq_zone" /> 42 | <span>QQ空间</span> 43 | </div> 44 | </li> 45 | </a> 46 | </ul> 47 | </div> 48 | </el-popover> 49 | </div> 50 | </li> 51 | </ul> 52 | </div> 53 | </div> 54 | </template> 55 | 56 | <script> 57 | import QRCode from 'qrcodejs2' 58 | import { shareUrl } from "@/utils/env"; 59 | 60 | export default { 61 | name:'sinaShare', 62 | data(){ 63 | return { 64 | qrcode:{ 65 | show:false 66 | }, 67 | qrcodeObj:{ 68 | text:shareUrl, // 要分享的网页路径 69 | width:80, 70 | height:80, 71 | colorDark: '#000000', 72 | colorLight: '#ffffff', 73 | correctLevel: QRCode.CorrectLevel.H 74 | } 75 | 76 | } 77 | }, 78 | mounted(){ 79 | this.creatQrCode(); 80 | }, 81 | methods: { 82 | showqrcode(){ 83 | this.qrcode.show = true; 84 | }, 85 | hideqrcode(){ 86 | this.qrcode.show = false; 87 | }, 88 | creatQrCode() { 89 | const qrcode = new QRCode(this.$refs.qrCodeUrl4, this.qrcodeObj) 90 | }, 91 | shareToQQ(){ 92 | this.$emit('shareToQQ'); 93 | }, 94 | shareToQQzone(){ 95 | this.$emit('shareToQQzone'); 96 | }, 97 | shareToWeibo(){ 98 | this.$emit('shareToWeibo'); 99 | }, 100 | shareToDouban(){ 101 | this.$emit('shareToDouban'); 102 | } 103 | 104 | } 105 | } 106 | </script> 107 | 108 | <style lang="less" scoped> 109 | .shareArea{ 110 | width: 320px; 111 | align-items: center; 112 | background: #fff; 113 | border-radius: 4px; 114 | .shareTitle{ 115 | border-bottom: 1px solid #e8e8e8; 116 | padding: 10px; 117 | box-sizing: border-box; 118 | width: 100%; 119 | font-size:14px; 120 | } 121 | .bottom{ 122 | align-items: center; 123 | padding: 20px; 124 | width: 100%; 125 | height: 100%; 126 | box-sizing: border-box; 127 | .shareUl{ 128 | justify-content: center; 129 | li{ 130 | display: flex; 131 | flex-direction: column; 132 | align-items: center; 133 | position: relative; 134 | cursor: pointer; 135 | margin-right: 10px; 136 | .title{ 137 | margin-bottom: 10px; 138 | font-size: 13px; 139 | color:#a9d86e; 140 | } 141 | .item{ 142 | width: 40px; 143 | height: 40px; 144 | display: flex; 145 | justify-content: center; 146 | align-items: center; 147 | .svg-icon{ 148 | font-size: 24px; 149 | } 150 | .active{ 151 | color:#FF6C60; 152 | } 153 | } 154 | .qrcodeArea{ 155 | position: absolute; 156 | top: 50px; 157 | left: -30px; 158 | text-align: center; 159 | border: 1px solid #a9d86e; 160 | border-radius: 4px; 161 | padding: 10px; 162 | z-index: 99; 163 | background: #fff; 164 | .saoTitle{ 165 | font-size: 14px; 166 | color:#a9d86e; 167 | margin-bottom: 5px; 168 | } 169 | } 170 | } 171 | } 172 | } 173 | 174 | } 175 | 176 | </style> 177 | -------------------------------------------------------------------------------- /src/page/share/components/wxCodeModal.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <el-dialog 3 | :append-to-body="true" 4 | :width="wxModal.width" 5 | :height="wxModal.height" 6 | :visible.sync="wxModal.show" 7 | :before-close="handleClose" 8 | > 9 | <div class="wxContent"> 10 | <p class="qrtitle">打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮</p> 11 | <div class="qrcode" ref="qrCodeUrl5"></div> 12 | </div> 13 | </el-dialog> 14 | </template> 15 | 16 | <script> 17 | import QRCode from 'qrcodejs2' 18 | import { shareUrl } from "@/utils/env"; 19 | 20 | export default { 21 | name:'wxCodeModal', 22 | data(){ 23 | return { 24 | qrcodeObj:{ 25 | text:shareUrl, // 要分享的网页路径 26 | width:190, 27 | height:190, 28 | colorDark: '#000000', 29 | colorLight: '#ffffff', 30 | correctLevel: QRCode.CorrectLevel.H 31 | } 32 | 33 | } 34 | }, 35 | props:{ 36 | wxModal:Object 37 | }, 38 | mounted(){ 39 | 40 | }, 41 | methods: { 42 | creatQrCode() { 43 | const qrcode = new QRCode(this.$refs.qrCodeUrl5, this.qrcodeObj) 44 | }, 45 | handleClose(){ 46 | this.$emit('hideWxCodeModal') 47 | } 48 | 49 | }, 50 | watch:{ 51 | 'wxModal.show': { 52 | handler(newName, oldName) { 53 | console.log(newName) 54 | newName?this.creatQrCode():''; 55 | }, 56 | deep: true, 57 | immediate: true 58 | } 59 | } 60 | } 61 | </script> 62 | 63 | <style lang="less" scoped> 64 | .wxContent{ 65 | text-align: center; 66 | padding: 20px; 67 | .qrtitle{ 68 | margin-bottom: 30px; 69 | } 70 | .qrcode{ 71 | display: flex; 72 | align-items: center; 73 | justify-content: center; 74 | flex-direction: column; 75 | } 76 | } 77 | 78 | </style> 79 | -------------------------------------------------------------------------------- /src/page/share/components/yanShare.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="shareArea cflex sharelast"> 3 | <p class="shareTitle">分享组件八:横向排列</p> 4 | <div class="bottom rflex"> 5 | <div class="yanItem" v-popover:yanShare> 6 | <icon-svg icon-class="iconshare1" /> 7 | <el-popover 8 | ref="yanShare" 9 | popper-class="yanshare" 10 | placement="bottom" 11 | trigger="hover"> 12 | <ul class="shareUl rflex wflex"> 13 | <li> 14 | <div class="item" v-popover:yanSharewx> 15 | <icon-svg icon-class="iconwechat" /> 16 | <el-popover 17 | ref="yanSharewx" 18 | popper-class="yanSharewx" 19 | placement="bottom" 20 | trigger="hover"> 21 | <div class="qrcodeArea"> 22 | <p class="saoTitle">扫一扫</p> 23 | <div class="qrcode" ref="qrCodeUrl6"></div> 24 | </div> 25 | </el-popover> 26 | </div> 27 | </li> 28 | <li> 29 | <div class="item" @click="shareToWeibo()"> 30 | <icon-svg icon-class="iconweibo" /> 31 | </div> 32 | </li> 33 | <li> 34 | <div class="item" @click="shareToQQ()"> 35 | <icon-svg icon-class="iconqq" /> 36 | </div> 37 | </li> 38 | <li> 39 | <div class="item" @click="shareToQQzone()"> 40 | <icon-svg icon-class="iconqq_zone" /> 41 | </div> 42 | </li> 43 | 44 | <li> 45 | <div class="item" @click="shareToDouban()"> 46 | <icon-svg icon-class="icondouban" /> 47 | </div> 48 | </li> 49 | </ul> 50 | </el-popover> 51 | </div> 52 | </div> 53 | </div> 54 | </template> 55 | 56 | <script> 57 | import QRCode from 'qrcodejs2' 58 | import { shareUrl } from "@/utils/env"; 59 | 60 | export default { 61 | name:'hengShare', 62 | data(){ 63 | return { 64 | qrcode:{ 65 | show:false 66 | }, 67 | qrcodeObj:{ 68 | text:shareUrl, // 要分享的网页路径 69 | width:80, 70 | height:80, 71 | colorDark: '#000000', 72 | colorLight: '#ffffff', 73 | correctLevel: QRCode.CorrectLevel.H 74 | } 75 | 76 | } 77 | }, 78 | mounted(){ 79 | this.creatQrCode(); 80 | }, 81 | methods: { 82 | showqrcode(){ 83 | this.qrcode.show = true; 84 | }, 85 | hideqrcode(){ 86 | this.qrcode.show = false; 87 | }, 88 | creatQrCode() { 89 | const qrcode = new QRCode(this.$refs.qrCodeUrl6, this.qrcodeObj) 90 | }, 91 | shareToQQ(){ 92 | this.$emit('shareToQQ'); 93 | }, 94 | shareToQQzone(){ 95 | this.$emit('shareToQQzone'); 96 | }, 97 | shareToWeibo(){ 98 | this.$emit('shareToWeibo'); 99 | }, 100 | shareToDouban(){ 101 | this.$emit('shareToDouban'); 102 | } 103 | 104 | } 105 | } 106 | </script> 107 | 108 | <style lang="less" scoped> 109 | .shareArea{ 110 | width: 340px; 111 | align-items: center; 112 | background: #fff; 113 | border-radius: 4px; 114 | .shareTitle{ 115 | border-bottom: 1px solid #e8e8e8; 116 | padding: 10px; 117 | box-sizing: border-box; 118 | width: 100%; 119 | font-size:14px; 120 | } 121 | .bottom{ 122 | align-items: center; 123 | padding: 20px; 124 | width: 100%; 125 | height: 100%; 126 | box-sizing: border-box; 127 | justify-content: center; 128 | .yanItem{ 129 | justify-content: center; 130 | align-items: center; 131 | .svg-icon{ 132 | font-size: 34px; 133 | } 134 | } 135 | .toTitle{ 136 | font-size: 13px; 137 | } 138 | .shareUl{ 139 | justify-content: space-between; 140 | li{ 141 | display: flex; 142 | flex-direction: column; 143 | align-items: center; 144 | position: relative; 145 | cursor: pointer; 146 | .title{ 147 | margin-bottom: 10px; 148 | font-size: 13px; 149 | color:#a9d86e; 150 | } 151 | .item{ 152 | background: #EFF2F7; 153 | width: 40px; 154 | height: 40px; 155 | border-radius: 50%; 156 | display: flex; 157 | justify-content: center; 158 | align-items: center; 159 | .svg-icon{ 160 | font-size: 24px; 161 | } 162 | .active{ 163 | color:#FF6C60; 164 | } 165 | } 166 | 167 | } 168 | } 169 | } 170 | 171 | } 172 | 173 | </style> 174 | -------------------------------------------------------------------------------- /src/page/share/index.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="shareContainer" ref="shareContainer"> 3 | <el-row :gutter="20"> 4 | <el-col :span="6"> 5 | <heng-share @shareToQQ="shareToQQ" @shareToQQzone="shareToQQzone" @shareToWeibo="shareToWeibo" @shareToDouban="shareToDouban"></heng-share> 6 | </el-col> 7 | <el-col :span="6"> 8 | <invite-share @shareToQQ="shareToQQ" @shareToQQzone="shareToQQzone" @shareToWeibo="shareToWeibo" @shareToDouban="shareToDouban"></invite-share> 9 | </el-col> 10 | <el-col :span="6"> 11 | <jianshu-share @shareToWeixin="shareToWeixin" @shareToQQ="shareToQQ" @shareToQQzone="shareToQQzone" @shareToWeibo="shareToWeibo" @shareToDouban="shareToDouban"></jianshu-share> 12 | </el-col> 13 | <el-col :span="6"> 14 | <jianshu-left-share @shareToWeixin="shareToWeixin" @shareToQQ="shareToQQ" @shareToQQzone="shareToQQzone" @shareToWeibo="shareToWeibo" @shareToDouban="shareToDouban"></jianshu-left-share> 15 | </el-col> 16 | </el-row> 17 | <el-row :gutter="20"> 18 | <el-col :span="6"> 19 | <info-share @shareToQQ="shareToQQ" @shareToQQzone="shareToQQzone" @shareToWeibo="shareToWeibo" @shareToDouban="shareToDouban"></info-share> 20 | </el-col> 21 | <el-col :span="6"> 22 | <juejin-share @shareToQQ="shareToQQ" @shareToQQzone="shareToQQzone" @shareToWeibo="shareToWeibo" @shareToDouban="shareToDouban"></juejin-share> 23 | </el-col> 24 | <el-col :span="6"> 25 | <sina-share @shareToQQ="shareToQQ" @shareToQQzone="shareToQQzone" @shareToWeibo="shareToWeibo" @shareToDouban="shareToDouban"></sina-share> 26 | </el-col> 27 | <el-col :span="6"> 28 | <yan-share @shareToQQ="shareToQQ" @shareToQQzone="shareToQQzone" @shareToWeibo="shareToWeibo" @shareToDouban="shareToDouban"></yan-share> 29 | </el-col> 30 | </el-row> 31 | <wx-code-modal v-if="wxModal.show" :wxModal="wxModal" @hideWxCodeModal="hideWxCodeModal"></wx-code-modal> 32 | </div> 33 | </template> 34 | 35 | <script> 36 | import { 37 | HengShare, 38 | InviteShare, 39 | JianshuShare, 40 | JianshuLeftShare, 41 | WxCodeModal, 42 | JuejinShare, 43 | InfoShare, 44 | SinaShare, 45 | YanShare 46 | } from "./components"; 47 | import * as mutils from '@/utils/mUtils' 48 | 49 | export default { 50 | data(){ 51 | return { 52 | wxModal:{ 53 | show:false, 54 | width:"358px", 55 | height:"358px", 56 | } 57 | } 58 | }, 59 | components:{ 60 | HengShare, 61 | InviteShare, 62 | JianshuShare, 63 | JianshuLeftShare, 64 | WxCodeModal, 65 | JuejinShare, 66 | InfoShare, 67 | SinaShare, 68 | YanShare 69 | }, 70 | mounted(){ 71 | mutils.setContentHeight(this,this.$refs.shareContainer,210); 72 | }, 73 | methods: { 74 | hideWxCodeModal(){ 75 | this.wxModal.show = false; 76 | }, 77 | // 分享到微信,显示微信二维码弹框; 78 | shareToWeixin(){ 79 | this.wxModal.show = true; 80 | }, 81 | shareToQQ(){ 82 | this.shareConfig('qq') 83 | }, 84 | shareToQQzone(){ 85 | this.shareConfig('qqZone') 86 | }, 87 | shareToWeibo(){ 88 | this.shareConfig('weibo') 89 | }, 90 | shareToDouban(){ 91 | this.shareConfig('douban') 92 | } 93 | } 94 | } 95 | </script> 96 | 97 | <style lang="less" scoped> 98 | .shareContainer{ 99 | display: flex; 100 | flex-direction: column; 101 | justify-content: space-around; 102 | padding: 20px; 103 | .el-row:first-child{ 104 | margin-bottom: 20px; 105 | } 106 | .el-row{ 107 | height:210px; 108 | .el-col{ 109 | height:100%; 110 | .shareArea{ 111 | height: 100%; 112 | width:100%; 113 | } 114 | } 115 | } 116 | } 117 | </style> 118 | -------------------------------------------------------------------------------- /src/page/userList/userList.vue: -------------------------------------------------------------------------------- 1 | <template> 2 | <div class="fillcontain"> 3 | <div class="contain"> 4 | <div class="table_container"> 5 | <el-table 6 | v-loading="loading" 7 | :data="tableData" 8 | border 9 | stripe 10 | highlight-current-row 11 | header-cell-class-name="table-header-class" 12 | style="width:100%"> 13 | <el-table-column 14 | label="序号" 15 | width="60" 16 | align='center'> 17 | <template slot-scope="scope"> 18 | <span>{{scope.$index+(paginations.pageIndex - 1) * paginations.pageSize + 1}} </span> 19 | </template> 20 | </el-table-column> 21 | <el-table-column 22 | property="username" 23 | label="用户姓名" 24 | width="80" 25 | align='center'> 26 | </el-table-column> 27 | <el-table-column 28 | property="email" 29 | label="邮箱地址" 30 | width="180" 31 | align='center'> 32 | </el-table-column> 33 | <el-table-column 34 | property="address" 35 | label="注册地址" 36 | align='center'> 37 | </el-table-column> 38 | <el-table-column 39 | property="region" 40 | label="地区" 41 | width="80" 42 | align='center'> 43 | </el-table-column> 44 | <el-table-column 45 | property="isp" 46 | label="网络" 47 | width="80" 48 | align='center'> 49 | </el-table-column> 50 | <el-table-column 51 | property="ip" 52 | label="IP地址" 53 | width="180" 54 | align='center'> 55 | </el-table-column> 56 | <el-table-column 57 | property="createTime" 58 | label="注册时间" 59 | width="180" 60 | align='center'> 61 | </el-table-column> 62 | <el-table-column 63 | property="updateTime" 64 | label="登录时间" 65 | width="180" 66 | align='center'> 67 | </el-table-column> 68 | </el-table> 69 | <el-row> 70 | <el-col :span="24"> 71 | <div class="pagination"> 72 | <el-pagination 73 | v-if='paginations.total > 0' 74 | :page-sizes="paginations.pageSizes" 75 | :page-size="paginations.pageSize" 76 | :layout="paginations.layout" 77 | :total="paginations.total" 78 | :current-page='paginations.pageIndex' 79 | @current-change='handleCurrentChange' 80 | @size-change='handleSizeChange'> 81 | </el-pagination> 82 | </div> 83 | </el-col> 84 | </el-row> 85 | </div> 86 | </div> 87 | </div> 88 | </template> 89 | 90 | <script> 91 | import { getUserList } from "@/api/user"; 92 | export default { 93 | data(){ 94 | return { 95 | tableData: [], 96 | loading:true, 97 | //需要给分页组件传的信息 98 | paginations: { 99 | total: 0, // 总数 100 | pageIndex: 1, // 当前位于哪页 101 | pageSize: 20, // 1页显示多少条 102 | pageSizes: [5, 10, 15, 20], //每页显示多少条 103 | layout: "total, sizes, prev, pager, next, jumper" // 翻页属性 104 | }, 105 | } 106 | }, 107 | created(){ 108 | }, 109 | mounted(){ 110 | this.getUserList(); 111 | }, 112 | methods: { 113 | getUserList(){ 114 | let para = { 115 | limit:this.paginations.pageSize, 116 | page:this.paginations.pageIndex 117 | } 118 | getUserList(para).then(res => { 119 | this.loading = false; 120 | this.paginations.total = res.data.total; 121 | this.tableData = res.data.userList; 122 | }) 123 | }, 124 | // 每页多少条切换 125 | handleSizeChange(pageSize) { 126 | this.paginations.pageSize = pageSize; 127 | this.getUserList(); 128 | }, 129 | // 上下分页 130 | handleCurrentChange(page) { 131 | this.paginations.pageIndex = page; 132 | this.getUserList(); 133 | } 134 | }, 135 | } 136 | </script> 137 | 138 | <style lang="less" scoped> 139 | .fillcontain{ 140 | padding-bottom: 0; 141 | } 142 | .contain{ 143 | background: #fff; 144 | padding: 10px; 145 | margin-bottom: 20px; 146 | } 147 | .pagination{ 148 | padding: 10px 20px; 149 | text-align: right; 150 | } 151 | </style> 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/permission.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import NProgress from 'nprogress' // Progress 进度条 4 | process.env.NODE_ENV === "development" && import('nprogress/nprogress.css') 5 | import { Message } from 'element-ui' 6 | import { getToken } from '@/utils/auth' // 验权(从cookie中获取) 7 | import { getUserInfo } from "@/api/user"; 8 | import { 9 | setTitle 10 | } from '@/utils/mUtils' // 设置浏览器头部标题 11 | 12 | function hasPermission(roles, permissionRoles) { 13 | if (roles.indexOf('admin') >= 0) return true 14 | if (!permissionRoles) return true 15 | return roles.some(role => permissionRoles.indexOf(role) >= 0) 16 | } 17 | const whiteList = ['/login'] // 不重定向白名单 18 | 19 | router.beforeEach((to, from, next) => { 20 | NProgress.start() 21 | // 设置浏览器头部标题 22 | const browserHeaderTitle = to.meta.title 23 | store.commit('SET_BROWSERHEADERTITLE', { 24 | browserHeaderTitle: browserHeaderTitle 25 | }) 26 | // 点击登录时,拿到了token并存入了cookie,保证页面刷新时,始终可以拿到token 27 | if (getToken('Token')) { 28 | if(to.path === '/login') { 29 | next({ path: '/' }) 30 | NProgress.done() 31 | } else { 32 | // 用户登录成功之后,每次点击路由都进行了角色的判断; 33 | if (store.getters.roles.length === 0) { 34 | let token = getToken('Token'); 35 | getUserInfo({"token":token}).then().then(res => { // 根据token拉取用户信息 36 | let userList = res.data.userList; 37 | store.commit("SET_ROLES",userList.roles); 38 | store.commit("SET_NAME",userList.name); 39 | store.commit("SET_AVATAR",userList.avatar); 40 | store.dispatch('GenerateRoutes', { "roles":userList.roles }).then(() => { // 根据roles权限生成可访问的路由表 41 | router.addRoutes(store.getters.addRouters) // 动态添加可访问权限路由表 42 | next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 43 | }) 44 | }).catch((err) => { 45 | store.dispatch('LogOut').then(() => { 46 | Message.error(err || 'Verification failed, please login again') 47 | next({ path: '/' }) 48 | }) 49 | }) 50 | } else { 51 | // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓ 52 | if (hasPermission(store.getters.roles, to.meta.roles)) { 53 | next()// 54 | } else { 55 | next({ path: '/401', replace: true, query: { noGoBack: true }}) 56 | } 57 | } 58 | } 59 | } else { 60 | if (whiteList.indexOf(to.path) !== -1) { 61 | // 点击退出时,会定位到这里 62 | next() 63 | } else { 64 | next('/login') 65 | NProgress.done() 66 | } 67 | } 68 | }) 69 | 70 | router.afterEach(() => { 71 | NProgress.done() // 结束Progress 72 | setTimeout(() => { 73 | const browserHeaderTitle = store.getters.browserHeaderTitle 74 | setTitle(browserHeaderTitle) 75 | }, 0) 76 | }) 77 | 78 | 79 | 80 | /** 81 | 本系统权限逻辑分析: 82 | 1、路由对象区分权限路由对象和非权限路由对象;初始化时,将非权限路由对象赋值给Router;同时设置权限路由中的meta对象,如:meta:{roles:['admin','editor']} 83 | 表示该roles所拥有的路由权限; 84 | 2、通过用户登录成功之后返回的roles值,进行路由的匹配并生成新的路由对象; 85 | 3、用户成功登录并跳转到首页时,根据刚刚生成的路由对象,渲染左侧的菜单;即,不同的用户看到的菜单是不一样的; 86 | 87 | 用户点击登录和退出之后的业务逻辑分析: 88 | 1、用户点击登录按钮,通过路由导航钩子router.beforeEach()函数确定下一步的跳转逻辑,如下: 89 | 1.1、用户已经登录成功过,并从cookie中拿到了token值; 90 | 1.1.1、用户访问登录页面,直接定位到登录页面; 91 | 1.1.1、用户访问非登录页面,需要根据用户是否有roles信息,进行不同的业务逻辑,如下: 92 | (1)、初始情况下,用户roles信息为空; 93 | 1.通过getUserInfo()函数,根据token拉取用户信息;并通过store将该用户roles,name,avatar信息存储于vuex; 94 | 2.通过store.dispatch('GenerateRoutes', { roles })去重新过滤和生成路由,通过router.addRoutes()合并路由表; 95 | 3.如果在获取用户信息接口时出现错误,则调取store.dispatch('LogOut')接口,返回到login页面; 96 | 97 | (2)、用户已经拥有roles信息; 98 | 1.点击页面路由,通过roles权限判断 hasPermission()。如果用户有该路由权限,直接跳转对应的页面;如果没有权限,则跳转至401提示页面; 99 | 100 | 2.用户点击退出,token已被清空 101 | 1.如果设置了白名单用户,则直接跳转到相应的页面; 102 | 2.反之,则跳转至登录页面; 103 | */ -------------------------------------------------------------------------------- /src/router/topRouter.js: -------------------------------------------------------------------------------- 1 | 2 | export const topRouterMap = [ 3 | { 4 | 'parentName':'infoShow', 5 | 'data':[ 6 | { 7 | path: 'infoShow1', 8 | name: 'infoShow1', 9 | meta: { 10 | title: '个人信息子菜单1', 11 | icon: 'fa-asterisk', 12 | routerType: 'topmenu' 13 | }, 14 | component: () => import('@/page/infoManage/infoShow') 15 | }, 16 | { 17 | path: 'infoShow2', 18 | name: 'infoShow2', 19 | meta: { 20 | title: '个人信息子菜单2', 21 | icon: 'fa-asterisk', 22 | routerType: 'topmenu' 23 | }, 24 | component: () => import('@/page/fundList/moneyData') 25 | }, 26 | { 27 | path: 'infoShow3', 28 | name: 'infoShow3', 29 | meta: { 30 | title: '个人信息子菜单3', 31 | icon: 'fa-asterisk', 32 | routerType: 'topmenu' 33 | }, 34 | component: () => import('@/page/fundList/moneyData') 35 | }, 36 | { 37 | path: 'infoShow4', 38 | name: 'infoShow4', 39 | meta: { 40 | title: '个人信息子菜单4', 41 | icon: 'fa-asterisk', 42 | routerType: 'topmenu' 43 | }, 44 | component: () => import('@/page/fundList/moneyData') 45 | }, 46 | { 47 | path: 'infoShow5', 48 | name: 'infoShow5', 49 | meta: { 50 | title: '个人信息子菜单5', 51 | icon: 'fa-asterisk', 52 | routerType: 'topmenu' 53 | }, 54 | component: () => import('@/page/fundList/moneyData') 55 | } 56 | ] 57 | }, 58 | { 59 | 'parentName':'infoModify', 60 | 'data':[ 61 | { 62 | path:'infoModify1', 63 | name:'infoModify1', 64 | meta:{ 65 | title:'修改信息子菜单1', 66 | icon:'fa-asterisk', 67 | routerType:'topmenu' 68 | }, 69 | component: () => import('@/page/infoManage/infoModify') 70 | }, 71 | { 72 | path:'infoModify2', 73 | name:'infoModify2', 74 | meta:{ 75 | title:'修改信息子菜单2', 76 | icon:'fa-asterisk', 77 | routerType:'topmenu' 78 | }, 79 | component: () => import('@/page/fundList/moneyData') 80 | }, 81 | { 82 | path:'infoModify3', 83 | name:'infoModify3', 84 | meta:{ 85 | title:'修改信息子菜单3', 86 | icon:'fa-asterisk', 87 | routerType:'topmenu' 88 | }, 89 | component: () => import('@/page/fundList/moneyData') 90 | } 91 | ] 92 | } 93 | ] 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | if(process.env.NODE_ENV === "development"){ 4 | Vue.use(Vuex) 5 | } 6 | 7 | import user from './modules/user' 8 | import permission from './modules/permission' 9 | import money from './modules/money' 10 | import menu from './modules/menu' 11 | 12 | export default new Vuex.Store({ 13 | modules: { 14 | user, 15 | permission, 16 | money, 17 | menu 18 | } 19 | }); 20 | 21 | -------------------------------------------------------------------------------- /src/store/modules/menu.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const types = { 4 | HANDLE_LEFT_MENU:'HANDLE_LEFT_MENU', // 收缩左侧菜单 5 | INIT_LEFT_MENU:'INIT_LEFT_MENU', // 初始化左侧菜单 6 | SET_LEFT_COLLAPSE:"SET_LEFT_COLLAPSE", // 改变左边菜单的收缩宽度 7 | SET_FOOTER_SHOW:"SET_FOOTER_SHOW", // 显示隐藏底部layout 8 | } 9 | const menu = { 10 | state :{ 11 | minLeftMenuWidth:35, 12 | maxLeftMenuWidth:180, 13 | sidebar: { 14 | opened: true, 15 | width: 180 16 | }, 17 | isCollapse:false, // 菜单默认展开 18 | isFooter:false 19 | }, 20 | getters : { 21 | sidebar:state => state.sidebar, 22 | isCollapse:state => state.isCollapse, 23 | isFooter:state => state.isFooter 24 | }, 25 | mutations:{ 26 | [types.HANDLE_LEFT_MENU] (state) { 27 | if(state.sidebar.opened){//true 28 | state.sidebar.width = state.minLeftMenuWidth; 29 | }else{ 30 | state.sidebar.width = state.maxLeftMenuWidth; 31 | } 32 | state.sidebar.opened = !state.sidebar.opened 33 | }, 34 | [types.INIT_LEFT_MENU] (state) { 35 | state.sidebar = state.sidebar 36 | }, 37 | [types.SET_LEFT_COLLAPSE] (state) { 38 | state.isCollapse = !state.isCollapse 39 | }, 40 | [types.SET_FOOTER_SHOW] (state) { 41 | state.isFooter = true 42 | } 43 | 44 | }, 45 | actions:{ 46 | handleLeftMenu:({ commit }) => { 47 | commit(types.HANDLE_LEFT_MENU) 48 | }, 49 | initLeftMenu:({ commit }) => { 50 | commit(types.INIT_LEFT_MENU) 51 | }, 52 | setLeftCollapse:({ commit}) => { 53 | commit(types.SET_LEFT_COLLAPSE) 54 | } 55 | } 56 | 57 | } 58 | 59 | 60 | export default menu; -------------------------------------------------------------------------------- /src/store/modules/money.js: -------------------------------------------------------------------------------- 1 | 2 | import { getMoneyIncomePay } from '@/api/money' // 导入资金信息相关接口 3 | 4 | const money = { 5 | state: { 6 | addFundDialog: { 7 | title:'新增资金信息', 8 | type:'add' 9 | }, 10 | search: { 11 | startTime:'', 12 | endTime:'', 13 | name:'' 14 | }, 15 | searchBtnDisabled: true 16 | }, 17 | getters:{ 18 | addFundDialog: state => state.addFundDialog, 19 | search: state => state.search, 20 | searchBtnDisabled: state => state.searchBtnDisabled, 21 | }, 22 | mutations: { 23 | SET_DIALOG_TITLE: (state, type) => { 24 | if(type === 'add'){ 25 | state.addFundDialog.title = '新增资金信息' 26 | state.addFundDialog.type = 'add' 27 | }else{ 28 | state.addFundDialog.title = '编辑资金信息' 29 | state.addFundDialog.type = 'edit' 30 | } 31 | }, 32 | SET_SEARCH : (state, payload) => { 33 | state.search = payload; 34 | }, 35 | SET_SEARCHBTN_DISABLED : (state, payload) => { 36 | state.searchBtnDisabled = payload; 37 | } 38 | }, 39 | actions: { 40 | // 获取资金列表 41 | GetMoneyIncomePay({commit},reqData) { 42 | return new Promise(resolve => { 43 | getMoneyIncomePay(reqData).then(response => { 44 | const data = response.data 45 | resolve(data) 46 | }) 47 | }) 48 | } 49 | 50 | 51 | } 52 | } 53 | 54 | export default money 55 | -------------------------------------------------------------------------------- /src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import { asyncRouterMap, constantRouterMap } from '@/router' 2 | import { topRouterMap } from "@/router/topRouter"; 3 | import * as mutils from '@/utils/mUtils' 4 | 5 | 6 | 7 | // 循环追加顶栏菜单 8 | function addTopRouter(){ 9 | asyncRouterMap.forEach( (item) => { 10 | if(item.children && item.children.length >= 1){ 11 | item.children.forEach((sitem) => { 12 | topRouterMap.forEach((citem) => { 13 | if(sitem.name === citem.parentName){ 14 | let newChildren = item.children.concat(citem.topmenulist); // arr 15 | item.children = newChildren; 16 | } 17 | }) 18 | }) 19 | } 20 | }) 21 | return asyncRouterMap; 22 | } 23 | 24 | // 获取到当前路由对应顶部子菜单 25 | function filterTopRouters(data){ 26 | let topRouters = topRouterMap.find((item)=>{ 27 | return item.parentName === data.name 28 | }) 29 | if(!mutils.isEmpty(topRouters)){ 30 | return topRouters.topmenulist; 31 | } 32 | } 33 | 34 | /** 35 | * 通过meta.role判断是否与当前用户权限匹配 36 | * @param roles 37 | * @param route 38 | */ 39 | function hasPermission(roles, route) { 40 | // roles为权限身份数组 41 | if (route.meta && route.meta.roles) { 42 | return roles.some(role => route.meta.roles.indexOf(role) >= 0) 43 | } else { 44 | return true 45 | } 46 | } 47 | 48 | /** 49 | * 递归过滤异步路由表,返回符合用户角色权限的路由表 50 | * @param asyncRouterMap 51 | * @param roles 52 | */ 53 | function filterAsyncRouter(asyncRouterMap, roles) { 54 | // 返回满足条件的子路由对象 55 | const accessedRouters = asyncRouterMap.filter(route => { 56 | if (hasPermission(roles, route)) { 57 | if (route.children && route.children.length) { 58 | // route.children重新过滤赋值; 59 | route.children = filterAsyncRouter(route.children, roles) 60 | } 61 | return true // 返回该权限路由对象; 62 | } 63 | return false 64 | }) 65 | return accessedRouters 66 | } 67 | 68 | 69 | const permission = { 70 | state: { 71 | routers: constantRouterMap, 72 | addRouters: [], 73 | topRouters:[], 74 | topTitle:'', 75 | menuIndex:0 76 | }, 77 | getters:{ 78 | permission_routers: state => state.routers, // 所有路由 79 | addRouters: state => state.addRouters, // 权限过滤路由 80 | topRouters: state => state.topRouters, // 顶部三级路由 81 | topTitle:state => state.topTitle, // 顶部的title 82 | menuIndex:state => state.menuIndex, // 顶部菜单的index 83 | }, 84 | mutations: { 85 | SET_ROUTERS: (state, routers) => { 86 | state.addRouters = routers // 权限路由 87 | state.routers = constantRouterMap.concat(routers) // 总路由 88 | }, 89 | CLICK_INNER_LEFT_MENU:(state,data) => { // titleList:arr 90 | state.topRouters = data.titleList; 91 | }, 92 | CLICK_TOP_MENU:(state,data) => { 93 | state.topTitle = data.title 94 | state.menuIndex = data.menuIndex 95 | 96 | }, 97 | }, 98 | actions: { 99 | // 根据角色,重新设置权限路由;并保存到vuex中,SET_ROUTERS; 100 | GenerateRoutes({ commit }, data) { 101 | return new Promise(resolve => { 102 | let roles = data.roles; 103 | let accessedRouters = ''; 104 | if (roles.indexOf('admin') >= 0) { 105 | // 如果是管理员,直接将权限路由赋值给新路由; 106 | accessedRouters = asyncRouterMap 107 | } else { 108 | // 非管理员用户,如roles:['editor','developer'],则需要过滤权限路由数据 109 | accessedRouters = filterAsyncRouter(asyncRouterMap, roles) 110 | } 111 | commit('SET_ROUTERS', accessedRouters) 112 | resolve() 113 | }) 114 | }, 115 | ClickLeftInnerMenu({ commit },data) { 116 | commit('CLICK_INNER_LEFT_MENU',data) 117 | }, 118 | ClickTopMenu({ commit },data) { 119 | commit('CLICK_TOP_MENU',data) 120 | } 121 | } 122 | } 123 | 124 | export default permission 125 | -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | 2 | import * as mUtils from '@/utils/mUtils' 3 | import { logout ,getUserInfo } from '@/api/user' // 导入用户信息相关接口 4 | import { getToken, setToken, removeToken } from '@/utils/auth' 5 | 6 | 7 | const user = { 8 | state : { 9 | name:'', 10 | avatar:'', 11 | token: getToken('Token'), 12 | roles: [], 13 | browserHeaderTitle: mUtils.getStore('browserHeaderTitle') || '小爱Admin' 14 | }, 15 | getters : { 16 | token: state => state.token, 17 | roles: state => state.roles, 18 | avatar: state => state.avatar, 19 | name: state => state.name, 20 | browserHeaderTitle: state => state.browserHeaderTitle, 21 | }, 22 | mutations: { 23 | SET_ROLES: (state, roles) => { 24 | state.roles = roles 25 | }, 26 | SET_BROWSERHEADERTITLE: (state, action) => { 27 | state.browserHeaderTitle = action.browserHeaderTitle 28 | }, 29 | SET_NAME: (state, name) => { 30 | state.name = name 31 | }, 32 | SET_AVATAR: (state, avatar) => { 33 | state.avatar = avatar 34 | } 35 | }, 36 | actions:{ 37 | //登出 38 | LogOut({ commit, reqData }) { 39 | return new Promise((resolve, reject) => { 40 | logout(reqData).then(response => { 41 | commit('SET_ROLES', []) 42 | removeToken('Token') 43 | resolve() 44 | }) 45 | }) 46 | }, 47 | // 动态修改权限;本实例中,role和token是相同的; 48 | ChangeRoles({ commit }, role) { 49 | return new Promise(resolve => { 50 | const token = role; 51 | setToken("Token",token) 52 | getUserInfo({"token":token}).then(res => { 53 | let data = res.data.userList; 54 | commit('SET_ROLES', data.roles) 55 | commit('SET_NAME', data.name) 56 | commit('SET_AVATAR', data.avatar) 57 | resolve() 58 | }) 59 | }) 60 | }, 61 | 62 | } 63 | } 64 | 65 | export default user; 66 | 67 | /** 68 | * 1、用户退出,需要调取后台接口吗?后台具体的业务逻辑是什么? 69 | * 70 | * 71 | */ -------------------------------------------------------------------------------- /src/style/common.less: -------------------------------------------------------------------------------- 1 | body, div, span, header, footer, nav, section, aside, article, ul, dl, dt, dd, li, a, p, h1, h2, h3, h4,h5, h6, i, b, textarea, button, input, select, figure, figcaption { 2 | padding: 0; 3 | margin: 0; 4 | list-style: none; 5 | font-style: normal; 6 | text-decoration: none; 7 | border: none; 8 | font-family: "Microsoft Yahei",sans-serif; 9 | -webkit-tap-highlight-color:transparent; 10 | -webkit-font-smoothing: antialiased; 11 | &:focus { 12 | outline: none; 13 | } 14 | } 15 | 16 | 17 | 18 | input[type="button"], input[type="submit"], input[type="search"], input[type="reset"] { 19 | -webkit-appearance: none; 20 | } 21 | 22 | textarea { -webkit-appearance: none;} 23 | 24 | html,body{ 25 | height: 100%; 26 | width: 100%; 27 | } 28 | 29 | .clear:after{ 30 | content: ''; 31 | display: block; 32 | clear: both; 33 | } 34 | 35 | .clear{ 36 | zoom:1; 37 | } 38 | 39 | .back_img{ 40 | background-repeat: no-repeat; 41 | background-size: 100% 100%; 42 | } 43 | 44 | .margin{ 45 | margin: 0 auto; 46 | } 47 | 48 | .left{ 49 | float: left; 50 | } 51 | 52 | .right{ 53 | float: right; 54 | } 55 | 56 | .hide{ 57 | display: none; 58 | } 59 | .show{ 60 | display: block; 61 | } 62 | .text_center{ 63 | text-align: center; 64 | } 65 | .text_left{ 66 | text-align: left; 67 | } 68 | .text_right{ 69 | text-align: right; 70 | } 71 | .ver_align{ 72 | vertical-align: middle; 73 | } 74 | .rflex{ 75 | display: flex; 76 | flex-direction: row; 77 | } 78 | .cflex{ 79 | display: flex; 80 | flex-direction: column; 81 | } 82 | .wflex{ 83 | flex:1; 84 | } 85 | 86 | 87 | // 公共的color 88 | @left-bgColor:#2a3542; // 左侧菜单背景颜色; 89 | 90 | 91 | 92 | //页面的公共样式 93 | .ellipsis{ 94 | overflow: hidden; 95 | text-overflow: ellipsis; 96 | white-space: nowrap; 97 | } 98 | .el-table .odd-row { 99 | background:#eaefea; 100 | } 101 | .el-table .even-row { 102 | background: #d9e6f1; 103 | } 104 | 105 | .fillcontain{ 106 | padding: 20px; 107 | box-sizing: border-box; 108 | } 109 | .fillcontainer{ 110 | width:100%; 111 | text-align:center; 112 | padding: 20px; 113 | box-sizing: border-box; 114 | background: #fff; 115 | } 116 | .tabContainer{ 117 | padding: 10px; 118 | box-sizing: border-box; 119 | background: #fff; 120 | border-radius: 2px; 121 | } 122 | .echartsPosition{ 123 | position: relative; 124 | width: 100%; 125 | height: 100%; 126 | box-shadow: 0 0 10px #FF6C60; 127 | border-radius: 10px; 128 | padding: 20px; 129 | box-sizing: border-box; 130 | } 131 | .shareItem{ 132 | .shareArea{ 133 | margin-right: 30px; 134 | flex:1; 135 | .shareTitle{ 136 | border-left: 4px solid #a9d86e; 137 | } 138 | } 139 | .sharelast{ 140 | margin-right: 0; 141 | } 142 | } 143 | 144 | .saleColor{ 145 | color:@saleColor; 146 | } 147 | .taxColor{ 148 | color:@taxColor; 149 | } 150 | .extenedColor{ 151 | color: @extenedColor; 152 | } 153 | .likeColor{ 154 | color: @likeColor; 155 | } 156 | .saleBgcolor{ 157 | background:@saleColor; 158 | } 159 | .taxBgcolor{ 160 | background: @taxColor; 161 | } 162 | .extenedBgcolor{ 163 | background: @extenedColor; 164 | } 165 | .likeBgcolor{ 166 | background: @likeColor; 167 | } 168 | .linkBgColor{ 169 | background: @linkColor; 170 | } 171 | .keleBgColor{ 172 | background: @keleColor; 173 | } 174 | 175 | 176 | -------------------------------------------------------------------------------- /src/style/scrollBar.less: -------------------------------------------------------------------------------- 1 | // 竖向滚动条 2 | .is-scroll-right::-webkit-scrollbar, 3 | .is-scroll-left::-webkit-scrollbar, 4 | .el-scrollbar::-webkit-scrollbar { 5 | width: 4px; 6 | height: 8px !important; 7 | } 8 | /*滚动条的背景区域的内阴影*/ 9 | .el-scrollbar::-webkit-scrollbar-track { 10 | // box-shadow:0px 1px 3px rgba(0,0,0,0.3) inset; 11 | /*滚动条的背景区域的圆角*/ 12 | border-radius: 10px; 13 | /*滚动条的背景颜色*/ 14 | background-color: #ddd; 15 | } 16 | /*滑块 滚动条的内阴影*/ 17 | .is-scroll-right::-webkit-scrollbar-thumb, 18 | .is-scroll-left::-webkit-scrollbar-thumb, 19 | .el-scrollbar::-webkit-scrollbar-thumb { 20 | box-shadow:0px 1px 3px rgba(0,0,0,0.3) inset; 21 | background-color:#FF6C60; 22 | border-radius: 4px; 23 | } 24 | 25 | // 横向滚动条 26 | ::-webkit-scrollbar:horizontal { 27 | width:0; 28 | height:10px !important; 29 | } 30 | ::-webkit-scrollbar-track:horizontal { 31 | background-color:#fff; 32 | } 33 | 34 | 35 | 36 | 37 | 38 | 39 | // .el-scrollbar:active>.el-scrollbar__bar,.el-scrollbar:focus>.el-scrollbar__bar,.el-scrollbar:hover>.el-scrollbar__bar { 40 | // opacity: 1; 41 | // -webkit-transition: opacity 340ms ease-out; 42 | // transition: opacity 340ms ease-out 43 | // } 44 | 45 | // .el-scrollbar__wrap { 46 | // overflow: scroll; 47 | // height: 100%; 48 | // } 49 | 50 | // .el-scrollbar__wrap--hidden-default::-webkit-scrollbar { 51 | // width: 0; 52 | // height: 0 53 | // } 54 | 55 | // .el-scrollbar__thumb { 56 | // position: relative; 57 | // display: block; 58 | // width: 0; 59 | // height: 0; 60 | // cursor: pointer; 61 | // border-radius: inherit; 62 | // background-color: rgba(144,147,153,.3); 63 | // -webkit-transition: .3s background-color; 64 | // transition: .3s background-color 65 | // } 66 | 67 | // .el-scrollbar__thumb:hover { 68 | // background-color: rgba(144,147,153,.5) 69 | // } 70 | 71 | // .el-scrollbar__bar { 72 | // position: absolute; 73 | // right: 2px; 74 | // bottom: 2px; 75 | // z-index: 1; 76 | // border-radius: 4px; 77 | // opacity: 0; 78 | // -webkit-transition: opacity 120ms ease-out; 79 | // transition: opacity 120ms ease-out 80 | // } 81 | 82 | // .el-scrollbar__bar.is-vertical { 83 | // width: 6px; 84 | // top: 2px 85 | // } 86 | 87 | // .el-scrollbar__bar.is-vertical>div { 88 | // width: 100% 89 | // } 90 | 91 | // .el-scrollbar__bar.is-horizontal { 92 | // height: 6px; 93 | // left: 2px 94 | // } 95 | 96 | // .el-scrollbar__bar.is-horizontal>div { 97 | // height: 100% 98 | // } 99 | -------------------------------------------------------------------------------- /src/style/variables.less: -------------------------------------------------------------------------------- 1 | // 基础颜色 2 | @saleColor:#FFA3A1; 3 | @taxColor:#84d9d2; 4 | @extenedColor:#87DE75; 5 | @likeColor:#a5e7f0; 6 | @linkColor:#93b7e3; 7 | @keleColor:#edafda; 8 | 9 | 10 | // 背景颜色 11 | @white: #ffffff; 12 | @primary-color: #313653; 13 | @primary-color-light: fade(lighten(@primary-color, 5%), 15%); 14 | // 顶部背景色 15 | @layout-header-background:@primary-color; 16 | // 左边菜单light颜色 17 | @layout-sider-background-light: #f9f9f9; 18 | // 字体颜色 19 | @text-color: #000000; 20 | @table-selected-row-bg: #fbfbfb; 21 | @primary-2: @primary-color-light; 22 | // 基础圆角 23 | @border-radius-base: 2px; 24 | // 输入框后缀背景色 25 | @input-addon-bg: @primary-color; 26 | 27 | -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | // const TokenKey = 'Admin-Token' 4 | 5 | export function getToken(TokenKey) { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(TokenKey,token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken(TokenKey) { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/axios.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message, MessageBox } from 'element-ui' 3 | import store from '../store' 4 | import { getToken } from '@/utils/auth' 5 | 6 | // 创建axios实例 7 | let service = axios.create({ 8 | baseURL: process.env.BASE_API, // api的base_url 9 | timeout: 5000 // 请求超时时间 10 | }) 11 | // request拦截器 12 | service.interceptors.request.use(config => { 13 | if (store.getters.token) { 14 | config.headers = { 15 | 'Authorization' : "Token " + getToken('Token'), //携带权限参数 16 | }; 17 | } 18 | return config 19 | }, error => { 20 | Promise.reject(error) 21 | }) 22 | 23 | // respone拦截器 24 | service.interceptors.response.use( 25 | response => { 26 | /** 27 | * code:200,接口正常返回; 28 | */ 29 | const res = response.data 30 | if (res.code !== 200) { 31 | Message({ 32 | message: res.message, 33 | type: 'error', 34 | duration: 5 * 1000 35 | }) 36 | // 根据服务端约定的状态码:5001:非法的token; 5002:其他客户端登录了; 5004:Token 过期了; 37 | if (res.code === 5001 || res.code === 5002 || res.code === 5004) { 38 | MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', { 39 | confirmButtonText: '重新登录', 40 | cancelButtonText: '取消', 41 | type: 'warning' 42 | }).then(() => { 43 | store.dispatch('LogOut').then(() => { 44 | location.reload()// 为了重新实例化vue-router对象 避免bug 45 | }) 46 | }) 47 | } 48 | return Promise.reject('error') 49 | } else { // res.code === 200,正常返回数据 50 | return response.data 51 | } 52 | }, 53 | error => { 54 | Message({ 55 | message: error.message, 56 | type: 'error', 57 | duration: 5 * 1000 58 | }) 59 | return Promise.reject(error) 60 | } 61 | ) 62 | 63 | export default service 64 | -------------------------------------------------------------------------------- /src/utils/env.js: -------------------------------------------------------------------------------- 1 | const github = 'https://github.com/wdlhao/vue2-element-touzi-admin'; 2 | const appUrl = process.env.VUE_APP_URL // development和production环境是不同的 3 | const shareUrl = 'https://juejin.im/post/5d0b4d28f265da1baf7cf293' 4 | const shareTitle = '用Vue-cli3+element+mockjs 实现后台管理权限系统及顶栏三级菜单显示'; 5 | const weibo = { 6 | 'weiboUrl': 'http://service.weibo.com/share/share.php', 7 | 'weiboAppkey' : '2003962826', 8 | 'pic':'https://user-gold-cdn.xitu.io/2019/6/20/16b7425dfa01dbf3?imageView2/1/w/1304/h/734/q/85/format/webp/interlace/1' 9 | } 10 | const qq = { 11 | 'baseUrl':'http://connect.qq.com/widget/shareqq/index.html', 12 | 'pic':'https://user-gold-cdn.xitu.io/2019/6/20/16b7425dfa01dbf3?imageView2/1/w/1304/h/734/q/85/format/webp/interlace/1', 13 | 'desc':'最近完成了我的后台管理系统权限功能的实现,同时觉得后台系统所有的菜单都左置,会限制菜单的扩展,因此我改进了三级菜单的显示。', 14 | 'summary':'文章梗概', 15 | 'source':'qzone' 16 | } 17 | const qqZone = { 18 | 'baseUrl':'https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey', 19 | 'pic':'https://user-gold-cdn.xitu.io/2019/6/20/16b7425dfa01dbf3?imageView2/1/w/1304/h/734/q/85/format/webp/interlace/1', 20 | 'desc':'最近完成了我的后台管理系统权限功能的实现,同时觉得后台系统所有的菜单都左置,会限制菜单的扩展,因此我改进了三级菜单的显示。', 21 | 'summary':'文章梗概', 22 | 'site':'qzone' 23 | } 24 | const douban = { 25 | 'baseUrl':'https://www.douban.com/share/service', 26 | 'pic':'https://user-gold-cdn.xitu.io/2019/6/20/16b7425dfa01dbf3?imageView2/1/w/1304/h/734/q/85/format/webp/interlace/1', 27 | } 28 | export { 29 | appUrl, 30 | shareUrl, 31 | shareTitle, 32 | weibo, 33 | qq, 34 | qqZone, 35 | douban, 36 | github 37 | } -------------------------------------------------------------------------------- /src/utils/mUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 存储localStorage 3 | */ 4 | export const setStore = (name, content) => { 5 | if (!name) return; 6 | if (typeof content !== 'string') { 7 | content = JSON.stringify(content); 8 | } 9 | window.localStorage.setItem(name, content); 10 | } 11 | 12 | /** 13 | * 获取localStorage 14 | */ 15 | export const getStore = name => { 16 | if (!name) return; 17 | var value = window.localStorage.getItem(name); 18 | if (value !== null) { 19 | try { 20 | value = JSON.parse(value); 21 | } catch (e) { 22 | value = value; 23 | } 24 | } 25 | return value; 26 | } 27 | 28 | /** 29 | * 删除localStorage 30 | */ 31 | export const removeStore = name => { 32 | if (!name) return; 33 | window.localStorage.removeItem(name); 34 | } 35 | 36 | /** 37 | * 让整数自动保留2位小数 38 | */ 39 | // export const returnFloat = value => { 40 | // var value=Math.round(parseFloat(value)*100)/100; 41 | // var xsd=value.toString().split("."); 42 | // if(xsd.length==1){ 43 | // value=value.toString()+".00"; 44 | // return value; 45 | // } 46 | // if(xsd.length>1){ 47 | // if(xsd[1].length<2){ 48 | // value=value.toString()+"0"; 49 | // } 50 | // return value; 51 | // } 52 | // } 53 | /** 54 | * @param {date} 标准时间格式:Fri Nov 17 2017 09:26:23 GMT+0800 (中国标准时间) 55 | * @param {type} 类型 56 | * type == 1 ---> "yyyy-mm-dd hh:MM:ss.fff" 57 | * type == 2 ---> "yyyymmddhhMMss" 58 | * type == '' ---> "yyyy-mm-dd hh:MM:ss" 59 | */ 60 | export const formatDate = (date, type) =>{ 61 | var year = date.getFullYear();//年 62 | var month = date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1;//月 63 | var day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate();//日 64 | var hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours();//时 65 | var minutes = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();//分 66 | var seconds = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();//秒 67 | var milliseconds = date.getMilliseconds() < 10 ? "0" + date.getMilliseconds() : date.getMilliseconds() //毫秒 68 | if (type == 1) { 69 | return year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds + "." + milliseconds; 70 | } else if(type == 2){ 71 | return year+""+month+""+day+""+hour+""+minutes+""+seconds; 72 | }else if(type == 3){ 73 | return year + "-" + month + "-" + day; 74 | }else { 75 | return year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds; 76 | } 77 | } 78 | /** 79 | * 时间转换:20150101010101 --> '2015-01-01 01:01:01' 80 | */ 81 | export const parseToDate = (timeValue) => { 82 | var result = ""; 83 | var year = timeValue.substr(0, 4); 84 | var month = timeValue.substr(4, 2); 85 | var date = timeValue.substr(6, 2); 86 | var hour = timeValue.substr(8, 2); 87 | var minute = timeValue.substr(10, 2); 88 | var second = timeValue.substr(12, 2); 89 | result = year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second; 90 | return result; 91 | } 92 | /** 93 | * 判断空值 94 | */ 95 | export const isEmpty = (keys) => { 96 | if (typeof keys === "string") { 97 | keys = keys.replace(/\"| |\\/g, '').replace(/(^\s*)|(\s*$)/g, ""); 98 | if (keys == "" || keys == null || keys == "null" || keys === "undefined" ) { 99 | return true; 100 | } else { 101 | return false; 102 | } 103 | } else if (typeof keys === "undefined") { // 未定义 104 | return true; 105 | } else if (typeof keys === "number") { 106 | return false; 107 | }else if(typeof keys === "boolean"){ 108 | return false; 109 | }else if(typeof keys == "object"){ 110 | if(JSON.stringify(keys )=="{}"){ 111 | return true; 112 | }else if(keys == null){ // null 113 | return true; 114 | }else{ 115 | return false; 116 | } 117 | } 118 | 119 | if(keys instanceof Array && keys.length == 0){// 数组 120 | return true; 121 | } 122 | 123 | } 124 | 125 | /** 126 | * 返回两位的小数的字符串 127 | */ 128 | export const toFixedNum = (num) => { 129 | const tonum = Number(num).toFixed(2); 130 | return tonum; 131 | } 132 | 133 | export const showMessage = () =>{ 134 | this.$message({ 135 | showClose: true, 136 | message: '对不起,您暂无此操作权限~', 137 | type: 'success' 138 | }); 139 | } 140 | 141 | /** 142 | * 读取base64 143 | */ 144 | export const readFile = file => { 145 | console.log(file) 146 | //var file = this.files[0]; 147 | //判断是否是图片类型 148 | if (!/image\/\w+/.test(file.raw.type)) { 149 | alert("只能选择图片"); 150 | return false; 151 | } 152 | var reader = new FileReader(); 153 | reader.readAsDataURL(file); 154 | reader.onload = function (e) { 155 | var filedata = { 156 | filename: file.name, 157 | filebase64: e.target.result 158 | } 159 | alert(e.target.result) 160 | } 161 | } 162 | 163 | /** 164 | * 动态插入css 165 | */ 166 | export const loadStyle = url => { 167 | const link = document.createElement('link') 168 | link.type = 'text/css' 169 | link.rel = 'stylesheet' 170 | link.href = url 171 | const head = document.getElementsByTagName('head')[0] 172 | head.appendChild(link) 173 | } 174 | /** 175 | * 设置浏览器头部标题 176 | */ 177 | export const setTitle = (title) => { 178 | title = title ? `${title}` : '小爱Admin' 179 | window.document.title = title 180 | } 181 | 182 | export const param2Obj = url => { 183 | const search = url.split('?')[1] 184 | if (!search) { 185 | return {} 186 | } 187 | return JSON.parse('{"' + decodeURIComponent(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}') 188 | } 189 | 190 | //是否为正整数 191 | export const isInteger = (s) => { 192 | var re = /^[0-9]+$/ ; 193 | return re.test(s) 194 | } 195 | 196 | export const setContentHeight = (that,ele,height) => { 197 | that.$nextTick(() => { 198 | ele.style.height = (document.body.clientHeight - height)+'px' 199 | }) 200 | } 201 | -------------------------------------------------------------------------------- /src/utils/share.js: -------------------------------------------------------------------------------- 1 | import { weibo,qq,qqZone,douban,shareUrl,shareTitle } from "@/utils/env"; 2 | import * as mutils from "@/utils/mUtils"; 3 | 4 | function getParamsUrl(obj){ 5 | let paramsUrl = ''; 6 | for(let key in obj){ 7 | paramsUrl += key+'='+obj[key]+'&' 8 | } 9 | return paramsUrl; 10 | } 11 | 12 | export function shareConfig(type,obj){ 13 | let baseUrl = ''; 14 | if(mutils.isEmpty(obj)){ 15 | obj = {}; 16 | } 17 | switch(type){ 18 | case 'weibo': 19 | const weiboData = { 20 | 'url':shareUrl, // 内容链接,默认当前页面location 21 | 'title':shareTitle, // 可选参数, 默认当前页title 22 | 'pic':obj.pic || weibo.pic, // 分享图片的路径(可选),多张图片通过"||"分开。 23 | 'count':'y', /**是否显示分享数,y|n(可选)*/ 24 | 'searchPic':true // 是否要自动抓取页面上的图片。true|falsetrue:自动抓取,false:不自动抓取。 25 | } 26 | baseUrl = weibo.weiboUrl+'?appkey='+weibo.weiboAppkey+getParamsUrl(weiboData); 27 | window.open(baseUrl,'_blank'); 28 | break; 29 | case 'qq': 30 | const qqData = { 31 | 'url':shareUrl, 32 | 'title':shareTitle, 33 | 'pics':obj.pic || qq.pic, //QZone接口暂不支持发送多张图片的能力,若传入多张图片,则会自动选入第一张图片作为预览图。 34 | 'source':obj.source || qq.source, // 分享来源 35 | 'desc':obj.desc || qq.desc, 36 | 'summary':obj.summary || qq.summary, 37 | } 38 | baseUrl = qq.baseUrl+'?'+getParamsUrl(qqData) 39 | window.open(baseUrl,'_blank'); 40 | break; 41 | case 'qqZone': 42 | const qqZoneData = { 43 | 'url':shareUrl, 44 | 'title':shareTitle, 45 | 'pics':obj.pic || (qqZone.pic).split(','), 46 | 'sharesource':obj.sharesource || qqZone.sharesource, // 分享来源 47 | 'desc':obj.desc || qqZone.desc, 48 | 'summary':obj.summary || qqZone.summary, 49 | } 50 | baseUrl = qqZone.baseUrl+'?'+getParamsUrl(qqZoneData) 51 | window.open(baseUrl,'_blank'); 52 | break; 53 | case 'douban': 54 | const doubanData = { 55 | 'href':shareUrl, 56 | 'name':shareTitle, 57 | 'image':obj.pic || douban.pic, 58 | } 59 | baseUrl = douban.baseUrl+'?'+getParamsUrl(doubanData) 60 | window.open(baseUrl,'_blank'); 61 | break; 62 | } 63 | } -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | 2 | const TerserPlugin = require('terser-webpack-plugin') // 用于在生成环境剔除debuger和console 3 | const CompressionPlugin = require("compression-webpack-plugin"); // gzip压缩,优化http请求,提高加载速度 4 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin // 代码分析工具 5 | const path = require('path'); 6 | const resolve = dir => { 7 | return path.join(__dirname, dir); 8 | }; 9 | 10 | const env = process.env.NODE_ENV 11 | let target = process.env.VUE_APP_URL // development和production环境是不同的 12 | 13 | const cdn = { 14 | // 开发环境 15 | dev: { 16 | css: [], 17 | js: [ 18 | ] 19 | }, 20 | // 生产环境 21 | build: { 22 | css: [ 23 | 'https://cdn.bootcss.com/element-ui/2.11.1/theme-chalk/index.css', 24 | 'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.css' 25 | ], 26 | js: [ 27 | 'https://cdn.bootcss.com/vue/2.6.10/vue.min.js', 28 | 'https://cdn.bootcss.com/vue-router/3.1.2/vue-router.min.js', 29 | 'https://cdn.bootcss.com/vuex/2.3.1/vuex.min.js', 30 | 'https://cdn.bootcss.com/axios/0.19.0/axios.min.js', 31 | 'https://cdn.bootcss.com/vue-i18n/8.13.0/vue-i18n.min.js', 32 | 'https://cdn.bootcss.com/element-ui/2.11.1/index.js', 33 | 'https://cdn.bootcss.com/echarts/3.8.5/echarts.min.js', 34 | 'https://cdn.bootcss.com/Mock.js/1.0.1-beta3/mock-min.js', 35 | 'https://cdn.bootcss.com/nprogress/0.2.0/nprogress.min.js', 36 | 'https://cdn.bootcss.com/js-cookie/2.2.0/js.cookie.min.js' 37 | ] 38 | } 39 | } 40 | 41 | 42 | module.exports = { 43 | publicPath: process.env.NODE_ENV === "production" ? "/permission/" : "/", 44 | outputDir: './dist', 45 | assetsDir:'static', 46 | filenameHashing:true, // false 来关闭文件名哈希 47 | lintOnSave: false, // 关闭eslint 48 | // 打包时不生成.map文件 49 | productionSourceMap: false, 50 | devServer: { 51 | open: true, 52 | host: '0.0.0.0', 53 | port: 8808 54 | // 由于本项目数据通过easy-mock和mockjs模拟,不存在跨域问题,无需配置代理; 55 | // proxy: { 56 | // '/v2': { 57 | // target: target, 58 | // changeOrigin: true 59 | // } 60 | // } 61 | }, 62 | // webpack相关配置 63 | chainWebpack: (config) => { 64 | config.entry.app = ['./src/main.js'] 65 | config.resolve.alias 66 | .set('@', resolve('src')) 67 | .set('cps', resolve('src/components')) 68 | // 关闭npm run build之后,This can impact web performance 警告 69 | config.performance 70 | .set('hints', false) 71 | // 移除 prefetch 插件 72 | config.plugins.delete("prefetch"); 73 | // 移除 preload 插件 74 | config.plugins.delete('preload'); 75 | // 压缩代码 76 | config.optimization.minimize(true); 77 | // 分割代码 78 | config.optimization.splitChunks({ 79 | chunks: 'all' 80 | }) 81 | // 对图片进行压缩处理 82 | config.module 83 | .rule('images') 84 | .use('image-webpack-loader') 85 | .loader('image-webpack-loader') 86 | .options({ 87 | disable: true, // webpack@2.x and newer 88 | quality: '65-80', 89 | speed: 4 90 | }) 91 | .end() 92 | // 项目文件大小分析 93 | config.plugin('webpack-bundle-analyzer') 94 | .use(new BundleAnalyzerPlugin({ 95 | openAnalyzer: false, // 是否打开默认浏览器 96 | analyzerPort:8777 97 | })) 98 | 99 | // 对vue-cli内部的 webpack 配置进行更细粒度的修改。 100 | // 添加CDN参数到htmlWebpackPlugin配置中, 详见public/index.html 修改 101 | config 102 | .plugin('html') 103 | .tap(args => { 104 | if (process.env.NODE_ENV === 'production') { 105 | args[0].cdn = cdn.build 106 | } 107 | if (process.env.NODE_ENV === 'development') { 108 | args[0].cdn = cdn.dev 109 | } 110 | return args 111 | }) 112 | }, 113 | configureWebpack:config => { 114 | // 为生产环境修改配置... 115 | if (process.env.NODE_ENV === 'production') { 116 | // 忽略生产环境打包的文件 117 | config.externals = { 118 | "vue": "Vue", 119 | "vue-router": "VueRouter", 120 | "vuex": "Vuex", 121 | "vue-i18n": "VueI18n", 122 | "axios": "axios", 123 | 'element-ui': 'ELEMENT', 124 | 'echarts':'echarts', 125 | 'mockjs':'Mock', 126 | 'nprogress':'NProgress', 127 | 'js-cookie':'Cookies' 128 | } 129 | // 去除console来减少文件大小,效果同'UglifyJsPlugin' 130 | new TerserPlugin({ 131 | cache: true, 132 | parallel: true, 133 | sourceMap: true, // Must be set to true if using source-maps in production 134 | terserOptions: { 135 | compress: { 136 | warnings: false, 137 | drop_console: true, 138 | drop_debugger: true, 139 | pure_funcs: ['console.log'] 140 | } 141 | } 142 | }) 143 | // 开启gzip压缩 144 | config.plugins.push(new CompressionPlugin({ 145 | algorithm: 'gzip', 146 | test: new RegExp("\\.(" + ["js", "css"].join("|") + ")quot;), // 匹配文件扩展名 147 | // threshold: 10240, // 对超过10k的数据进行压缩 148 | threshold: 5120, // 对超过5k的数据进行压缩 149 | minRatio: 0.8, 150 | cache: true, // 是否需要缓存 151 | deleteOriginalAssets:false // true删除源文件(不建议);false不删除源文件 152 | })) 153 | 154 | } else { 155 | // 为开发环境修改配置... 156 | 157 | } 158 | }, 159 | // 第三方插件配置 160 | pluginOptions: { 161 | 162 | } 163 | } 164 | 165 | --------------------------------------------------------------------------------