├── .env
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── index.html
├── markimg
├── 0.png
├── 1.png
├── 2.png
└── 3.png
├── package-lock.json
├── package.json
├── public
├── api.js
├── epgms
│ ├── account
│ │ ├── getUserInfo.json
│ │ ├── login.json
│ │ └── logout.json
│ └── template
│ │ └── list.json
├── favicon.ico
└── other
│ └── manage
│ └── add.json
├── src
├── App.vue
├── api
│ ├── epgms
│ │ ├── account
│ │ │ ├── getUserInfo.js
│ │ │ ├── login.js
│ │ │ └── logout.js
│ │ ├── index.js
│ │ └── template
│ │ │ └── list.js
│ └── other
│ │ ├── index.js
│ │ └── manage
│ │ └── add.js
├── assets
│ ├── 404.png
│ ├── 404_cloud.png
│ ├── logo.png
│ ├── user.png
│ └── welcome.png
├── components
│ ├── ColorPicker
│ │ └── index.vue
│ └── Layout
│ │ ├── components
│ │ ├── appmain
│ │ │ └── index.vue
│ │ ├── navbar
│ │ │ ├── components
│ │ │ │ ├── breadcrumb.vue
│ │ │ │ ├── hamburger.vue
│ │ │ │ └── userInfo.vue
│ │ │ └── index.vue
│ │ └── sidebar
│ │ │ ├── components
│ │ │ ├── filterRouter.js
│ │ │ ├── item.vue
│ │ │ └── logo.vue
│ │ │ ├── index.vue
│ │ │ └── sidebarItem.vue
│ │ └── index.vue
├── core
│ ├── addRoutePermission.js
│ ├── auth.js
│ ├── get-page-title.js
│ ├── permission.js
│ └── request.js
├── directive
│ └── index.js
├── elementui.js
├── main.js
├── mixin
│ └── global.js
├── plugin
│ └── icons
│ │ ├── svg
│ │ ├── eye-open.svg
│ │ ├── eye.svg
│ │ ├── link.svg
│ │ ├── logo.svg
│ │ ├── logo1.svg
│ │ ├── nest.svg
│ │ ├── portal-create.svg
│ │ ├── portal.svg
│ │ ├── template-audit.svg
│ │ ├── template-create.svg
│ │ ├── template.svg
│ │ ├── user-paint.svg
│ │ └── user.svg
│ │ ├── svgBuilder.js
│ │ └── svgIcon.vue
├── router
│ └── index.js
├── store
│ ├── getters.js
│ ├── index.js
│ └── modules
│ │ ├── skin.js
│ │ └── user.js
├── stylus
│ ├── chrome.styl
│ ├── elementUI.styl
│ ├── index.styl
│ ├── mixin.styl
│ ├── sidebar.styl
│ ├── transition.styl
│ └── variables.styl
└── views
│ ├── 404
│ └── index.vue
│ ├── dashboard
│ └── index.vue
│ ├── login
│ └── index.vue
│ ├── portal
│ └── create
│ │ └── index.vue
│ └── template
│ ├── audit
│ └── index.vue
│ └── create
│ └── index.vue
└── vite.config.js
/.env:
--------------------------------------------------------------------------------
1 | # 是否显示系统标题
2 | VITE_SYSTEM_SWITCH=true
3 |
4 | # 系统标题名称
5 | VITE_SYSTEM_NAME="vue3-admin-template-vite"
6 |
7 | # 系统标题svg
8 | VITE_SYSTEM_LOGO="logo1"
9 |
10 | # 权限类型 1: 静态权限 2: 动态权限
11 | VITE_POWER_TYPE=2
12 |
13 | # 版本号
14 | VITE_VERSION="Beta 0.0.1"
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/*
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["prettier"],
3 | "plugins": ["prettier"],
4 | "rules": {
5 | "prettier/prettier": ["error"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "trailingComma": "all",
4 | "printWidth": 70
5 | }
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Maxfengyan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## vue3-element-vite-template
2 |
3 | > 一款 vue3 开箱即用的纯净的后台管理系统,只包含 ui/权限/axios 封装/svg 等
4 |
5 | [github在线预览地址](https://maxfengyan.github.io/vue3-admin-template-vite/#/)
6 |
7 | [gitee在线预览地址](https://maxfengyan.gitee.io/vue3-admin-template-vite/#/)
8 |
9 | > 此项目是在 pan 大佬的vue-admin-template项目基础上进行功能微改的 vue3 版本,基本沿用 vue-admin-template 项目构造,添加了一些更贴合实际项目的改动和功能。如果有使用过 vue-admin-template 或者 vue-element-admin 的同学,轻松上手无缝切换。没使用过的话可以当作 vue3 的入门练习。
10 |
11 | #### 技术栈:vue3.x + vite2.x + element-plus + vue-router4.x + vuex4.x
12 |
13 | #### Build Setup
14 |
15 | ```bash
16 | git clone https://gitee.com/Maxfengyan/vue3-admin-template-vite.git
17 | cd vue3-admin-template-vite
18 | npm install
19 | npm run dev
20 | ```
21 | #### 项目截图
22 |
23 | 
24 |
25 | 
26 |
27 | 
28 |
29 | 
30 |
31 | #### 相对于 vue-admin-template 改动
32 |
33 | 1. **Vite**:使用 vite 构建工具,放弃 webpack。(vite 真香)
34 | 2. **Axios 封装请求**:
35 |
36 | > (1) 根据实际项目开发需求,系统中可能会请求多个不同的 **baseurl**,为方便处理封装 axios 类(自定义修改响应拦截在*src/core/request.js*)。
37 |
38 | > (2) 为了项目打包后也可以灵活调整后端接口地址,在打包后自动切换为闭包形式读取 baseurl(_public/api.js_),方便项目打包后部署时随便修改接口地址。
39 |
40 | 3. **权限过滤拦截**:权限处理更加颗粒化/灵活化。
41 |
42 | > (1) 在.env 配置文件中设置 VITE_POWER_TYPE 是否启用**动态权限**路由功能
43 |
44 | > (2) 改变动态路由逻辑,原本通过角色来确定路由权限,现改为每个路由中添加各自的唯一的路由标识,通过接口获取用户的权限标识,进行颗粒化的比对分配权限,更加灵活一些。
45 |
46 | > (3) 权限数据并非统一结构,可以结合项目进行调整(修改逻辑在*src/core/addRoutePermission*)
47 |
48 | 4. **Sidebar**:剔除了侧边栏适配 mobile 的响应式(懒...),调整代码逻辑,在.env 文件中设置。
49 | ```
50 | VITE_SYSTEM_SWITCH: 是否展示系统标题和logo
51 | VITE_SYSTEM_NAME: 系统标题
52 | VITE_SYSTEM_LOGO: 系统 logo
53 | ```
54 | 5. **svg**:下载 svg 文件存放于*src/plugin/icons/svg/*下,使用方式如下
55 | ```html
56 |
57 |
58 | ```
59 | 6. **mockJs**:因为本地开发时使用 **mockJs** 无法在浏览器捕捉到响应信息,故放弃,可采用以下两种方法:
60 |
61 | > (1) easy-mock(推荐):服务器搭建 easy-mock 服务进行接口管理,前后端开发方便协作规范。
62 |
63 | > (2) public:vite 项目 public 文件夹下文件会自动变为静态资源服务,可以在 public 文件夹下根据接口规范创建相应路径的 json 文件达到 ajax 请求目的。
64 |
65 |
66 | tips:**如果需要前后端联调接口需要在 vite.config.js 配置 proxy**
67 |
68 | #### 目录结构
69 |
70 | ```
71 | |- public // 公共静态资源以及模拟接口json文件
72 | |- src // 开发文件夹
73 | | |- api // 项目请求接口
74 | | |- assets // 静态图片
75 | | |- components // 公共组件
76 | | | |- ColorPicker // 颜色组件(修改全局皮肤,未完成)
77 | | | |- Layout // 项目布局核心组件
78 | | |- core // 封装核心功能
79 | | | |- addRoutePermission // 权限校验
80 | | | |- auth // 存放缓存
81 | | | |- get-page-title // 网页标题
82 | | | |- request // Axios封装
83 | | |- directive // 自定义指令
84 | | |- mixin // vue mixin
85 | | | |- global // 读取全局参数(颜色组件相关,未用)
86 | | |- plugin // 全局插件
87 | | | |- icons // 封装svg plugin
88 | | |- router // vue路由
89 | | |- store // vuex
90 | | | |- modules // vuex modules
91 | | | |- getters // vuex getters
92 | | | |- index // vuex入口
93 | | |- stylus // css-stylus
94 | | | |- chrome // chrome 覆盖原生css
95 | | | |- elementUI // element 覆盖原生css
96 | | | |- index // 入口styl
97 | | | |- sidebar // 侧边栏
98 | | | |- transition // 过渡动画
99 | | | |- variables // stylus变量
100 | | |- views // 页面
101 | | | |- 404 // 404
102 | | | |- dashboard // 根页面
103 | | | |- login // 登录
104 | | |- App // vue入口组件
105 | | |- elementui // 按需加载elementui组件
106 | | |- main // vue入口文件
107 | |- .env // 全局配置参数
108 | |- vite.config.js // vite配置文件
109 | ```
110 |
111 | #### 未完待续
112 |
113 | - 实现 vue-element-admin中的组件功能
114 | - typescript 版本
115 | - 采用setup方式,进行精简代码
116 |
117 | #### 路过的同学不要走 😀,如果本项目对你有帮助,请给我~~一键三连~~ 一个 star⭐~
118 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 | Vite App
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/markimg/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maxfengyan/vue3-admin-template-vite/294c8de4c4f46d9cf12780f896002fe6f69c2d04/markimg/0.png
--------------------------------------------------------------------------------
/markimg/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maxfengyan/vue3-admin-template-vite/294c8de4c4f46d9cf12780f896002fe6f69c2d04/markimg/1.png
--------------------------------------------------------------------------------
/markimg/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maxfengyan/vue3-admin-template-vite/294c8de4c4f46d9cf12780f896002fe6f69c2d04/markimg/2.png
--------------------------------------------------------------------------------
/markimg/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maxfengyan/vue3-admin-template-vite/294c8de4c4f46d9cf12780f896002fe6f69c2d04/markimg/3.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.0",
3 | "scripts": {
4 | "dev": "vite",
5 | "build": "vite build",
6 | "serve": "vite preview"
7 | },
8 | "dependencies": {
9 | "@element-plus/icons-vue": "^2.0.10",
10 | "axios": "^0.21.1",
11 | "element-plus": "^2.2.32",
12 | "js-cookie": "^2.2.1",
13 | "normalize.css": "^8.0.1",
14 | "nprogress": "^0.2.0",
15 | "path-to-regexp": "^6.2.0",
16 | "vue": "^3.2.6",
17 | "vue-router": "^4.0.11",
18 | "vuex": "^4.0.2"
19 | },
20 | "devDependencies": {
21 | "@vitejs/plugin-vue": "^1.6.0",
22 | "@vue/compiler-sfc": "^3.2.6",
23 | "eslint-config-prettier": "^8.3.0",
24 | "eslint-plugin-prettier": "^3.4.0",
25 | "ip": "^1.1.5",
26 | "stylus": "^0.54.8",
27 | "svg-sprite-loader": "^6.0.7",
28 | "unplugin-auto-import": "^0.15.0",
29 | "unplugin-element-plus": "^0.7.0",
30 | "unplugin-vue-components": "^0.24.0",
31 | "vite": "^2.5.1",
32 | "vite-plugin-style-import": "^1.2.1"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/public/api.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | window.api = {
3 | epgms:
4 | "http://10.10.8.14:9999/mock/5f23d33facd7311a719430ed/epgms",
5 | others:
6 | "http://10.10.8.14:9999/mock/5f23d3f0acd7311a719430f2/boss",
7 | };
8 | })();
9 |
--------------------------------------------------------------------------------
/public/epgms/account/getUserInfo.json:
--------------------------------------------------------------------------------
1 | {
2 | "resultCode": "0",
3 | "resultMessage": "获取成功",
4 | "data": {
5 | "roles": [
6 | "template",
7 | "template-create",
8 | "template-audit",
9 | "portal",
10 | "portal-create"
11 | ]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/public/epgms/account/login.json:
--------------------------------------------------------------------------------
1 | {
2 | "resultCode": "0",
3 | "resultMessage": "登录成功",
4 | "data": {
5 | "userName": "admin",
6 | "avator": "",
7 | "token": "xxxxxxxxxxxxxxxxxxxxxxxx",
8 | "role": "admin",
9 | "userAccount": "mafengyan"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/public/epgms/account/logout.json:
--------------------------------------------------------------------------------
1 | {
2 | "resultCode": "0",
3 | "resultMessage": "退出成功",
4 | "data": {}
5 | }
6 |
--------------------------------------------------------------------------------
/public/epgms/template/list.json:
--------------------------------------------------------------------------------
1 | {
2 | "resultCode": "0",
3 | "resultMessage": "请求",
4 | "data": [1, 2, 3, 4, 5]
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maxfengyan/vue3-admin-template-vite/294c8de4c4f46d9cf12780f896002fe6f69c2d04/public/favicon.ico
--------------------------------------------------------------------------------
/public/other/manage/add.json:
--------------------------------------------------------------------------------
1 | {
2 | "resultCode": "0",
3 | "resultMessage": "新增成功",
4 | "data": {}
5 | }
6 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
26 |
--------------------------------------------------------------------------------
/src/api/epgms/account/getUserInfo.js:
--------------------------------------------------------------------------------
1 | import epgmsRequest from "../index";
2 |
3 | export const getUserInfo = (data) => {
4 | return epgmsRequest({
5 | url: "/account/getUserInfo.json",
6 | method: "post",
7 | data,
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/src/api/epgms/account/login.js:
--------------------------------------------------------------------------------
1 | import epgmsRequest from "../index";
2 |
3 | export const login = (data) => {
4 | return epgmsRequest({
5 | url: "/account/login.json",
6 | method: "post",
7 | data,
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/src/api/epgms/account/logout.js:
--------------------------------------------------------------------------------
1 | import epgmsRequest from "../index";
2 |
3 | export const logout = () => {
4 | return epgmsRequest({
5 | url: "/account/logout.json",
6 | method: "post",
7 | });
8 | };
9 |
--------------------------------------------------------------------------------
/src/api/epgms/index.js:
--------------------------------------------------------------------------------
1 | import Axios from "@/core/request";
2 |
3 | const url = import.meta.env.DEV ? "/epgms" : window.api.epgms;
4 |
5 | const epgmsRequest = new Axios(url).getInstance();
6 |
7 | export default epgmsRequest;
8 |
--------------------------------------------------------------------------------
/src/api/epgms/template/list.js:
--------------------------------------------------------------------------------
1 | import epgmsRequest from "../index";
2 |
3 | export const getList = (data) => {
4 | return epgmsRequest({
5 | url: "/template/list.json",
6 | method: "post",
7 | data,
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/src/api/other/index.js:
--------------------------------------------------------------------------------
1 | import Axios from "@/core/request";
2 |
3 | const url = import.meta.env.DEV ? "/other" : window.api.others;
4 |
5 | const otherRequest = new Axios(url).getInstance();
6 |
7 | export default otherRequest;
8 |
--------------------------------------------------------------------------------
/src/api/other/manage/add.js:
--------------------------------------------------------------------------------
1 | import otherRequest from "../index";
2 |
3 | export const manageAdd = (data) => {
4 | return otherRequest({
5 | url: "/manage/add.json",
6 | method: "post",
7 | data,
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/src/assets/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maxfengyan/vue3-admin-template-vite/294c8de4c4f46d9cf12780f896002fe6f69c2d04/src/assets/404.png
--------------------------------------------------------------------------------
/src/assets/404_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maxfengyan/vue3-admin-template-vite/294c8de4c4f46d9cf12780f896002fe6f69c2d04/src/assets/404_cloud.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maxfengyan/vue3-admin-template-vite/294c8de4c4f46d9cf12780f896002fe6f69c2d04/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maxfengyan/vue3-admin-template-vite/294c8de4c4f46d9cf12780f896002fe6f69c2d04/src/assets/user.png
--------------------------------------------------------------------------------
/src/assets/welcome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maxfengyan/vue3-admin-template-vite/294c8de4c4f46d9cf12780f896002fe6f69c2d04/src/assets/welcome.png
--------------------------------------------------------------------------------
/src/components/ColorPicker/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
39 |
47 |
--------------------------------------------------------------------------------
/src/components/Layout/components/appmain/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
17 |
18 |
32 |
--------------------------------------------------------------------------------
/src/components/Layout/components/navbar/components/breadcrumb.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ item.meta.title }}
10 | {{ item.meta.title }}
11 |
12 |
13 |
14 |
15 |
16 |
78 |
79 |
96 |
--------------------------------------------------------------------------------
/src/components/Layout/components/navbar/components/hamburger.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
38 |
39 |
51 |
--------------------------------------------------------------------------------
/src/components/Layout/components/navbar/components/userInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ userName }}
7 |
8 |
9 |
10 |
11 | 个人信息
14 | 退出登录
15 |
16 |
17 |
18 |
19 |
20 |
21 |
45 |
46 |
--------------------------------------------------------------------------------
/src/components/Layout/components/navbar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
40 |
41 |
--------------------------------------------------------------------------------
/src/components/Layout/components/sidebar/components/filterRouter.js:
--------------------------------------------------------------------------------
1 | export default (children = []) => {
2 | const filterChildren = children.filter((item) => {
3 | if (!item.hidden) {
4 | return true;
5 | }
6 | });
7 | return filterChildren;
8 | };
9 |
--------------------------------------------------------------------------------
/src/components/Layout/components/sidebar/components/item.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ title }}
4 |
5 |
18 |
--------------------------------------------------------------------------------
/src/components/Layout/components/sidebar/components/logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
40 |
41 |
--------------------------------------------------------------------------------
/src/components/Layout/components/sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/components/Layout/components/sidebar/sidebarItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
15 |
20 |
21 |
22 |
23 |
24 |
44 |
45 |
46 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/src/components/Layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
39 |
40 |
--------------------------------------------------------------------------------
/src/core/addRoutePermission.js:
--------------------------------------------------------------------------------
1 | import { asyncRoutes } from "@/router/index";
2 | export default function filterAsyncRoutes(roles, routes = asyncRoutes) {
3 | const res = [];
4 | routes.forEach((route) => {
5 | const tmp = { ...route };
6 | if (hasPermission(roles, tmp)) {
7 | if (tmp.children) {
8 | tmp.children = filterAsyncRoutes(roles, tmp.children);
9 | }
10 | res.push(tmp);
11 | }
12 | });
13 | return res;
14 | }
15 |
16 | function hasPermission(roles, route) {
17 | if (route.meta && route.meta.role) {
18 | return roles.some((role) => role === route.meta.role);
19 | } else {
20 | return true;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/core/auth.js:
--------------------------------------------------------------------------------
1 | /**
2 | * * * * * * * * * * * * * * *
3 | * author: 马丰彦
4 | * date: 2021-06-22
5 | * function: 封装js-cookie
6 | * * * * * * * * * * * * * * *
7 | */
8 | import Cookies from "js-cookie";
9 |
10 | const Token = "epgms-token";
11 | const UserName = "epgms-username";
12 |
13 | export function getToken() {
14 | return Cookies.get(Token);
15 | }
16 |
17 | export function setToken(token) {
18 | Cookies.set(Token, token);
19 | }
20 |
21 | export function removeToken() {
22 | Cookies.remove(Token);
23 | }
24 |
25 | export function getUserName() {
26 | return Cookies.get(UserName);
27 | }
28 |
29 | export function setUserName(username) {
30 | Cookies.set(UserName, username);
31 | }
32 |
--------------------------------------------------------------------------------
/src/core/get-page-title.js:
--------------------------------------------------------------------------------
1 | const title = import.meta.env.VITE_SYSTEM_NAME;
2 |
3 | export default (pageTitle) => {
4 | return pageTitle ? `${pageTitle} - ${title}` : `${title}`;
5 | };
6 |
--------------------------------------------------------------------------------
/src/core/permission.js:
--------------------------------------------------------------------------------
1 | /**
2 | * * * * * * * * * * * * * * *
3 | * author: 马丰彦
4 | * date: 2021-06-22
5 | * function: 全局权限
6 | * * * * * * * * * * * * * * *
7 | */
8 |
9 | import { getToken } from "@/core/auth";
10 | import { router } from "@/router/index";
11 | import store from "@/store";
12 | import NProgress from "nprogress";
13 | import "nprogress/nprogress.css"; // css初始化
14 | import getPageTitle from "./get-page-title";
15 | const whiteList = ["/login"]; // 不重定向白名单
16 | const powerType = import.meta.env.VITE_POWER_TYPE; // 权限类型
17 |
18 | NProgress.configure({ showSpinner: false }); // 进度条
19 |
20 | router.beforeEach(async (to, from, next) => {
21 | // 进度条开始
22 | NProgress.start();
23 |
24 | document.title = getPageTitle(to.meta.title);
25 | const token = getToken();
26 |
27 | // 有token,已经登录
28 | if (token) {
29 | // 动态路由权限
30 | if (powerType === "2") {
31 | const { routes } = store.getters;
32 | // 权限正常
33 | if (Array.isArray(routes) && routes.length > 0) {
34 | next();
35 | } else {
36 | // 已经登录但是强制刷新页面
37 | const result = await store.dispatch("GetUserInfo", token);
38 | if (Array.isArray(result) && result.length > 0) {
39 | // 与路由表过滤权限
40 | const routes = await store.dispatch("GenerateRoutes", result);
41 | routes.forEach((item) => router.addRoute(item));
42 |
43 | next({ ...to, replace: true });
44 | } else {
45 | alert("无权限");
46 | return false;
47 | }
48 | }
49 | } else {
50 | // 静态路由权限
51 | next();
52 | }
53 | } else {
54 | if (whiteList.indexOf(to.path) !== -1) {
55 | next();
56 | } else {
57 | next(`/login?redirect=${to.path}`); // 否则全部重定向到登录页
58 | }
59 | }
60 | NProgress.done();
61 | });
62 |
--------------------------------------------------------------------------------
/src/core/request.js:
--------------------------------------------------------------------------------
1 | /**
2 | * * * * * * * * * * * * * * *
3 | * author: 马丰彦
4 | * date: 2021-06-18
5 | * function: 封装axios类
6 | * * * * * * * * * * * * * * *
7 | */
8 | import axios from "axios";
9 |
10 | class Axios {
11 | constructor(url, requestTime) {
12 | this.url = url;
13 | this.axiosInstance = null;
14 | this.createService(url, requestTime);
15 | }
16 |
17 | createService(url, requestTime) {
18 | const timeout = requestTime || 10000;
19 | this.axiosInstance = axios.create({
20 | baseURL: url,
21 | withCredentials: true,
22 | timeout: timeout,
23 | });
24 | this.interceptorsRequestToken();
25 | this.interceptorsResponseToken();
26 | }
27 |
28 | interceptorsRequestToken() {
29 | this.axiosInstance.interceptors.request.use(
30 | (config) => {
31 | config.url = this.handleJsonSuffix(config.url);
32 | return config;
33 | },
34 | (error) => {
35 | return Promise.reject(error);
36 | },
37 | );
38 | }
39 |
40 | interceptorsResponseToken() {
41 | this.axiosInstance.interceptors.response.use(
42 | (response) => {
43 | // 相应拦截预留处理 自定义错误码
44 | return response.data;
45 | },
46 | (error) => {
47 | return Promise.reject(error);
48 | },
49 | );
50 | }
51 |
52 | getUrl() {
53 | return this.url;
54 | }
55 |
56 | getInstance() {
57 | return this.axiosInstance;
58 | }
59 |
60 | // 处理.json后缀
61 | handleJsonSuffix(url) {
62 | // 不排除真的有请求.json的情况,偷懒嫌疑
63 | if (!import.meta.env.DEV && url.indexOf(".json") !== -1) {
64 | return url.replace(".json", "");
65 | } else {
66 | return url;
67 | }
68 | }
69 | }
70 |
71 | export default Axios;
72 |
--------------------------------------------------------------------------------
/src/directive/index.js:
--------------------------------------------------------------------------------
1 | export default (app) => {
2 | app.directive("focus", {
3 | mounted(el, binding) {
4 | el.focus();
5 | },
6 | });
7 | };
8 |
--------------------------------------------------------------------------------
/src/elementui.js:
--------------------------------------------------------------------------------
1 | import * as ElementPlusIconsVue from "@element-plus/icons-vue";
2 | import {
3 | ElButton,
4 | ElForm,
5 | ElFormItem,
6 | ElInput,
7 | ElIcon,
8 | ElColorPicker,
9 | ElTag,
10 | ElScrollbar,
11 | ElMenu,
12 | ElMenuItem,
13 | ElSubMenu,
14 | ElBreadcrumb,
15 | ElBreadcrumbItem,
16 | ElDropdown,
17 | ElDropdownItem,
18 | ElDropdownMenu,
19 | } from "element-plus";
20 |
21 | const useElementUi = (app) => {
22 | for (const [key, component] of Object.entries(
23 | ElementPlusIconsVue,
24 | )) {
25 | app.component(key, component);
26 | }
27 | app.component(ElButton.name, ElButton);
28 | app.component(ElForm.name, ElForm);
29 | app.component(ElFormItem.name, ElFormItem);
30 | app.component(ElInput.name, ElInput);
31 | app.component(ElIcon.name, ElIcon);
32 | app.component(ElColorPicker.name, ElColorPicker);
33 | app.component(ElTag.name, ElTag);
34 | app.component(ElScrollbar.name, ElScrollbar);
35 | app.component(ElMenu.name, ElMenu);
36 | app.component(ElMenuItem.name, ElMenuItem);
37 | app.component(ElSubMenu.name, ElSubMenu);
38 | app.component(ElBreadcrumb.name, ElBreadcrumb);
39 | app.component(ElBreadcrumbItem.name, ElBreadcrumbItem);
40 | app.component(ElDropdown.name, ElDropdown);
41 | app.component(ElDropdownItem.name, ElDropdownItem);
42 | app.component(ElDropdownMenu.name, ElDropdownMenu);
43 | };
44 |
45 | export default useElementUi;
46 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "vue";
2 | import App from "./App.vue";
3 |
4 | import { router } from "./router/index"; // router
5 |
6 | import store from "./store"; // store
7 |
8 | import directive from "./directive/index"; // directive
9 |
10 | import useElementUi from "./elementui"; // 按需引入element-ui
11 |
12 | import "./core/permission"; // 动态校验路由
13 |
14 | import "./stylus/index.styl"; // css文件
15 |
16 | import svgIcon from "./plugin/icons/svgIcon.vue"; // svg文件
17 |
18 | const app = createApp(App);
19 |
20 | directive(app); // 放在mount上面,有顺序的
21 |
22 | useElementUi(app); // 引用element-ui
23 |
24 | app.component("svg-icon", svgIcon);
25 |
26 | app.use(store);
27 |
28 | app.use(router);
29 |
30 | app.mount("#app");
31 |
--------------------------------------------------------------------------------
/src/mixin/global.js:
--------------------------------------------------------------------------------
1 | const globalProperty = {
2 | computed: {
3 | bgColor() {
4 | return this.$store.getters.bgColor;
5 | },
6 | },
7 | watch: {
8 | bgColor(newValue) {
9 | this.backgroundColor = newValue;
10 | },
11 | },
12 | };
13 |
14 | export default globalProperty;
15 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/eye-open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/logo1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/nest.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/portal-create.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/portal.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/template-audit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/template-create.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/template.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/user-paint.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svg/user.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/plugin/icons/svgBuilder.js:
--------------------------------------------------------------------------------
1 | import { readFileSync, readdirSync } from "fs";
2 |
3 | let idPerfix = "";
4 | const svgTitle = /", "");
40 | svgRes.push(svg);
41 | }
42 | }
43 | return svgRes;
44 | }
45 |
46 | export const svgBuilder = (path, perfix = "icon") => {
47 | if (path === "") return;
48 | idPerfix = perfix;
49 | const res = findSvgFile(path);
50 | return {
51 | name: "svg-transform",
52 | transformIndexHtml(html) {
53 | return html.replace(
54 | "",
55 | `
56 |
57 |
60 | `
61 | );
62 | },
63 | };
64 | };
65 |
--------------------------------------------------------------------------------
/src/plugin/icons/svgIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
35 |
36 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory } from "vue-router";
2 | import Layout from "@/components/Layout/index.vue";
3 |
4 | const powerType = import.meta.env.VITE_POWER_TYPE; // 权限类型
5 | /**
6 | * hidden: true 如果为true,侧边栏显示,否则是为通用隐藏路由
7 | * alwaysShow: true 如果为true,将总显示
8 | * 如果设置了,路由数量必须大于1
9 | * redirect: noRedirect 如果设置noRedirect,面包屑不重定向标题
10 | * meta : {
11 | role: 'admin' 路由权限唯一标识
12 | title: 'title' 路由标题,面包屑展示标题
13 | icon: 'svg-name' 路由图标
14 | breadcrumb: false 如果为false,面包屑标题隐藏
15 | }
16 | */
17 |
18 | // 静态路由
19 |
20 | const constantRouters = [
21 | {
22 | path: "/",
23 | name: "index",
24 | redirect: "/dashboard/index",
25 | },
26 | {
27 | path: "/dashboard",
28 | name: "Dashboard",
29 | component: Layout,
30 | redirect: "/dashboard/index",
31 | children: [
32 | {
33 | name: "Index",
34 | path: "index",
35 | component: () => import("@/views/dashboard/index.vue"),
36 | },
37 | ],
38 | },
39 | {
40 | path: "/login",
41 | name: "login",
42 | component: () => import("@/views/login/index.vue"),
43 | },
44 | ];
45 |
46 | // 动态路由
47 | export const asyncRoutes = [
48 | {
49 | path: "/nested",
50 | // hidden: true,
51 | component: Layout,
52 | name: "Nested",
53 | meta: {
54 | title: "Nested",
55 | icon: "nest",
56 | },
57 | children: [
58 | {
59 | path: "menu1",
60 | component: () => import("@/views/template/create/index.vue"), // Parent router-view
61 | name: "Menu1",
62 | meta: {
63 | title: "Menu1",
64 | },
65 | children: [
66 | {
67 | path: "menu1-1",
68 | hidden: true,
69 | component: () =>
70 | import("@/views/template/create/index.vue"),
71 | name: "Menu1-1",
72 | meta: {
73 | title: "Menu1-1",
74 | },
75 | },
76 | {
77 | path: "menu1-2",
78 | component: () =>
79 | import("@/views/template/create/index.vue"),
80 | name: "Menu1-2",
81 | meta: {
82 | title: "Menu1-2",
83 | },
84 | children: [
85 | {
86 | path: "menu1-2-1",
87 | component: () =>
88 | import("@/views/template/create/index.vue"),
89 | name: "Menu1-2-1",
90 | meta: {
91 | title: "Menu1-2-1",
92 | },
93 | },
94 | {
95 | path: "menu1-2-2",
96 | component: () =>
97 | import("@/views/template/create/index.vue"),
98 | name: "Menu1-2-2",
99 | meta: {
100 | title: "Menu1-2-2",
101 | },
102 | },
103 | ],
104 | },
105 | {
106 | path: "menu1-3",
107 | component: () =>
108 | import("@/views/template/create/index.vue"),
109 | name: "Menu1-3",
110 | meta: {
111 | title: "Menu1-3",
112 | },
113 | },
114 | ],
115 | },
116 | {
117 | path: "menu2",
118 | component: () => import("@/views/template/create/index.vue"),
119 | meta: {
120 | title: "menu2",
121 | },
122 | },
123 | ],
124 | },
125 | {
126 | path: "/template",
127 | component: Layout,
128 | name: "Template",
129 | redirect: "/template/create",
130 | meta: {
131 | title: "模板管理",
132 | role: "template",
133 | icon: "template",
134 | },
135 | children: [
136 | {
137 | path: "create",
138 | name: "template-create",
139 | component: () => import("@/views/template/create/index.vue"),
140 | meta: {
141 | title: "模板创建",
142 | icon: "template-create",
143 | role: "template-create",
144 | },
145 | },
146 | {
147 | path: "audit",
148 | name: "template-audit",
149 | component: () => import("@/views/template/audit/index.vue"),
150 | meta: {
151 | title: "模板审核",
152 | icon: "template-audit",
153 | role: "template-audit",
154 | },
155 | },
156 | ],
157 | },
158 | {
159 | path: "/portal",
160 | component: Layout,
161 | name: "Portal",
162 | redirect: "/portal/create",
163 | meta: {
164 | role: "portal",
165 | icon: "portal",
166 | },
167 | children: [
168 | {
169 | path: "create",
170 | name: "portal-create",
171 | component: () => import("@/views/portal/create/index.vue"),
172 | meta: {
173 | title: "门户创建",
174 | icon: "portal-create",
175 | role: "portal-create",
176 | },
177 | },
178 | ],
179 | },
180 | {
181 | path: "/extra",
182 | name: "Extra",
183 | component: () => import("@/views/404/index.vue"),
184 | children: [
185 | {
186 | path: "https://github.com/Maxfengyan/vue3-admin-template-vite",
187 | meta: {
188 | title: "外链",
189 | icon: "link",
190 | },
191 | },
192 | ],
193 | },
194 | // 重定向放最后
195 | {
196 | path: "/:pathMatch(.*)*",
197 | hidden: true,
198 | name: "404",
199 | component: () => import("@/views/404/index.vue"),
200 | },
201 | ];
202 |
203 | const allRouter =
204 | powerType == "1"
205 | ? constantRouters.concat(asyncRoutes)
206 | : constantRouters;
207 |
208 | export const router = createRouter({
209 | history: createWebHashHistory(),
210 | routes: allRouter,
211 | });
212 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | bgColor: (state) => state.skin.bgColor,
3 | token: (state) => state.user.token,
4 | userName: (state) => state.user.userName,
5 | account: (state) => state.user.account,
6 | avatar: (state) => state.user.avatar,
7 | routes: (state) => state.user.routes,
8 | sidebar: (state) => state.user.sidebar,
9 | };
10 |
11 | export default getters;
12 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from "vuex";
2 | import getters from "./getters";
3 | import skin from "./modules/skin";
4 | import user from "./modules/user";
5 |
6 | export default createStore({
7 | modules: {
8 | skin,
9 | user
10 | },
11 | getters,
12 | });
13 |
--------------------------------------------------------------------------------
/src/store/modules/skin.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | bgColor: "#2d3a4b",
3 | contentColor: "#fff",
4 | };
5 |
6 | const mutations = {
7 | SET_BGCOLOR: (state, bgcolor) => {
8 | state.bgColor = bgcolor;
9 | },
10 | };
11 |
12 | const actions = {
13 | Changebgcolor(context, bgColor) {
14 | context.commit("SET_BGCOLOR", bgColor);
15 | },
16 | };
17 |
18 | export default {
19 | namespaced: true,
20 | state,
21 | mutations,
22 | actions,
23 | };
24 |
--------------------------------------------------------------------------------
/src/store/modules/user.js:
--------------------------------------------------------------------------------
1 | import { login } from "@/api/epgms/account/login";
2 | import { getUserInfo } from "@/api/epgms/account/getUserInfo";
3 | import { logout } from "@/api/epgms/account/logout";
4 | import filterAsyncRoutes from "@/core/addRoutePermission";
5 | import {
6 | setToken,
7 | getToken,
8 | setUserName,
9 | getUserName,
10 | removeToken,
11 | } from "@/core/auth";
12 |
13 | const state = {
14 | token: getToken(),
15 | userName: getUserName(),
16 | account: "",
17 | avatar: "",
18 | sidebar: true,
19 | routes: [],
20 | };
21 |
22 | const mutations = {
23 | SET_TOKEN: (state, token) => {
24 | state.token = token;
25 | },
26 | SET_NAME: (state, userName) => {
27 | state.userName = userName;
28 | },
29 | SET_ACCOUNT: (state, account) => {
30 | state.account = account;
31 | },
32 | SET_AVATAR: (state, avatar) => {
33 | state.avatar = avatar;
34 | },
35 | SET_SIDEBAR: (state) => {
36 | state.sidebar = !state.sidebar;
37 | },
38 | SET_ROUTES: (state, routes) => {
39 | state.routes = routes;
40 | },
41 | };
42 |
43 | const actions = {
44 | // 登录
45 | async LoginAction(context, userInfo) {
46 | const { account, password } = userInfo;
47 | var result = await login({
48 | account: account.trim(),
49 | password: password.trim(),
50 | });
51 | const { data } = result;
52 | const { token, userName, avator, userAccount } = data;
53 | context.commit("SET_TOKEN", token);
54 | context.commit("SET_NAME", userName);
55 | context.commit("SET_AVATAR", avator);
56 | context.commit("SET_ACCOUNT", userAccount);
57 | setToken(token);
58 | setUserName(userName);
59 | },
60 |
61 | // 获取用户信息
62 | async GetUserInfo(context, token) {
63 | const result = await getUserInfo(token);
64 | const { data } = result;
65 | const { roles } = data;
66 | return roles;
67 | },
68 |
69 | // 退出登录
70 | async Logout(context) {
71 | logout().then(() => {
72 | context.commit("SET_TOKEN", "");
73 | context.commit("SET_NAME", "");
74 | context.commit("SET_AVATAR", "");
75 | context.commit("SET_ACCOUNT", "");
76 | removeToken();
77 | });
78 | },
79 |
80 | // sidebar status
81 | ChangeSidebar(context) {
82 | context.commit("SET_SIDEBAR");
83 | },
84 |
85 | async GenerateRoutes(context, roles) {
86 | const accessedRoutes = await filterAsyncRoutes(roles);
87 | context.commit("SET_ROUTES", accessedRoutes);
88 | return accessedRoutes;
89 | },
90 | };
91 |
92 | export default {
93 | namespace: true,
94 | state,
95 | mutations,
96 | actions,
97 | };
98 |
--------------------------------------------------------------------------------
/src/stylus/chrome.styl:
--------------------------------------------------------------------------------
1 | input:-webkit-autofill {
2 | // 字体颜色
3 | -webkit-text-fill-color: #ededed !important;
4 | // 背景颜色
5 | background-color:transparent;
6 | // 背景图片
7 | background-image: none;
8 | // 过渡
9 | transition: background-color 50000s ease-in-out 0s;
10 | }
--------------------------------------------------------------------------------
/src/stylus/elementUI.styl:
--------------------------------------------------------------------------------
1 | .color-picker .el-color-picker__trigger {
2 | border: none !important;
3 | }
4 |
5 | .el-breadcrumb__inner,
6 | .el-breadcrumb__inner a {
7 | font-weight: 400 !important;
8 | }
9 |
10 | .el-sub-menu__title {
11 | padding-right: 0;
12 | }
--------------------------------------------------------------------------------
/src/stylus/index.styl:
--------------------------------------------------------------------------------
1 | @import "normalize.css";
2 | @import "variables.styl";
3 | @import "chrome.styl";
4 | @import "elementUI.styl";
5 | @import "sidebar.styl";
6 | @import "transition.styl";
7 | @import "mixin.styl";
8 | body {
9 | height: 100%;
10 | -moz-osx-font-smoothing: grayscale;
11 | -webkit-font-smoothing: antialiased;
12 | text-rendering: optimizeLegibility;
13 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
14 | }
15 |
16 | label {
17 | font-weight: 700;
18 | }
19 |
20 | html {
21 | height: 100%;
22 | box-sizing: border-box;
23 | }
24 |
25 | *,
26 | *:before,
27 | *:after {
28 | box-sizing: inherit;
29 | }
30 |
31 | a,
32 | a:focus,
33 | a:hover {
34 | cursor: pointer;
35 | color: inherit;
36 | outline: none;
37 | text-decoration: none;
38 | }
39 |
40 | div:focus{
41 | outline: none;
42 | }
43 |
44 | a:focus,
45 | a:active {
46 | outline: none;
47 | }
48 |
49 | a,
50 | a:focus,
51 | a:hover {
52 | cursor: pointer;
53 | color: inherit;
54 | text-decoration: none;
55 | }
56 |
57 | //main-container全局样式
58 | .app-main{
59 | min-height: 100%;
60 | }
61 |
62 | .app-container {
63 | padding: 20px;
64 | }
65 |
--------------------------------------------------------------------------------
/src/stylus/mixin.styl:
--------------------------------------------------------------------------------
1 | ellipsis()
2 | overflow: hidden
3 | white-space: nowrap
4 | text-overflow: ellipsis
--------------------------------------------------------------------------------
/src/stylus/sidebar.styl:
--------------------------------------------------------------------------------
1 | #app {
2 | .main-container {
3 | min-height: 100%;
4 | transition: margin-left .28s;
5 | margin-left: $sideBarWidth;
6 | position: relative;
7 | }
8 | .sidebar-container {
9 | transition: width 0.28s;
10 | height: 100%;
11 | position: fixed;
12 | top: 0;
13 | left: 0;
14 | bottom: 0;
15 | overflow: hidden;
16 | z-index: 1001;
17 | width: 180px;
18 | background-color: $menuBg;
19 |
20 | // reset element-ui css
21 | .horizontal-collapse-transition {
22 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
23 | }
24 |
25 | .scrollbar-wrapper {
26 | overflow-x: hidden !important;
27 | }
28 |
29 | .el-scrollbar__bar.is-vertical {
30 | right: 0px;
31 | }
32 |
33 | .el-scrollbar {
34 | height: 100%;
35 | }
36 |
37 | &.has-logo {
38 | .el-scrollbar {
39 | height: calc(100% - 50px);
40 | }
41 | }
42 |
43 | .is-horizontal {
44 | display: none;
45 | }
46 |
47 | a {
48 | // display: inline-block;
49 | width: 100%;
50 | overflow: hidden;
51 | }
52 |
53 | .svg-icon {
54 | margin-right: 16px;
55 | }
56 |
57 | .el-menu {
58 | border: none;
59 | height: 100%;
60 | width: 100% !important;
61 | }
62 |
63 | // menu hover
64 | .submenu-title-noDropdown,
65 | .el-submenu__title {
66 | &:hover {
67 | background-color: $menuHover !important;
68 | }
69 | }
70 |
71 | .is-active>.el-submenu__title {
72 | color: $subMenuActiveText !important;
73 | }
74 |
75 | & .nest-menu .el-submenu>.el-submenu__title,
76 | & .el-submenu .el-menu-item {
77 | min-width: $sideBarWidth !important;
78 | background-color: $subMenuBg !important;
79 | &:hover {
80 | background-color: $subMenuHover !important;
81 | }
82 | }
83 | }
84 |
85 | .hideSidebar {
86 | .sidebar-container {
87 | width: 54px !important;
88 | }
89 |
90 | .main-container {
91 | margin-left: 54px;
92 | }
93 |
94 | .svg-icon {
95 | margin-right: 0px;
96 | }
97 |
98 | .submenu-title-noDropdown {
99 | padding: 0 !important;
100 | position: relative;
101 |
102 | .svg-icon {
103 | margin-left: 20px;
104 | }
105 | }
106 |
107 | .el-submenu {
108 | overflow: hidden;
109 |
110 | &>.el-submenu__title {
111 | padding: 0 !important;
112 |
113 | .svg-icon {
114 | margin-left: 20px;
115 | }
116 |
117 | .el-submenu__icon-arrow {
118 | display: none;
119 | }
120 | }
121 | }
122 |
123 | .el-menu--collapse {
124 | .el-submenu {
125 | &>.el-submenu__title {
126 | &>span {
127 | height: 0;
128 | width: 0;
129 | overflow: hidden;
130 | visibility: hidden;
131 | display: inline-block;
132 | }
133 | }
134 | }
135 | }
136 | }
137 |
138 | .el-menu--collapse .el-menu .el-submenu {
139 | min-width: $sideBarWidth !important;
140 | }
141 |
142 | // mobile responsive
143 | /* .mobile {
144 | .main-container {
145 | margin-left: 0px;
146 | }
147 |
148 | .sidebar-container {
149 | transition: transform .28s;
150 | width: $sideBarWidth !important;
151 | }
152 |
153 | &.hideSidebar {
154 | .sidebar-container {
155 | pointer-events: none;
156 | transition-duration: 0.3s;
157 | transform: translate3d(-$sideBarWidth, 0, 0);
158 | }
159 | }
160 | }
161 |
162 | .withoutAnimation {
163 |
164 | .main-container,
165 | .sidebar-container {
166 | transition: none;
167 | }
168 | } */
169 |
170 | }
171 |
172 |
173 |
174 | // when menu collapsed
175 | .el-menu--vertical {
176 | &>.el-menu {
177 | .svg-icon {
178 | margin-right: 16px;
179 | }
180 | }
181 |
182 | .nest-menu .el-submenu>.el-submenu__title,
183 | .el-menu-item {
184 | &:hover {
185 | // you can use $subMenuHover
186 | background-color: $menuHover !important;
187 | }
188 | }
189 |
190 | // the scroll bar appears when the subMenu is too long
191 | >.el-menu--popup {
192 | max-height: 100vh;
193 | overflow-y: auto;
194 |
195 | &::-webkit-scrollbar-track-piece {
196 | background: #d3dce6;
197 | }
198 |
199 | &::-webkit-scrollbar {
200 | width: 6px;
201 | }
202 |
203 | &::-webkit-scrollbar-thumb {
204 | background: #99a9bf;
205 | border-radius: 20px;
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/stylus/transition.styl:
--------------------------------------------------------------------------------
1 |
2 | /* fade */
3 | .fade-enter-active,
4 | .fade-leave-active {
5 | transition: opacity 0.28s;
6 | }
7 |
8 | .fade-enter-from,
9 | .fade-leave-active {
10 | opacity: 0;
11 | }
12 |
13 |
14 | /* fade-transform */
15 | .fade-transform-leave-active,
16 | .fade-transform-enter-active {
17 | transition: all .5s;
18 | }
19 |
20 | .fade-transform-enter-from {
21 | opacity: 0;
22 | transform: translateX(-30px);
23 | }
24 |
25 | .fade-transform-leave-to {
26 | opacity: 0;
27 | transform: translateX(30px);
28 | }
29 |
30 |
31 | /* breadcrumb transition */
32 | .breadcrumb-enter-active,
33 | .breadcrumb-leave-active {
34 | transition: all .5s;
35 | }
36 |
37 | .breadcrumb-enter-from,
38 | .breadcrumb-leave-active {
39 | opacity: 0;
40 | transform: translateX(20px);
41 | }
42 |
43 | .breadcrumb-move-from {
44 | transition: all .5s;
45 | }
46 |
47 | .breadcrumb-leave-active {
48 | position: absolute;
49 | }
50 |
--------------------------------------------------------------------------------
/src/stylus/variables.styl:
--------------------------------------------------------------------------------
1 | // sidebar
2 | $menuText = #bfcbd9;
3 | $menuActiveText = #409EFF;
4 | $subMenuActiveText = #f4f4f5;
5 |
6 | $menuBg = #304156;
7 | $menuHover = #263445;
8 |
9 | $subMenuBg = #1f2d3d;
10 | $subMenuHover = #001528;
11 |
12 | $sideBarWidth = 180px;
13 |
14 | $backgroundColor = #2d3a4b;
15 | /* export {
16 | menuText: $menuText;
17 | menuActiveText: $menuActiveText;
18 | subMenuActiveText: $subMenuActiveText;
19 | menuBg: $menuBg;
20 | menuHover: $menuHover;
21 | subMenuBg: $subMenuBg;
22 | subMenuHover: $subMenuHover;
23 | sideBarWidth: $sideBarWidth;
24 | } */
--------------------------------------------------------------------------------
/src/views/404/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
出错啦!
12 |
页面不存在请重试
13 |
14 |
回到主页
15 |
16 |
17 |
18 |
19 |
20 |
33 |
34 |
254 |
260 |
--------------------------------------------------------------------------------
/src/views/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![]()
4 |
5 |
6 |
7 |
17 |
18 |
25 |
--------------------------------------------------------------------------------
/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 | {{ systemName }}
11 |
12 |
13 |
18 |
19 |
20 |
21 |
28 |
34 |
35 | 登录
42 | 版本号:{{ version }}
43 |
44 | 账号:any 密码:any
45 |
46 |
47 |
48 |
49 |
50 |
126 |
127 |
206 |
--------------------------------------------------------------------------------
/src/views/portal/create/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ route }}
4 | {{ data }}
5 |
6 |
7 |
8 |
26 |
27 |
--------------------------------------------------------------------------------
/src/views/template/audit/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ route }}
4 | {{ manage }}
5 |
6 |
7 |
8 |
27 |
28 |
--------------------------------------------------------------------------------
/src/views/template/create/index.vue:
--------------------------------------------------------------------------------
1 |
2 | template/create
3 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | const ip = require("ip");
2 |
3 | import { defineConfig } from "vite";
4 | import vue from "@vitejs/plugin-vue";
5 | import AutoImport from "unplugin-auto-import/vite";
6 | import Components from "unplugin-vue-components/vite";
7 | import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
8 | import { svgBuilder } from "./src/plugin/icons/svgBuilder";
9 | import ElementPlus from "unplugin-element-plus/vite";
10 |
11 | const path = require("path");
12 |
13 | function _resolve(dir) {
14 | return path.resolve(__dirname, dir);
15 | }
16 | // https://vitejs.dev/config/
17 | export default defineConfig({
18 | resolve: {
19 | alias: {
20 | "@": _resolve("src"),
21 | },
22 | },
23 | plugins: [
24 | vue(),
25 | ElementPlus({}),
26 | AutoImport({
27 | resolvers: [ElementPlusResolver()],
28 | }),
29 | Components({
30 | resolvers: [ElementPlusResolver()],
31 | }),
32 | svgBuilder("./src/plugin/icons/svg/"),
33 | ],
34 | server: {
35 | port: 9000,
36 | cors: true,
37 | host: ip.address(),
38 | },
39 | });
40 |
--------------------------------------------------------------------------------