This commit is contained in:
yzcheng90
2023-01-18 15:28:50 +08:00
parent 7b48b11180
commit 66dc0d1deb
109 changed files with 10000 additions and 0 deletions

5
.env Normal file
View File

@@ -0,0 +1,5 @@
# port 端口号
VUE_APP_PORT = 9999
# open 运行 npm run dev 时自动打开浏览器
VUE_APP_OPEN = false

2
.env.development Normal file
View File

@@ -0,0 +1,2 @@
# 开发环境
VUE_APP_BASE_API = 'http://localhost:9999/'

2
.env.production Normal file
View File

@@ -0,0 +1,2 @@
# 线上环境
VUE_APP_BASE_API = ''

18
.eslintignore Normal file
View File

@@ -0,0 +1,18 @@
*.sh
node_modules
lib
*.md
*.scss
*.woff
*.ttf
*.json
.vscode
.idea
dist
mock
public
bin
build
config
index.html
src/assets

22
.eslintrc.js Normal file
View File

@@ -0,0 +1,22 @@
module.exports = {
root: true,
env: {
node: true,
},
parserOptions: {
parser: '@babel/eslint-parser',
},
plugins: ['vue'],
extends: ['plugin:vue/essential', 'eslint:recommended'],
rules: {
// http://eslint.cn/docs/rules/
'vue/no-parsing-error': 'off',
'no-unused-vars': 'error',
'no-dupe-args': 'error',
'no-empty': 'off',
'no-extra-semi': 'off',
'no-constant-condition': 'off',
'no-console': 'error',
'vue/multi-word-component-names': 'off',
},
};

22
.gitignore vendored Normal file
View File

@@ -0,0 +1,22 @@
node_modules
.DS_Store
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

39
.prettierrc.js Normal file
View File

@@ -0,0 +1,39 @@
module.exports = {
// 一行最多多少个字符
printWidth: 150,
// 指定每个缩进级别的空格数
tabWidth: 2,
// 使用制表符而不是空格缩进行
useTabs: true,
// 在语句末尾打印分号
semi: true,
// 使用单引号而不是双引号
singleQuote: true,
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
quoteProps: 'as-needed',
// 在JSX中使用单引号而不是双引号
jsxSingleQuote: false,
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>"默认none
trailingComma: 'es5',
// 在对象文字中的括号之间打印空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 在单独的箭头函数参数周围包括括号 always(x) => x \ avoidx => x
arrowParens: 'always',
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeStart: 0,
rangeEnd: Infinity,
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准 always\never\preserve
proseWrap: 'preserve',
// 指定HTML文件的全局空格敏感度 css\strict\ignore
htmlWhitespaceSensitivity: 'css',
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'lf',
};

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 lyt-Top
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

37
README.md Normal file
View File

@@ -0,0 +1,37 @@
#### 🚧 安装 cnpm、yarn
- 复制代码(桌面 cmd 运行) `npm install -g cnpm --registry=https://registry.npm.taobao.org`
- 复制代码(桌面 cmd 运行) `npm install -g yarn`
#### ⚡ 使用说明
建议使用 cnpm因为 yarn 有时会报错。`npm install` 安装报错的话,请使用 `cnpm install`
> 注意:`node` 需大于 `12.xxx` 小于等于 `v16.14.0`,否则安装依赖将报错。
```bash
# 克隆项目
git clone https://gitee.com/lyt-top/vue-next-admin.git
# 进入项目
cd vue-next-admin
# 切换分支
git checkout vue-prev-admin
# 安装依赖
cnpm install
cnpm install eslint-webpack-plugin --save-dev
cnpm install core-js --save-dev
# 运行项目
cnpm run dev
# 打包发布
cnpm run build
```
#### 📚 开发文档
- 查看开发文档:<a href="https://lyt-top.gitee.io/vue-next-admin-doc-preview" target="_blank">vue-next-admin-doc</a>

4
babel.config.js Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
presets: ['@vue/cli-plugin-babel/preset'],
plugins: ['@babel/plugin-proposal-optional-chaining'],
};

11
jsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"allowJs": true,
"lib": ["esnext", "dom", "dom.iterable", "scripthost"],
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"/@/*": ["src/*"]
}
}
}

71
package.json Normal file
View File

@@ -0,0 +1,71 @@
{
"name": "vue-prev-admin",
"version": "1.2.1",
"private": true,
"description": "vue2 webpack admin template",
"author": "lyt_20201208",
"license": "MIT",
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"webpack": "webpack --version"
},
"dependencies": {
"axios": "0.24.0",
"clipboard": "2.0.8",
"countup.js": "2.0.8",
"echarts": "5.2.2",
"element-ui": "2.15.6",
"nprogress": "0.2.0",
"screenfull": "5.2.0",
"sign-canvas": "1.1.4",
"vue": "2.6.14",
"vue-i18n": "8.26.7",
"vue-particles": "1.0.9",
"vue-router": "3.5.3",
"vue-seamless-scroll": "1.1.23",
"vuex": "3.6.2"
},
"devDependencies": {
"@babel/eslint-parser": "7.16.5",
"@babel/plugin-proposal-optional-chaining": "7.17.12",
"@vue/cli-plugin-babel": "4.5.15",
"@vue/cli-plugin-eslint": "4.5.15",
"@vue/cli-plugin-router": "4.5.15",
"@vue/cli-plugin-vuex": "4.5.15",
"@vue/cli-service": "4.5.15",
"core-js": "^3.27.1",
"eslint": "7.0.0",
"eslint-plugin-vue": "8.2.0",
"eslint-webpack-plugin": "^3.2.0",
"sass": "1.45.0",
"sass-loader": "10.1.1",
"vue-template-compiler": "2.6.14"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
],
"bugs": {
"url": "https://gitee.com/lyt-top/vue-next-admin/issues"
},
"engines": {
"node": ">=12.0.0",
"npm": ">= 6.0.0"
},
"keywords": [
"vue",
"vue3",
"vuejs/vue-next",
"element-ui",
"element-plus",
"vue-next-admin",
"next-admin"
],
"repository": {
"type": "git",
"url": "https://gitee.com/lyt-top/vue-next-admin.git"
}
}

290
public/admin.json Normal file
View File

@@ -0,0 +1,290 @@
{
"code": 0,
"data": [
{
"path": "/home",
"name": "home",
"component": "home",
"meta": {
"title": "message.router.home",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": true,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-shouye"
}
},
{
"path": "/tools",
"name": "tools",
"component": "tools",
"meta": {
"title": "message.router.tools",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-gongju"
}
},
{
"path": "/menu",
"name": "menu",
"component": "layout/routerView/parent",
"redirect": "/menu/menu1",
"meta": {
"title": "message.router.menu",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
},
"children": [
{
"path": "/menu/menu1",
"name": "menu1",
"component": "layout/routerView/parent",
"redirect": "/menu/menu1/menu11",
"meta": {
"title": "message.router.menu1",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
},
"children": [
{
"path": "/menu/menu1/menu11",
"name": "menu11",
"component": "menu/menu1/menu11/index",
"meta": {
"title": "message.router.menu11",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
},
{
"path": "/menu/menu1/menu12",
"name": "menu12",
"component": "layout/routerView/parent",
"redirect": "/menu/menu1/menu12/menu121",
"meta": {
"title": "message.router.menu12",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
},
"children": [
{
"path": "/menu/menu1/menu12/menu121",
"name": "menu121",
"component": "menu/menu1/menu12/menu121/index",
"meta": {
"title": "message.router.menu121",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
},
{
"path": "/menu/menu1/menu12/menu122",
"name": "menu122",
"component": "menu/menu1/menu12/menu122/index",
"meta": {
"title": "message.router.menu122",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
}
]
},
{
"path": "/menu/menu1/menu13",
"name": "menu13",
"component": "menu/menu1/menu13/index",
"meta": {
"title": "message.router.menu13",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
}
]
},
{
"path": "/menu/menu2",
"name": "menu2",
"component": "menu/menu2/index",
"meta": {
"title": "message.router.menu2",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
}
]
},
{
"path": "/fun",
"name": "funIndex",
"component": "layout/routerView/parent",
"redirect": "/fun/tagsView",
"meta": {
"title": "message.router.funIndex",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-crew_feature"
},
"children": [
{
"path": "/fun/tagsView",
"name": "funTagsView",
"component": "fun/tagsView/index",
"meta": {
"title": "message.router.funTagsView",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "el-icon-thumb"
}
},
{
"path": "/fun/signCanvas",
"name": "funSignCanvas",
"component": "fun/signCanvas/index",
"meta": {
"title": "message.router.funSignCanvas",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "el-icon-edit"
}
}
]
},
{
"path": "/pages",
"name": "pagesIndex",
"component": "layout/routerView/parent",
"redirect": "/pages/formAdapt",
"meta": {
"title": "message.router.pagesIndex",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-fuzhiyemian"
},
"children": [
{
"path": "/pages/formAdapt",
"name": "pagesFormAdapt",
"component": "pages/formAdapt/index",
"meta": {
"title": "message.router.pagesFormAdapt",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-biaodan"
}
}
]
},
{
"path": "/personal",
"name": "personal",
"component": "personal/index",
"meta": {
"title": "message.router.personal",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-gerenzhongxin"
}
},
{
"path": "/link",
"name": "layoutLinkView",
"component": "layout/routerView/parent",
"meta": {
"title": "message.router.layoutLinkView",
"isLink": "https://element-plus.gitee.io/#/zh-CN/component/installation",
"isHide": false,
"isKeepAlive": false,
"isAffix": false,
"isIframe": false,
"roles": ["admin"],
"icon": "iconfont icon-caozuo-wailian"
}
},
{
"path": "/iframes",
"name": "layoutIfameView",
"component": "layout/routerView/parent",
"meta": {
"title": "message.router.layoutIfameView",
"isLink": "https://element-plus.gitee.io/zh-CN/#/zh-CN/component/installation",
"isHide": false,
"isKeepAlive": false,
"isAffix": true,
"isIframe": true,
"roles": ["admin"],
"icon": "iconfont icon-neiqianshujuchucun"
}
}
]
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

30
public/index.html Normal file
View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="keywords"
content="vue-next-adminvue-prev-adminvue-admin-wonderful后台管理系统一站式平台模板希望可以帮你完成快速开发。vue2.xvue2.0vue2vue3vue3.xvue3.0CompositionAPItypescriptelement pluselementplusadminwonderfulwonderful-nextvue-next-adminvitevite-admin快速高效后台模板后台系统管理系统"
/>
<meta
name="description"
content="vue-next-admin基于 vue3 + CompositionAPI + typescript + vite + element plus适配手机、平板、pc 的后台开源免费管理系统模板vue-prev-admin基于 vue2 + element ui适配手机、平板、pc 的后台开源免费管理系统模板!"
/>
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title>vue-prev-admin</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript">
var _hmt = _hmt || [];
(function() {
var hm = document.createElement('script');
hm.src = 'https://hm.baidu.com/hm.js?9d1e524198ede8205ac7c938c243344c';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</body>
</html>

227
public/test.json Normal file
View File

@@ -0,0 +1,227 @@
{
"code": 0,
"data": [
{
"path": "/home",
"name": "home",
"component": "home",
"meta": {
"title": "message.router.home",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": true,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-shouye"
}
},
{
"path": "/tools",
"name": "tools",
"component": "tools",
"meta": {
"title": "message.router.tools",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin"],
"icon": "iconfont icon-gongju"
}
},
{
"path": "/menu",
"name": "menu",
"component": "layout/routerView/parent",
"redirect": "/menu/menu1",
"meta": {
"title": "message.router.menu",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin"],
"icon": "iconfont icon-caidan"
},
"children": [
{
"path": "/menu/menu1",
"name": "menu1",
"component": "layout/routerView/parent",
"redirect": "/menu/menu1/menu11",
"meta": {
"title": "message.router.menu1",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
},
"children": [
{
"path": "/menu/menu1/menu11",
"name": "menu11",
"component": "menu/menu1/menu11/index",
"meta": {
"title": "message.router.menu11",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
},
{
"path": "/menu/menu1/menu12",
"name": "menu12",
"component": "layout/routerView/parent",
"redirect": "/menu/menu1/menu12/menu121",
"meta": {
"title": "message.router.menu12",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
},
"children": [
{
"path": "/menu/menu1/menu12/menu121",
"name": "menu121",
"component": "menu/menu1/menu12/menu121/index",
"meta": {
"title": "message.router.menu121",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
},
{
"path": "/menu/menu1/menu12/menu122",
"name": "menu122",
"component": "menu/menu1/menu12/menu122/index",
"meta": {
"title": "message.router.menu122",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
}
]
},
{
"path": "/menu/menu1/menu13",
"name": "menu13",
"component": "menu/menu1/menu13/index",
"meta": {
"title": "message.router.menu13",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
}
]
},
{
"path": "/menu/menu2",
"name": "menu2",
"component": "menu/menu2/index",
"meta": {
"title": "message.router.menu2",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-caidan"
}
}
]
},
{
"path": "/fun",
"name": "funIndex",
"component": "layout/routerView/parent",
"redirect": "/fun/tagsView",
"meta": {
"title": "message.router.funIndex",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "iconfont icon-crew_feature"
},
"children": [
{
"path": "/fun/tagsView",
"name": "funTagsView",
"component": "fun/tagsView/index",
"meta": {
"title": "message.router.funTagsView",
"isLink": "",
"isHide": false,
"isKeepAlive": true,
"isAffix": false,
"isIframe": false,
"roles": ["admin", "common"],
"icon": "el-icon-thumb"
}
}
]
},
{
"path": "/link",
"name": "layoutLinkView",
"component": "layout/routerView/parent",
"meta": {
"title": "message.router.layoutLinkView",
"isLink": "https://element-plus.gitee.io/#/zh-CN/component/installation",
"isHide": false,
"isKeepAlive": false,
"isAffix": false,
"isIframe": false,
"roles": ["admin"],
"icon": "iconfont icon-caozuo-wailian"
}
},
{
"path": "/iframes",
"name": "layoutIfameView",
"component": "layout/routerView/parent",
"meta": {
"title": "message.router.layoutIfameView",
"isLink": "https://gitee.com/lyt-top/vue-next-admin",
"isHide": false,
"isKeepAlive": false,
"isAffix": true,
"isIframe": true,
"roles": ["admin"],
"icon": "iconfont icon-neiqianshujuchucun"
}
}
]
}

76
src/App.vue Normal file
View File

@@ -0,0 +1,76 @@
<template>
<div id="app">
<router-view />
<Setings ref="setingsRef" />
<Upgrade v-if="getVersion" />
</div>
</template>
<script>
import config from '/package.json';
import setIntroduction from '@/utils/setIconfont.js';
import { Local } from '@/utils/storage.js';
import Setings from '@/layout/navBars/breadcrumb/setings.vue';
import Upgrade from '@/layout/upgrade/index.vue';
export default {
name: 'App',
components: { Setings, Upgrade },
mounted() {
this.initSetIconfont();
this.openSetingsDrawer();
this.getLayoutThemeConfig();
},
computed: {
// 获取版本号
getVersion() {
let isVersion = false;
if (this.$route.path !== '/login') {
if ((Local.get('version') && Local.get('version') !== config.version) || !Local.get('version')) isVersion = true;
}
return isVersion;
},
},
methods: {
// 设置初始化,防止刷新时恢复默认
initSetIconfont() {
// 设置批量第三方 icon 图标
setIntroduction.cssCdn();
// 设置批量第三方 js
setIntroduction.jsCdn();
},
// 布局配置弹窗打开
openSetingsDrawer() {
this.bus.$on('openSetingsDrawer', () => {
this.$refs.setingsRef.openDrawer();
});
},
// 获取缓存中的布局配置
getLayoutThemeConfig() {
if (Local.get('themeConfigPrev')) {
this.$store.dispatch('themeConfig/setThemeConfig', Local.get('themeConfigPrev'));
document.documentElement.style.cssText = Local.get('themeConfigStyle');
} else {
Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
}
},
},
watch: {
// 监听路由的变化
$route: {
handler(to) {
this.$nextTick(() => {
let webTitle = '';
let { globalTitle } = this.$store.state.themeConfig.themeConfig;
to.path === '/login' ? (webTitle = to.meta.title) : (webTitle = this.$t(to.meta.title));
document.title = `${webTitle} - ${globalTitle}` || globalTitle;
});
},
deep: true,
immediate: true,
},
},
destroyed() {
this.bus.$off('openSetingsDrawer');
},
};
</script>

25
src/api/login/index.js Normal file
View File

@@ -0,0 +1,25 @@
import request from '@/utils/request';
/**
* 登录api接口集合
* @method signIn 用户登录
* @method signOut 用户退出登录
*/
export function useLoginApi() {
return {
signIn: (params) => {
return request({
url: '/user/signIn',
method: 'post',
data: params,
});
},
signOut: (params) => {
return request({
url: '/user/signOut',
method: 'post',
data: params,
});
},
};
}

48
src/api/menu/index.js Normal file
View File

@@ -0,0 +1,48 @@
import request from '@/utils/request';
/**
* webpack 的代理只是本地开发生效,打包后需要在部署环境 搭建 nginx 代理
* 所以:
* 开发环境,请求跨越的接口。为了配置跨越示例
* 线上环境,请求目录下的 `json` 数据
* 一般后端接口都会处理跨越问题,可根据具体情况进行修改
* json 格式地址https://gitee.com/lyt-top/vue-next-admin-images/tree/master/vue2
* 本地菜单地址public/xxx.json
*/
/**
* 后端控制菜单模拟json路径在 https://gitee.com/lyt-top/vue-next-admin-images/tree/master/menu
* 后端控制路由isRequestRoutes 为 true则开启后端控制路由
* @method getMenuAdmin 获取后端动态路由菜单(admin)
* @method getMenuTest 获取后端动态路由菜单(test)
*/
export function useMenuApi() {
return {
getMenuAdmin: (params) => {
// 本地数据,路径:`/public/xxx.json`
return request({
url: './admin.json',
method: 'get',
params,
});
// 模拟跨域
// return request({
// url: '/gitee/lyt-top/vue-next-admin-images/raw/master/vue2/admin.json',
// method: 'get',
// });
},
getMenuTest: (params) => {
// 本地数据,路径:`/public/xxx.json`
return request({
url: './test.json',
method: 'get',
params,
});
// 模拟跨域
// return request({
// url: '/gitee/lyt-top/vue-next-admin-images/raw/master/vue2/test.json',
// method: 'get',
// });
},
};
}

60
src/i18n/index.js Normal file
View File

@@ -0,0 +1,60 @@
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import zhcnLocale from 'element-ui/lib/locale/lang/zh-CN';
import enLocale from 'element-ui/lib/locale/lang/en';
import zhtwLocale from 'element-ui/lib/locale/lang/zh-TW';
import store from '@/store/index.js';
import nextZhcn from '@/i18n/lang/zh-cn.js';
import nextEn from '@/i18n/lang/en.js';
import nextZhtw from '@/i18n/lang/zh-tw.js';
import pagesHomeZhcn from '@/i18n/pages/home/zh-cn.js';
import pagesHomeEn from '@/i18n/pages/home/en.js';
import pagesHomeZhtw from '@/i18n/pages/home/zh-tw.js';
import pagesLoginZhcn from '@/i18n/pages/login/zh-cn.js';
import pagesLoginEn from '@/i18n/pages/login/en.js';
import pagesLoginZhtw from '@/i18n/pages/login/zh-tw.js';
// 使用插件
Vue.use(VueI18n);
// 定义语言国际化内容
/**
* 说明:
* /src/i18n/lang 下的 js 为框架的国际化内容
* /src/i18n/pages 下的 js 为各界面的国际化内容
*/
const messages = {
'zh-cn': {
...zhcnLocale,
message: {
...nextZhcn,
...pagesHomeZhcn,
...pagesLoginZhcn,
},
},
en: {
...enLocale,
message: {
...nextEn,
...pagesHomeEn,
...pagesLoginEn,
},
},
'zh-tw': {
...zhtwLocale,
message: {
...nextZhtw,
...pagesHomeZhtw,
...pagesLoginZhtw,
},
},
};
// 导出语言国际化
export const i18n = new VueI18n({
locale: store.state.themeConfig.themeConfig.globalI18n,
fallbackLocale: 'zh-cn',
messages,
});

164
src/i18n/lang/en.js Normal file
View File

@@ -0,0 +1,164 @@
// 定义内容
export default {
router: {
home: 'home',
system: 'system',
systemMenu: 'systemMenu',
systemUser: 'systemUser',
limits: 'limits',
limitsFrontEnd: 'FrontEnd',
limitsFrontEndPage: 'FrontEndPage',
limitsFrontEndBtn: 'FrontEndBtn',
limitsBackEnd: 'BackEnd',
limitsBackEndEndPage: 'BackEndEndPage',
menu: 'menu',
menu1: 'menu1',
menu11: 'menu11',
menu12: 'menu12',
menu121: 'menu121',
menu122: 'menu122',
menu13: 'menu13',
menu2: 'menu2',
funIndex: 'function',
funTagsView: 'funTagsView',
funSignCanvas: 'Online signature',
funCountup: 'countup',
funEchartsTree: 'echartsTree',
funSelector: 'funSelector',
funWangEditor: 'wangEditor',
funCropper: 'cropper',
funMindMap: 'G6 MindMap',
funQrcode: 'qrcode',
funEchartsMap: 'EchartsMap',
funPrintJs: 'PrintJs',
funClipboard: 'Copy cut',
funScreenShort: 'screenCapture',
pagesIndex: 'pages',
pagesFiltering: 'Filtering',
pagesFilteringDetails: 'FilteringDetails',
pagesFilteringDetails1: 'FilteringDetails1',
pagesIocnfont: 'iconfont icon',
pagesElement: 'element icon',
pagesAwesome: 'awesome icon',
pagesCityLinkage: 'CityLinkage',
pagesFormAdapt: 'FormAdapt',
pagesListAdapt: 'ListAdapt',
pagesWaterfall: 'Waterfall',
pagesSteps: 'Steps',
chartIndex: 'chartIndex',
personal: 'personal',
tools: 'tools',
layoutLinkView: 'LinkView',
layoutIfameView: 'IfameView',
},
staticRoutes: {
signIn: 'signIn',
notFound: 'notFound',
noPower: 'noPower',
},
user: {
title0: 'Component size',
title1: 'Language switching',
title2: 'Menu search',
title3: 'Layout configuration',
title4: 'news',
title5: 'Full screen on',
title6: 'Full screen off',
dropdownDefault: 'default',
dropdownMedium: 'medium',
dropdownSmall: 'small',
dropdownMini: 'mini',
dropdown1: 'home page',
dropdown2: 'Personal Center',
dropdown3: '404',
dropdown4: '401',
dropdown5: 'Log out',
dropdown6: 'Code warehouse',
searchPlaceholder: 'Menu search: support Chinese, routing path',
newTitle: 'notice',
newBtn: 'All read',
newGo: 'Go to the notification center',
newDesc: 'No notice',
logOutTitle: 'Tips',
logOutMessage: 'This operation will log out. Do you want to continue?',
logOutConfirm: 'determine',
logOutCancel: 'cancel',
logOutExit: 'Exiting',
logOutSuccess: 'Exit successfully!',
},
tagsView: {
refresh: 'refresh',
close: 'close',
closeOther: 'closeOther',
closeAll: 'closeAll',
fullscreen: 'fullscreen',
},
notFound: {
foundTitle: 'Wrong address input, please re-enter the address~',
foundMsg: 'You can check the web address first, and then re-enter or give us feedback.',
foundBtn: 'Back to home page',
},
noAccess: {
accessTitle: 'You are not authorized to operate~',
accessMsg: 'Contact information: add QQ group discussion 665452019',
accessBtn: 'Reauthorization',
},
layout: {
configTitle: 'Layout configuration',
oneTitle: 'Global Themes',
twoTitle: 'Menu / top bar',
twoTopBar: 'Top bar background',
twoMenuBar: 'Menu background',
twoColumnsMenuBar: 'Column menu background',
twoTopBarColor: 'Top bar default font color',
twoMenuBarColor: 'Menu default font color',
twoColumnsMenuBarColor: 'Default font color bar menu',
twoIsTopBarColorGradual: 'Top bar gradient',
twoIsMenuBarColorGradual: 'Menu gradient',
twoIsMenuBarColorHighlight: 'Menu font highlight',
threeTitle: 'Interface settings',
threeIsCollapse: 'Menu horizontal collapse',
threeIsUniqueOpened: 'Menu accordion',
threeIsFixedHeader: 'Fixed header',
threeIsClassicSplitMenu: 'Classic layout split menu',
threeIsLockScreen: 'Open the lock screen',
threeLockScreenTime: 'screen locking(s/s)',
fourTitle: 'Interface display',
fourIsShowLogo: 'Sidebar logo',
fourIsBreadcrumb: 'Open breadcrumb',
fourIsBreadcrumbIcon: 'Open breadcrumb icon',
fourIsTagsview: 'Open tagsview',
fourIsTagsviewIcon: 'Open tagsview Icon',
fourIsCacheTagsView: 'Enable tagsview cache',
fourIsSortableTagsView: 'Enable tagsview drag',
fourIsFooter: 'Open footer',
fourIsGrayscale: 'Grey model',
fourIsInvert: 'Color weak mode',
fourIsDark: 'Dark Mode',
fourIsWartermark: 'Turn on watermark',
fourWartermarkText: 'Watermark copy',
fiveTitle: 'Other settings',
fiveTagsStyle: 'Tagsview style',
fiveAnimation: 'page animation',
fiveColumnsAsideStyle: 'Column style',
fiveColumnsAsideLayout: 'Column layout',
sixTitle: 'Layout switch',
sixDefaults: 'One',
sixClassic: 'Two',
sixTransverse: 'Three',
sixColumns: 'Four',
tipText: 'Click the button below to copy the layout configuration to `/src/store/modules/themeConfig.js` It has been modified in.',
copyText: 'replication configuration',
resetText: 'restore default',
copyTextSuccess: 'Copy succeeded!',
copyTextError: 'Copy failed!',
},
upgrade: {
title: 'New version',
msg: 'The new version is available, please update it now! Dont worry, the update is fast!',
desc: 'Prompt: Update will restore the default configuration',
btnOne: 'Cruel refusal',
btnTwo: 'Update now',
btnTwoLoading: 'Updating',
},
};

164
src/i18n/lang/zh-cn.js Normal file
View File

@@ -0,0 +1,164 @@
// 定义内容
export default {
router: {
home: '首页',
system: '系统设置',
systemMenu: '菜单管理',
systemUser: '用户管理',
limits: '权限管理',
limitsFrontEnd: '前端控制',
limitsFrontEndPage: '页面权限',
limitsFrontEndBtn: '按钮权限',
limitsBackEnd: '后端控制',
limitsBackEndEndPage: '页面权限',
menu: '菜单嵌套',
menu1: '菜单1',
menu11: '菜单11',
menu12: '菜单12',
menu121: '菜单121',
menu122: '菜单122',
menu13: '菜单13',
menu2: '菜单2',
funIndex: '功能',
funTagsView: 'tagsView 操作',
funSignCanvas: '在线签名',
funCountup: 'countup 数字滚动',
funEchartsTree: 'echartsTree 树图',
funSelector: '图标选择器',
funWangEditor: 'wangEditor 编辑器',
funCropper: 'cropper 图片裁剪',
funMindMap: 'G6 思维导图',
funQrcode: 'qrcode 二维码生成',
funEchartsMap: '地理坐标/地图',
funPrintJs: '页面打印',
funClipboard: '复制剪切',
funScreenShort: 'web端自定义截屏',
pagesIndex: '页面',
pagesFiltering: '过滤筛选组件',
pagesFilteringDetails: '过滤筛选组件详情',
pagesFilteringDetails1: '过滤筛选组件详情111',
pagesIocnfont: 'iconfont 字体图标',
pagesElement: 'element 字体图标',
pagesAwesome: 'awesome 字体图标',
pagesCityLinkage: '城市多级联动',
pagesFormAdapt: '表单自适应',
pagesListAdapt: '列表自适应',
pagesWaterfall: '瀑布屏',
pagesSteps: '步骤条',
chartIndex: '大数据图表',
personal: '个人中心',
tools: '工具类集合',
layoutLinkView: '外链',
layoutIfameView: '内嵌 iframe',
},
staticRoutes: {
signIn: '登录',
notFound: '找不到此页面',
noPower: '没有权限',
},
user: {
title0: '组件大小',
title1: '语言切换',
title2: '菜单搜索',
title3: '布局配置',
title4: '消息',
title5: '开全屏',
title6: '关全屏',
dropdownDefault: '默认',
dropdownMedium: '中等',
dropdownSmall: '小型',
dropdownMini: '超小',
dropdown1: '首页',
dropdown2: '个人中心',
dropdown3: '404',
dropdown4: '401',
dropdown5: '退出登录',
dropdown6: '代码仓库',
searchPlaceholder: '菜单搜索:支持中文、路由路径',
newTitle: '通知',
newBtn: '全部已读',
newGo: '前往通知中心',
newDesc: '暂无通知',
logOutTitle: '提示',
logOutMessage: '此操作将退出登录, 是否继续?',
logOutConfirm: '确定',
logOutCancel: '取消',
logOutExit: '退出中',
logOutSuccess: '安全退出成功!',
},
tagsView: {
refresh: '刷新',
close: '关闭',
closeOther: '关闭其它',
closeAll: '全部关闭',
fullscreen: '当前页全屏',
},
notFound: {
foundTitle: '地址输入错误,请重新输入地址~',
foundMsg: '您可以先检查网址,然后重新输入或给我们反馈问题。',
foundBtn: '返回首页',
},
noAccess: {
accessTitle: '您未被授权,没有操作权限~',
accessMsg: '联系方式加QQ群探讨 665452019',
accessBtn: '重新授权',
},
layout: {
configTitle: '布局配置',
oneTitle: '全局主题',
twoTitle: '菜单 / 顶栏',
twoTopBar: '顶栏背景',
twoMenuBar: '菜单背景',
twoColumnsMenuBar: '分栏菜单背景',
twoTopBarColor: '顶栏默认字体颜色',
twoMenuBarColor: '菜单默认字体颜色',
twoColumnsMenuBarColor: '分栏菜单默认字体颜色',
twoIsTopBarColorGradual: '顶栏背景渐变',
twoIsMenuBarColorGradual: '菜单背景渐变',
twoIsMenuBarColorHighlight: '菜单字体背景高亮',
threeTitle: '界面设置',
threeIsCollapse: '菜单水平折叠',
threeIsUniqueOpened: '菜单手风琴',
threeIsFixedHeader: '固定 Header',
threeIsClassicSplitMenu: '经典布局分割菜单',
threeIsLockScreen: '开启锁屏',
threeLockScreenTime: '自动锁屏(s/秒)',
fourTitle: '界面显示',
fourIsShowLogo: '侧边栏 Logo',
fourIsBreadcrumb: '开启 Breadcrumb',
fourIsBreadcrumbIcon: '开启 Breadcrumb 图标',
fourIsTagsview: '开启 Tagsview',
fourIsTagsviewIcon: '开启 Tagsview 图标',
fourIsCacheTagsView: '开启 TagsView 缓存',
fourIsSortableTagsView: '开启 TagsView 拖拽',
fourIsFooter: '开启 Footer',
fourIsGrayscale: '灰色模式',
fourIsInvert: '色弱模式',
fourIsDark: '深色模式',
fourIsWartermark: '开启水印',
fourWartermarkText: '水印文案',
fiveTitle: '其它设置',
fiveTagsStyle: 'Tagsview 风格',
fiveAnimation: '主页面切换动画',
fiveColumnsAsideStyle: '分栏高亮风格',
fiveColumnsAsideLayout: '分栏布局风格',
sixTitle: '布局切换',
sixDefaults: '默认',
sixClassic: '经典',
sixTransverse: '横向',
sixColumns: '分栏',
tipText: '点击下方按钮,复制布局配置去 `src/store/modules/themeConfig.js` 中修改。',
copyText: '一键复制配置',
resetText: '一键恢复默认',
copyTextSuccess: '复制成功!',
copyTextError: '复制失败!',
},
upgrade: {
title: '新版本升级',
msg: '新版本来啦,马上更新尝鲜吧!不用担心,更新很快的哦!',
desc: '提示:更新会还原默认配置',
btnOne: '残忍拒绝',
btnTwo: '马上更新',
btnTwoLoading: '更新中',
},
};

164
src/i18n/lang/zh-tw.js Normal file
View File

@@ -0,0 +1,164 @@
// 定义内容
export default {
router: {
home: '首頁',
system: '系統設置',
systemMenu: '選單管理',
systemUser: '用戶管理',
limits: '許可權管理',
limitsFrontEnd: '前端控制',
limitsFrontEndPage: '頁面許可權',
limitsFrontEndBtn: '按鈕許可權',
limitsBackEnd: '後端控制',
limitsBackEndEndPage: '頁面許可權',
menu: '選單嵌套',
menu1: '選單1',
menu11: '選單11',
menu12: '選單12',
menu121: '選單121',
menu122: '選單122',
menu13: '選單13',
menu2: '選單2',
funIndex: '功能',
funTagsView: 'tagsView 操作',
funSignCanvas: '線上簽名',
funCountup: 'countup 數位滾動',
funEchartsTree: 'echartsTree 樹圖',
funSelector: '圖標選擇器',
funWangEditor: 'wangEditor 編輯器',
funCropper: 'cropper 圖片裁剪',
funMindMap: 'G6 心智圖',
funQrcode: 'qrcode 二維碼生成',
funEchartsMap: '地理座標/地圖',
funPrintJs: '頁面列印',
funClipboard: '複製剪切',
funScreenShort: '自定義截圖',
pagesIndex: '頁面',
pagesFiltering: '過濾篩選組件',
pagesFilteringDetails: '過濾篩選組件詳情',
pagesFilteringDetails1: '過濾篩選組件詳情111',
pagesIocnfont: 'iconfont 字體圖標',
pagesElement: 'element 字體圖標',
pagesAwesome: 'awesome 字體圖標',
pagesCityLinkage: '都市多級聯動',
pagesFormAdapt: '表單自我調整',
pagesListAdapt: '清單自我調整',
pagesWaterfall: '瀑布屏',
pagesSteps: '步驟條',
chartIndex: '大資料圖表',
personal: '個人中心',
tools: '工具類集合',
layoutLinkView: '外鏈',
layoutIfameView: '内嵌 iframe',
},
staticRoutes: {
signIn: '登入',
notFound: '找不到此頁面',
noPower: '沒有許可權',
},
user: {
title0: '組件大小',
title1: '語言切換',
title2: '選單蒐索',
title3: '佈局配寘',
title4: '消息',
title5: '開全屏',
title6: '關全屏',
dropdownDefault: '默認',
dropdownMedium: '中等',
dropdownSmall: '小型',
dropdownMini: '超小',
dropdown1: '首頁',
dropdown2: '個人中心',
dropdown3: '404',
dropdown4: '401',
dropdown5: '登出',
dropdown6: '程式碼倉庫',
searchPlaceholder: '選單蒐索:支援中文、路由路徑',
newTitle: '通知',
newBtn: '全部已讀',
newGo: '前往通知中心',
newDesc: '暫無通知',
logOutTitle: '提示',
logOutMessage: '此操作將登出,是否繼續?',
logOutConfirm: '確定',
logOutCancel: '取消',
logOutExit: '退出中',
logOutSuccess: '安全退出成功!',
},
tagsView: {
refresh: '重繪',
close: '關閉',
closeOther: '關閉其它',
closeAll: '全部關閉',
fullscreen: '當前頁全屏',
},
notFound: {
foundTitle: '地址輸入錯誤,請重新輸入地址~',
foundMsg: '您可以先檢查網址,然後重新輸入或給我們迴響問題。',
foundBtn: '返回首頁',
},
noAccess: {
accessTitle: '您未被授權,沒有操作許可權~',
accessMsg: '聯繫方式加QQ群探討665452019',
accessBtn: '重新授權',
},
layout: {
configTitle: '佈局配寘',
oneTitle: '全域主題',
twoTitle: '選單 / 頂欄',
twoTopBar: '頂欄背景',
twoMenuBar: '選單背景',
twoColumnsMenuBar: '分欄選單背景',
twoTopBarColor: '頂欄默認字體顏色',
twoMenuBarColor: '選單默認字體顏色',
twoColumnsMenuBarColor: '分欄選單默認字體顏色',
twoIsTopBarColorGradual: '頂欄背景漸變',
twoIsMenuBarColorGradual: '選單背景漸變',
twoIsMenuBarColorHighlight: '選單字體背景高亮',
threeTitle: '介面設定',
threeIsCollapse: '選單水准折疊',
threeIsUniqueOpened: '選單手風琴',
threeIsFixedHeader: '固定 Header',
threeIsClassicSplitMenu: '經典佈局分割選單',
threeIsLockScreen: '開啟鎖屏',
threeLockScreenTime: '自動鎖屏(s/秒)',
fourTitle: '介面顯示',
fourIsShowLogo: '側邊欄 Logo',
fourIsBreadcrumb: '開啟 Breadcrumb',
fourIsBreadcrumbIcon: '開啟 Breadcrumb 圖標',
fourIsTagsview: '開啟 Tagsview',
fourIsTagsviewIcon: '開啟 Tagsview 圖標',
fourIsCacheTagsView: '開啟 TagsView 緩存',
fourIsSortableTagsView: '開啟 TagsView 拖拽',
fourIsFooter: '開啟 Footer',
fourIsGrayscale: '灰色模式',
fourIsInvert: '色弱模式',
fourIsDark: '深色模式',
fourIsWartermark: '開啟浮水印',
fourWartermarkText: '浮水印文案',
fiveTitle: '其它設定',
fiveTagsStyle: 'Tagsview 風格',
fiveAnimation: '主頁面切換動畫',
fiveColumnsAsideStyle: '分欄高亮風格',
fiveColumnsAsideLayout: '分欄佈局風格',
sixTitle: '佈局切換',
sixDefaults: '默認',
sixClassic: '經典',
sixTransverse: '橫向',
sixColumns: '分欄',
tipText: '點擊下方按鈕,複製佈局配寘去`src/store/modules/themeConfig.js`中修改。',
copyText: '一鍵複製配寘',
resetText: '一鍵恢復默認',
copyTextSuccess: '複製成功!',
copyTextError: '複製失敗!',
},
upgrade: {
title: '新版本陞級',
msg: '新版本來啦,馬上更新嘗鮮吧! 不用擔心,更新很快的哦!',
desc: '提示:更新會還原默認配寘',
btnOne: '殘忍拒絕',
btnTwo: '馬上更新',
btnTwoLoading: '更新中',
},
};

14
src/i18n/pages/home/en.js Normal file
View File

@@ -0,0 +1,14 @@
// 定义内容
export default {
card: {
title1: 'My desk',
title2: 'Message notification',
title3: 'more',
title4: 'Marketing recommendation',
title5: 'more',
title6: 'Inventory operations',
title7: 'Performance',
title8: 'Out of stock monitoring',
title9: 'Performance overtime warning',
},
};

View File

@@ -0,0 +1,14 @@
// 定义内容
export default {
card: {
title1: '我的工作台',
title2: '消息通知',
title3: '更多',
title4: '营销推荐',
title5: '更多',
title6: '库存作业',
title7: '履约情况',
title8: '缺货监控',
title9: '履约超时预警',
},
};

View File

@@ -0,0 +1,14 @@
// 定义内容
export default {
card: {
title1: '我的工作臺',
title2: '消息通知',
title3: '更多',
title4: '行銷推薦',
title5: '更多',
title6: '庫存工作',
title7: '履約情况',
title8: '缺貨監控',
title9: '履約超時預警',
},
};

View File

@@ -0,0 +1,18 @@
// 定义内容
export default {
login: {
placeholder1: 'The user name admin or not is test',
placeholder2: 'Password: 123456',
placeholder3: 'Please enter the verification code',
btnText: 'Sign in',
link: {
one1: 'Third party login',
one2: 'Links',
},
signInText: 'welcome back!',
copyright: {
one5: 'Copyright: Shenzhen XXX Software Technology Co., Ltd',
two6: 'Copyright: Shenzhen XXX software technology Guangdong ICP preparation no.05010000',
},
},
};

View File

@@ -0,0 +1,18 @@
// 定义内容
export default {
login: {
placeholder1: '用户名 admin 或不输均为 test',
placeholder2: '密码123456',
placeholder3: '请输入验证码',
btnText: '登 录',
link: {
one1: '第三方登录',
one2: '友情链接',
},
signInText: '欢迎回来!',
copyright: {
one5: '版权所有深圳市xxx软件科技有限公司',
two6: 'Copyright: Shenzhen XXX Software Technology 粤ICP备05010000号',
},
},
};

View File

@@ -0,0 +1,18 @@
// 定义内容
export default {
login: {
placeholder1: '用戶名admin或不輸均為test',
placeholder2: '密碼123456',
placeholder3: '請輸入驗證碼',
btnText: '登 录',
link: {
one1: '協力廠商登入',
one2: '友情連結',
},
signInText: '歡迎回來!',
copyright: {
one5: '版權所有深圳市xxx軟件科技有限公司',
two6: 'Copyright: Shenzhen XXX Software Technology 粵ICP備05010000號',
},
},
};

View File

@@ -0,0 +1,106 @@
<template>
<el-aside class="layout-aside" :class="setCollapseWidth" v-if="clientWidth > 1000">
<Logo v-if="setShowLogo" />
<el-scrollbar class="flex-auto" ref="layoutAsideRef">
<Vertical :menuList="menuList" :class="setCollapseWidth" />
</el-scrollbar>
</el-aside>
<el-drawer :visible.sync="getThemeConfig.isCollapse" :with-header="false" direction="ltr" size="220px" v-else>
<el-aside class="layout-aside w100 h100">
<Logo v-if="setShowLogo" />
<el-scrollbar class="flex-auto" ref="layoutAsideRef">
<Vertical :menuList="menuList" />
</el-scrollbar>
</el-aside>
</el-drawer>
</template>
<script>
import Vertical from '@/layout/navMenu/vertical.vue';
import Logo from '@/layout/logo/index.vue';
export default {
name: 'layoutAside',
components: { Vertical, Logo },
data() {
return {
menuList: [],
clientWidth: '',
};
},
computed: {
// 设置左侧菜单的具体宽度
setCollapseWidth() {
let { layout, isCollapse } = this.$store.state.themeConfig.themeConfig;
let asideBrColor = '';
layout === 'classic' || layout === 'columns' ? (asideBrColor = 'layout-el-aside-br-color') : '';
if (layout === 'columns') {
// 分栏布局,菜单收起时宽度给 1px
if (isCollapse) {
return ['layout-aside-width1', asideBrColor];
} else {
return ['layout-aside-width-default', asideBrColor];
}
} else {
// 其它布局给 64px
if (isCollapse) {
return ['layout-aside-width64', asideBrColor];
} else {
return ['layout-aside-width-default', asideBrColor];
}
}
},
// 设置 logo 是否显示
setShowLogo() {
let { layout, isShowLogo } = this.$store.state.themeConfig.themeConfig;
return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
},
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
},
created() {
this.initMenuFixed(document.body.clientWidth);
this.setFilterRoutes();
this.bus.$on('setSendColumnsChildren', (res) => {
this.menuList = res.children;
});
this.bus.$on('layoutMobileResize', (res) => {
this.initMenuFixed(res.clientWidth);
});
// 菜单滚动条监听
this.bus.$on('updateElScrollBar', () => {
setTimeout(() => {
this.$refs.layoutAsideRef.update();
}, 300);
});
},
methods: {
// 设置/过滤路由(非静态路由/是否显示在菜单中)
setFilterRoutes() {
if (this.$store.state.themeConfig.themeConfig.layout === 'columns') return false;
this.menuList = this.filterRoutesFun(this.$store.state.routesList.routesList);
},
// 设置/过滤路由 递归函数
filterRoutesFun(arr) {
return arr
.filter((item) => !item.meta.isHide)
.map((item) => {
item = Object.assign({}, item);
if (item.children) item.children = this.filterRoutesFun(item.children);
return item;
});
},
// 设置菜单导航是否固定(移动端)
initMenuFixed(clientWidth) {
this.clientWidth = clientWidth;
},
},
// 页面销毁时
destroyed() {
// 取消菜单滚动条监听
this.bus.$off('updateElScrollBar', () => {});
},
};
</script>

View File

@@ -0,0 +1,223 @@
<template>
<div class="layout-columns-aside">
<el-scrollbar>
<ul>
<li
v-for="(v, k) in columnsAsideList"
:key="k"
@click="onColumnsAsideMenuClick(v, k)"
ref="columnsAsideOffsetTopRefs"
:class="{ 'layout-columns-active': liIndex === k }"
:title="$t(v.meta.title)"
>
<div :class="setColumnsAsidelayout" v-if="!v.meta.isLink || (v.meta.isLink && v.meta.isIframe)">
<i :class="v.meta.icon"></i>
<div class="font12">
{{
$t(v.meta.title) && $t(v.meta.title).length >= 4
? $t(v.meta.title).substr(0, setColumnsAsidelayout === 'columns-vertical' ? 4 : 3)
: $t(v.meta.title)
}}
</div>
</div>
<div :class="setColumnsAsidelayout" v-else>
<a :href="v.meta.isLink" target="_blank">
<i :class="v.meta.icon"></i>
<div class="font12">
{{
$t(v.meta.title) && $t(v.meta.title).length >= 4
? $t(v.meta.title).substr(0, setColumnsAsidelayout === 'columns-vertical' ? 4 : 3)
: $t(v.meta.title)
}}
</div>
</a>
</div>
</li>
<div ref="columnsAsideActiveRef" :class="setColumnsAsideStyle"></div>
</ul>
</el-scrollbar>
</div>
</template>
<script>
export default {
name: 'layoutColumnsAside',
data() {
return {
columnsAsideList: [],
liIndex: 0,
difference: 0,
routeSplit: [],
};
},
computed: {
// 设置分栏高亮风格
setColumnsAsideStyle() {
return this.$store.state.themeConfig.themeConfig.columnsAsideStyle;
},
// 设置分栏布局风格
setColumnsAsidelayout() {
return this.$store.state.themeConfig.themeConfig.columnsAsideLayout;
},
},
mounted() {
this.setFilterRoutes();
},
methods: {
// 设置菜单高亮位置移动
setColumnsAsideMove(k) {
const els = this.$refs.columnsAsideOffsetTopRefs;
this.liIndex = k;
this.$refs.columnsAsideActiveRef.style.top = `${els[k].offsetTop + this.difference}px`;
},
// 菜单高亮点击事件
onColumnsAsideMenuClick(v, k) {
this.setColumnsAsideMove(k);
let { path, redirect } = v;
if (redirect) this.$router.push(redirect);
else this.$router.push(path);
},
// 设置高亮动态位置
onColumnsAsideDown(k) {
this.$nextTick(() => {
this.setColumnsAsideMove(k);
});
},
// 设置/过滤路由(非静态路由/是否显示在菜单中)
setFilterRoutes() {
if (this.$store.state.routesList.routesList.length <= 0) return false;
this.columnsAsideList = this.filterRoutesFun(this.$store.state.routesList.routesList);
const resData = this.setSendChildren(this.$route.path);
if (Object.keys(resData).length <= 0) return false;
this.onColumnsAsideDown(resData.item[0].k);
this.bus.$emit('setSendColumnsChildren', resData);
},
// 传送当前子级数据到菜单中
setSendChildren(path) {
const currentPathSplit = path.split('/');
let currentData = {};
this.columnsAsideList.map((v, k) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
},
// 路由过滤递归函数
filterRoutesFun(arr) {
return arr
.filter((item) => !item.meta.isHide)
.map((item) => {
item = Object.assign({}, item);
if (item.children) item.children = this.filterRoutesFun(item.children);
return item;
});
},
// tagsView 点击时,根据路由查找下标 columnsAsideList实现左侧菜单高亮
setColumnsMenuHighlight(path) {
this.routeSplit = path.split('/');
this.routeSplit.shift();
const routeFirst = `/${this.routeSplit[0]}`;
const currentSplitRoute = this.columnsAsideList.find((v) => v.path === routeFirst);
if (!currentSplitRoute) return false;
// 延迟拿值,防止取不到
setTimeout(() => {
this.onColumnsAsideDown(currentSplitRoute.k);
}, 0);
},
},
watch: {
// 监听 vuex 数据变化
'$store.state': {
handler(val) {
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (this.difference = 3) : (this.difference = 0);
if (val.routesList.routesList.length === this.columnsAsideList.length) return false;
this.setFilterRoutes();
},
deep: true,
},
// 监听路由的变化
$route: {
handler(to) {
this.setColumnsMenuHighlight(to.path);
this.bus.$emit('setSendColumnsChildren', this.setSendChildren(to.path));
},
deep: true,
},
},
};
</script>
<style scoped lang="scss">
.layout-columns-aside {
width: 70px;
height: 100%;
background: var(--prev-bg-columnsMenuBar);
ul {
position: relative;
li {
color: var(--prev-bg-columnsMenuBarColor);
width: 100%;
height: 50px;
text-align: center;
display: flex;
cursor: pointer;
position: relative;
z-index: 1;
.columns-vertical {
margin: auto;
.columns-vertical-title {
padding-top: 1px;
}
}
.columns-horizontal {
display: flex;
height: 50px;
width: 100%;
align-items: center;
padding: 0 5px;
i {
margin-right: 3px;
}
a {
display: flex;
.columns-horizontal-title {
padding-top: 1px;
}
}
}
a {
text-decoration: none;
color: var(--prev-bg-columnsMenuBarColor);
}
}
.layout-columns-active {
color: var(--prev-color-text-white);
transition: 0.3s ease-in-out;
}
.columns-round {
background: var(--prev-color-primary);
color: var(--prev-color-text-white);
position: absolute;
left: 50%;
top: 2px;
height: 50px;
width: 65px;
transform: translateX(-50%);
z-index: 0;
transition: 0.3s ease-in-out;
border-radius: 5px;
}
.columns-card {
@extend .columns-round;
top: 0;
height: 50px;
width: 100%;
border-radius: 0;
}
}
}
</style>

View File

@@ -0,0 +1,24 @@
<template>
<el-header class="layout-header" :height="setHeaderHeight">
<NavBarsIndex />
</el-header>
</template>
<script>
import NavBarsIndex from '@/layout/navBars/index.vue';
export default {
name: 'layoutHeader',
components: { NavBarsIndex },
data() {
return {};
},
computed: {
// 设置顶部 header 的具体高度
setHeaderHeight() {
let { isTagsview, layout } = this.$store.state.themeConfig.themeConfig;
if (isTagsview && layout !== 'classic') return '84px';
else return '50px';
},
},
};
</script>

View File

@@ -0,0 +1,93 @@
<template>
<el-main class="layout-main">
<el-scrollbar
class="layout-scrollbar"
ref="layoutScrollbarRef"
v-show="!currentRouteMeta.isLink && !currentRouteMeta.isIframe"
:style="{ minHeight: `calc(100vh - ${headerHeight}` }"
>
<LayoutParentView />
<Footers v-if="getThemeConfig.isFooter" />
</el-scrollbar>
<Links
:style="{ height: `calc(100vh - ${headerHeight}` }"
:meta="currentRouteMeta"
v-if="currentRouteMeta.isLink && !currentRouteMeta.isIframe"
/>
<Iframes
:style="{ height: `calc(100vh - ${headerHeight}` }"
:meta="currentRouteMeta"
v-if="currentRouteMeta.isLink && currentRouteMeta.isIframe && isShowLink"
@getCurrentRouteMeta="onGetCurrentRouteMeta"
/>
</el-main>
</template>
<script>
import LayoutParentView from '@/layout/routerView/parent.vue';
import Footers from '@/layout/footer/index.vue';
import Links from '@/layout/routerView/link.vue';
import Iframes from '@/layout/routerView/iframes.vue';
export default {
name: 'layoutMain',
components: { LayoutParentView, Footers, Links, Iframes },
data() {
return {
headerHeight: '',
currentRouteMeta: {},
isShowLink: false,
};
},
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
},
mounted() {
this.initHeaderHeight();
this.initCurrentRouteMeta(this.$route.meta);
},
methods: {
// 初始化当前路由 meta 信息
initCurrentRouteMeta(meta) {
this.isShowLink = false;
this.currentRouteMeta = meta;
setTimeout(() => {
this.isShowLink = true;
}, 100);
},
// 设置 main 的高度
initHeaderHeight() {
let { isTagsview } = this.$store.state.themeConfig.themeConfig;
if (isTagsview) return (this.headerHeight = `84px`);
else return (this.headerHeight = `50px`);
},
// 子组件触发更新
onGetCurrentRouteMeta() {
this.initCurrentRouteMeta(this.$route.meta);
},
},
watch: {
// 监听 vuex 数据变化
'$store.state.themeConfig.themeConfig': {
handler(val) {
this.headerHeight = val.isTagsview ? '84px' : '50px';
if (val.isFixedHeaderChange !== val.isFixedHeader) {
if (!this.$refs.layoutScrollbarRef) return false;
this.$refs.layoutScrollbarRef.update();
}
},
deep: true,
},
// 监听路由的变化
$route: {
handler(to) {
this.initCurrentRouteMeta(to.meta);
this.$refs.layoutScrollbarRef.wrap.scrollTop = 0;
},
deep: true,
},
},
};
</script>

View File

@@ -0,0 +1,29 @@
<template>
<div class="layout-footer mt15">
<div class="layout-footer-warp">
<div>vue-prev-adminMade by lyt with </div>
<div class="mt5">{{ $t('message.login.copyright.one5') }}</div>
</div>
</div>
</template>
<script>
export default {
name: 'layoutFooter',
data() {
return {};
},
};
</script>
<style scoped lang="scss">
.layout-footer {
width: 100%;
display: flex;
&-warp {
margin: auto;
color: var(--prev-color-text-secondary);
text-align: center;
}
}
</style>

51
src/layout/index.vue Normal file
View File

@@ -0,0 +1,51 @@
<template>
<Defaults v-if="getThemeConfig.layout === 'defaults'" />
<Classic v-else-if="getThemeConfig.layout === 'classic'" />
<Transverse v-else-if="getThemeConfig.layout === 'transverse'" />
<Columns v-else-if="getThemeConfig.layout === 'columns'" />
</template>
<script>
import { Local } from '@/utils/storage.js';
export default {
name: 'layout',
components: {
Defaults: () => import('@/layout/main/defaults.vue'),
Classic: () => import('@/layout/main/classic.vue'),
Transverse: () => import('@/layout/main/transverse.vue'),
Columns: () => import('@/layout/main/columns.vue'),
},
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
},
created() {
this.onLayoutResize();
window.addEventListener('resize', this.onLayoutResize);
},
methods: {
// 窗口大小改变时(适配移动端)
onLayoutResize() {
if (!Local.get('oldLayout')) Local.set('oldLayout', this.$store.state.themeConfig.themeConfig.layout);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) {
this.$store.state.themeConfig.themeConfig.isCollapse = false;
this.bus.$emit('layoutMobileResize', {
layout: 'defaults',
clientWidth,
});
} else {
this.bus.$emit('layoutMobileResize', {
layout: Local.get('oldLayout') ? Local.get('oldLayout') : this.$store.state.themeConfig.themeConfig.layout,
clientWidth,
});
}
},
},
distroyed() {
window.removeEventListener('resize', this.onLayoutResize);
},
};
</script>

70
src/layout/logo/index.vue Normal file
View File

@@ -0,0 +1,70 @@
<template>
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/vue2/logo-mini.svg" class="layout-logo-medium-img" />
<span>{{ getThemeConfig.globalTitle }}</span>
</div>
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
<img src="https://gitee.com/lyt-top/vue-next-admin-images/raw/master/vue2/logo-mini.svg" class="layout-logo-size-img" />
</div>
</template>
<script>
export default {
name: 'layoutLogo',
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
// 设置 logo 是否显示
setShowLogo() {
let { isCollapse, layout } = this.$store.state.themeConfig.themeConfig;
return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
},
},
methods: {
// logo 点击实现菜单展开/收起
onThemeConfigChange() {
if (this.$store.state.themeConfig.themeConfig.layout === 'transverse') return false;
this.$store.state.themeConfig.themeConfig.isCollapse = !this.$store.state.themeConfig.themeConfig.isCollapse;
},
},
};
</script>
<style scoped lang="scss">
.layout-logo {
width: 220px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: rgb(0 21 41 / 2%) 0px 1px 4px;
color: var(--prev-color-primary);
font-size: 16px;
cursor: pointer;
animation: logoAnimation 0.3s ease-in-out;
&:hover {
span {
opacity: 0.9;
}
}
&-medium-img {
width: 20px;
margin-right: 5px;
position: relative;
top: 2px;
}
}
.layout-logo-size {
width: 100%;
height: 50px;
display: flex;
cursor: pointer;
&-img {
width: 20px;
margin: auto;
animation: logoAnimation 0.3s ease-in-out;
}
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<el-container class="layout-container flex-center">
<Headers />
<el-container class="layout-mian-height-50">
<Asides />
<div class="flex-center layout-backtop">
<TagsView v-if="getThemeConfig.isTagsview" />
<Mains />
</div>
</el-container>
<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script>
import Asides from '@/layout/component/aside.vue';
import Headers from '@/layout/component/header.vue';
import Mains from '@/layout/component/main.vue';
import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
export default {
name: 'layoutClassic',
components: { Asides, Headers, Mains, TagsView },
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
},
};
</script>

View File

@@ -0,0 +1,33 @@
<template>
<el-container class="layout-container">
<ColumnsAside />
<div class="layout-columns-warp">
<Asides />
<el-container class="flex-center layout-backtop">
<Headers v-if="isFixedHeader" />
<el-scrollbar>
<Headers v-if="!isFixedHeader" />
<Mains />
</el-scrollbar>
</el-container>
</div>
<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script>
import Asides from '@/layout/component/aside.vue';
import Headers from '@/layout/component/header.vue';
import Mains from '@/layout/component/main.vue';
import ColumnsAside from '@/layout/component/columnsAside.vue';
export default {
name: 'layoutColumns',
components: { Asides, Headers, Mains, ColumnsAside },
computed: {
// 是否开启固定 header
isFixedHeader() {
return this.$store.state.themeConfig.themeConfig.isFixedHeader;
},
},
};
</script>

View File

@@ -0,0 +1,41 @@
<template>
<el-container class="layout-container">
<Asides />
<el-container class="flex-center layout-backtop">
<Headers v-if="isFixedHeader" />
<el-scrollbar ref="layoutDefaultsScrollbarRef">
<Headers v-if="!isFixedHeader" />
<Mains />
</el-scrollbar>
</el-container>
<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script>
import Asides from '@/layout/component/aside.vue';
import Headers from '@/layout/component/header.vue';
import Mains from '@/layout/component/main.vue';
export default {
name: 'layoutDefaults',
components: { Asides, Headers, Mains },
data() {
return {};
},
computed: {
// 是否开启固定 header
isFixedHeader() {
return this.$store.state.themeConfig.themeConfig.isFixedHeader;
},
},
watch: {
// 监听路由的变化
$route: {
handler() {
this.$refs.layoutDefaultsScrollbarRef.wrap.scrollTop = 0;
},
deep: true,
},
},
};
</script>

View File

@@ -0,0 +1,16 @@
<template>
<el-container class="layout-container flex-center layout-backtop">
<Headers />
<Mains />
<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script>
import Headers from '@/layout/component/header.vue';
import Mains from '@/layout/component/main.vue';
export default {
name: 'layoutTransverse',
components: { Headers, Mains },
};
</script>

View File

@@ -0,0 +1,139 @@
<template>
<div class="layout-navbars-breadcrumb" :style="{ display: isShowBreadcrumb }">
<i
class="layout-navbars-breadcrumb-icon"
:class="getThemeConfig.isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'"
@click="onThemeConfigChange"
></i>
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
<transition-group name="breadcrumb" mode="out-in">
<el-breadcrumb-item v-for="(v, k) in breadcrumbList" :key="v.path">
<span v-if="k === breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
<i :class="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon"></i>{{ $t(v.meta.title) }}
</span>
<a v-else @click.prevent="onBreadcrumbClick(v)">
<i :class="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon"></i>{{ $t(v.meta.title) }}
</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</div>
</template>
<script>
import { Local } from '@/utils/storage.js';
export default {
name: 'layoutBreadcrumb',
data() {
return {
breadcrumbList: [],
routeSplit: [],
routeSplitFirst: '',
routeSplitIndex: 1,
};
},
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
// 动态设置经典、横向布局不显示
isShowBreadcrumb() {
const { layout, isBreadcrumb } = this.$store.state.themeConfig.themeConfig;
if (layout === 'classic' || layout === 'transverse') {
return 'none';
} else {
return isBreadcrumb ? '' : 'none';
}
},
},
mounted() {
this.initRouteSplit(this.$route.path);
},
methods: {
// breadcrumb 当前项点击时
onBreadcrumbClick(v) {
const { redirect, path } = v;
if (redirect) this.$router.push(redirect);
else this.$router.push(path);
},
// breadcrumb icon 点击菜单展开与收起
onThemeConfigChange() {
this.$store.state.themeConfig.themeConfig.isCollapse = !this.$store.state.themeConfig.themeConfig.isCollapse;
this.setLocalThemeConfig();
},
// 存储布局配置
setLocalThemeConfig() {
Local.remove('themeConfigPrev');
Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
},
// 递归设置 breadcrumb
getBreadcrumbList(arr) {
arr.map((item) => {
this.routeSplit.map((v, k, arrs) => {
if (this.routeSplitFirst === item.path) {
this.routeSplitFirst += `/${arrs[this.routeSplitIndex]}`;
this.breadcrumbList.push(item);
this.routeSplitIndex++;
if (item.children) this.getBreadcrumbList(item.children);
}
});
});
},
// 当前路由分割处理
initRouteSplit(path) {
this.breadcrumbList = [
{
path: '/',
meta: {
title: this.$store.state.routesList.routesList[0].meta.title,
icon: this.$store.state.routesList.routesList[0].meta.icon,
},
},
];
this.routeSplit = path.split('/');
this.routeSplit.shift();
this.routeSplitFirst = `/${this.routeSplit[0]}`;
this.routeSplitIndex = 1;
this.getBreadcrumbList(this.$store.state.routesList.routesList);
},
},
// 监听路由的变化
watch: {
$route: {
handler(newVal) {
this.initRouteSplit(newVal.path);
},
deep: true,
},
},
};
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb {
flex: 1;
height: inherit;
display: flex;
align-items: center;
padding-left: 15px;
.layout-navbars-breadcrumb-icon {
cursor: pointer;
font-size: 18px;
margin-right: 15px;
color: var(--prev-bg-topBarColor);
opacity: 0.8;
&:hover {
opacity: 1;
}
}
.layout-navbars-breadcrumb-span {
opacity: 0.7;
color: var(--prev-bg-topBarColor);
}
.layout-navbars-breadcrumb-iconfont {
font-size: 14px;
margin-right: 5px;
}
}
</style>

View File

@@ -0,0 +1,77 @@
<template>
<div class="layout-navbars-breadcrumb-index">
<Logo v-if="setIsShowLogo" />
<Breadcrumb />
<Horizontal :menuList="menuList" v-if="isLayoutTransverse" />
<User />
</div>
</template>
<script>
import Breadcrumb from '@/layout/navBars/breadcrumb/breadcrumb.vue';
import User from '@/layout/navBars/breadcrumb/user.vue';
import Logo from '@/layout/logo/index.vue';
import Horizontal from '@/layout/navMenu/horizontal.vue';
export default {
name: 'layoutNavBars',
components: { Breadcrumb, User, Logo, Horizontal },
data() {
return {
menuList: [],
};
},
computed: {
// 设置 logo 是否显示
setIsShowLogo() {
let { isShowLogo, layout } = this.$store.state.themeConfig.themeConfig;
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
},
// 设置是否显示横向菜单
isLayoutTransverse() {
let { layout, isClassicSplitMenu } = this.$store.state.themeConfig.themeConfig;
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
},
},
mounted() {
this.setFilterRoutes();
},
methods: {
// 设置路由的过滤
setFilterRoutes() {
this.menuList = this.filterRoutesFun(this.$store.state.routesList.routesList);
},
// 设置路由的过滤递归函数
filterRoutesFun(arr) {
return arr
.filter((item) => !item.meta.isHide)
.map((item) => {
item = Object.assign({}, item);
if (item.children) item.children = this.filterRoutesFun(item.children);
return item;
});
},
},
watch: {
// 监听 vuex 数据变化
'$store.state': {
handler(val) {
if (val.routesList.routesList.length === this.menuList.length) return false;
this.setFilterRoutes();
},
deep: true,
},
},
};
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-index {
height: 50px;
display: flex;
align-items: center;
padding-right: 15px;
overflow: hidden;
background: var(--prev-bg-topBar);
border-bottom: 1px solid var(--prev-border-color-lighter);
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<div class="layout-search-dialog">
<el-dialog :visible.sync="isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
<el-autocomplete
v-model="menuQuery"
:fetch-suggestions="menuSearch"
:placeholder="$t('message.user.searchPlaceholder')"
prefix-icon="el-icon-search"
ref="layoutMenuAutocompleteRef"
@select="onHandleSelect"
@blur="onSearchBlur"
>
<template slot-scope="{ item }">
<div><i :class="item.meta.icon" class="mr10"></i>{{ $t(item.meta.title) }}</div>
</template>
</el-autocomplete>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'layoutBreadcrumbSearch',
data() {
return {
isShowSearch: false,
menuQuery: '',
tagsViewList: [],
};
},
methods: {
// 搜索弹窗打开
openSearch() {
this.menuQuery = '';
this.isShowSearch = true;
this.initTageView();
this.$nextTick(() => {
this.$refs.layoutMenuAutocompleteRef.focus();
});
},
// 搜索弹窗关闭
closeSearch() {
setTimeout(() => {
this.isShowSearch = false;
}, 150);
},
// 菜单搜索数据过滤
menuSearch(queryString, cb) {
let results = queryString ? this.tagsViewList.filter(this.createFilter(queryString)) : this.tagsViewList;
cb(results);
},
// 菜单搜索过滤
createFilter(queryString) {
return (restaurant) => {
return (
restaurant.path.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
restaurant.meta.title.toLowerCase().indexOf(queryString.toLowerCase()) > -1 ||
this.$t(restaurant.meta.title).toLowerCase().indexOf(queryString.toLowerCase()) > -1
);
};
},
// 初始化菜单数据
initTageView() {
if (this.tagsViewList.length > 0) return false;
this.$store.state.tagsViewRoutes.tagsViewRoutes.map((v) => {
if (!v.meta.isHide) this.tagsViewList.push({ ...v });
});
},
// 当前菜单选中时
onHandleSelect(item) {
let { path, redirect } = item;
if (item.meta.isLink && !item.meta.isIframe) window.open(item.meta.isLink);
else if (redirect) this.$router.push(redirect);
else this.$router.push(path);
this.closeSearch();
},
// input 失去焦点时
onSearchBlur() {
this.closeSearch();
},
},
};
</script>
<style scoped lang="scss">
.layout-search-dialog {
::v-deep .el-dialog {
box-shadow: unset !important;
border-radius: 0 !important;
background: rgba(0, 0, 0, 0.5);
}
::v-deep .el-autocomplete {
width: 560px;
position: absolute;
top: 100px;
left: 50%;
transform: translateX(-50%);
}
}
</style>

View File

@@ -0,0 +1,538 @@
<template>
<div class="layout-breadcrumb-seting">
<el-drawer
:title="$t('message.layout.configTitle')"
:visible.sync="getThemeConfig.isDrawer"
direction="rtl"
destroy-on-close
size="240px"
@close="onDrawerClose"
>
<el-scrollbar class="layout-breadcrumb-seting-bar">
<!-- 全局主题 -->
<el-divider content-position="left">{{ $t('message.layout.oneTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">primary</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-color-picker v-model="getThemeConfig.primary" size="small" @change="onColorPickerChange"> </el-color-picker>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsDark') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isIsDark" :width="35" @change="onAddDarkChange"></el-switch>
</div>
</div>
<!-- 界面设置 -->
<el-divider content-position="left">{{ $t('message.layout.threeTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsCollapse') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isCollapse" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsUniqueOpened') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isUniqueOpened" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.threeIsFixedHeader') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isFixedHeader" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<!-- 界面显示 -->
<el-divider content-position="left">{{ $t('message.layout.fourTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsShowLogo') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isShowLogo" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div
class="layout-breadcrumb-seting-bar-flex mt15"
:style="{ opacity: getThemeConfig.layout === 'classic' || getThemeConfig.layout === 'transverse' ? 0.5 : 1 }"
>
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsBreadcrumb') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch
v-model="getThemeConfig.isBreadcrumb"
:disabled="getThemeConfig.layout === 'classic' || getThemeConfig.layout === 'transverse'"
:width="35"
@change="setLocalThemeConfig"
></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsBreadcrumbIcon') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isBreadcrumbIcon" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsTagsview') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isTagsview" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsTagsviewIcon') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isTagsviewIcon" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsCacheTagsView') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isCacheTagsView" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsFooter') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isFooter" :width="35" @change="setLocalThemeConfig"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsGrayscale') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isGrayscale" :width="35" @change="onAddFilterChange('grayscale')"></el-switch>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fourIsInvert') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-switch v-model="getThemeConfig.isInvert" :width="35" @change="onAddFilterChange('invert')"></el-switch>
</div>
</div>
<!-- 其它设置 -->
<el-divider content-position="left">{{ $t('message.layout.fiveTitle') }}</el-divider>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveTagsStyle') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="getThemeConfig.tagsStyle" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
<el-option label="风格1" value="tags-style-one"></el-option>
</el-select>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveAnimation') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="getThemeConfig.animation" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
<el-option label="slide-right" value="slide-right"></el-option>
<el-option label="slide-left" value="slide-left"></el-option>
<el-option label="opacitys" value="opacitys"></el-option>
</el-select>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveColumnsAsideStyle') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="getThemeConfig.columnsAsideStyle" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
<el-option label="圆角" value="columns-round"></el-option>
<el-option label="卡片" value="columns-card"></el-option>
</el-select>
</div>
</div>
<div class="layout-breadcrumb-seting-bar-flex mt15 mb28">
<div class="layout-breadcrumb-seting-bar-flex-label">{{ $t('message.layout.fiveColumnsAsideLayout') }}</div>
<div class="layout-breadcrumb-seting-bar-flex-value">
<el-select v-model="getThemeConfig.columnsAsideLayout" placeholder="请选择" size="mini" style="width: 90px" @change="setLocalThemeConfig">
<el-option label="水平" value="columns-horizontal"></el-option>
<el-option label="垂直" value="columns-vertical"></el-option>
</el-select>
</div>
</div>
<!-- 布局切换 -->
<el-divider content-position="left">{{ $t('message.layout.sixTitle') }}</el-divider>
<div class="layout-drawer-content-flex">
<!-- defaults 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('defaults')">
<section class="el-container el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'defaults' }">
<aside class="el-aside" style="width: 20px"></aside>
<section class="el-container is-vertical">
<header class="el-header" style="height: 10px"></header>
<main class="el-main"></main>
</section>
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'defaults' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">{{ $t('message.layout.sixDefaults') }}</p>
</div>
</div>
</div>
<!-- classic 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('classic')">
<section class="el-container is-vertical el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'classic' }">
<header class="el-header" style="height: 10px"></header>
<section class="el-container">
<aside class="el-aside" style="width: 20px"></aside>
<section class="el-container is-vertical">
<main class="el-main"></main>
</section>
</section>
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'classic' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">{{ $t('message.layout.sixClassic') }}</p>
</div>
</div>
</div>
<!-- transverse 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('transverse')">
<section class="el-container is-vertical el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'transverse' }">
<header class="el-header" style="height: 10px"></header>
<section class="el-container">
<section class="el-container is-vertical">
<main class="el-main"></main>
</section>
</section>
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'transverse' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">{{ $t('message.layout.sixTransverse') }}</p>
</div>
</div>
</div>
<!-- columns 布局 -->
<div class="layout-drawer-content-item" @click="onSetLayout('columns')">
<section class="el-container el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'columns' }">
<aside class="el-aside-dark" style="width: 10px"></aside>
<aside class="el-aside" style="width: 20px"></aside>
<section class="el-container is-vertical">
<header class="el-header" style="height: 10px"></header>
<main class="el-main"></main>
</section>
</section>
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'columns' }">
<div class="layout-tips-box">
<p class="layout-tips-txt">{{ $t('message.layout.sixColumns') }}</p>
</div>
</div>
</div>
</div>
<div class="copy-config">
<el-alert :title="$t('message.layout.tipText')" type="warning" :closable="false"> </el-alert>
<el-button
size="small"
class="copy-config-btn"
icon="el-icon-document-copy"
type="primary"
ref="copyConfigBtnRef"
@click="onCopyConfigClick"
>{{ $t('message.layout.copyText') }}
</el-button>
<el-button size="small" class="copy-config-btn-reset" type="info" icon="el-icon-refresh-right" @click="onResetConfigClick">
{{ $t('message.layout.resetText') }}
</el-button>
</div>
</el-scrollbar>
</el-drawer>
</div>
</template>
<script>
import ClipboardJS from 'clipboard';
import { Local } from '@/utils/storage.js';
import { useChangeColor } from '@/utils/theme.js';
import config from '/package.json';
export default {
name: 'layoutBreadcrumbSeting',
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
},
created() {
// 判断当前布局是否不相同不相同则初始化当前布局的样式防止监听窗口大小改变时布局配置logo、菜单背景等部分布局失效问题
if (!Local.get('frequency')) this.initSetLayoutChange();
Local.set('frequency', 1);
// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
this.bus.$on('layoutMobileResize', (res) => {
if (this.$store.state.themeConfig.themeConfig.layout === res.layout) return false;
this.$store.state.themeConfig.themeConfig.layout = res.layout;
this.$store.state.themeConfig.themeConfig.isDrawer = false;
this.$store.state.themeConfig.themeConfig.isCollapse = false;
this.initSetLayoutChange();
});
},
mounted() {
this.initLayoutConfig();
},
methods: {
// 全局主题
onColorPickerChange() {
if (!this.getThemeConfig.primary) return;
// 颜色加深
document.documentElement.style.setProperty('--prev-color-primary', this.getThemeConfig.primary);
// 颜色变浅
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(
`--prev-color-primary-light-${i}`,
`${useChangeColor().getLightColor(this.getThemeConfig.primary, i / 10)}`
);
}
this.setLocalThemeConfig();
},
// 深色模式
onAddDarkChange() {
const body = document.documentElement;
if (this.getThemeConfig.isIsDark) body.setAttribute('data-theme', 'dark');
else body.setAttribute('data-theme', '');
this.setLocalThemeConfig();
},
// 初始化:刷新页面时,设置了值,直接取缓存中的值进行初始化
initLayoutConfig() {
window.addEventListener('load', () => {
// 默认样式
this.onColorPickerChange();
// 灰色模式
if (this.$store.state.themeConfig.themeConfig.isGrayscale) this.onAddFilterChange('grayscale');
// 色弱模式
if (this.$store.state.themeConfig.themeConfig.isInvert) this.onAddFilterChange('invert');
// 深色模式
if (this.$store.state.themeConfig.themeConfig.isIsDark) this.onAddDarkChange();
// 语言国际化
if (Local.get('themeConfigPrev')) this.$i18n.locale = Local.get('themeConfigPrev').globalI18n;
});
},
// 存储布局配置
setLocalThemeConfig() {
Local.remove('themeConfigPrev');
Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
this.setLocalThemeConfigStyle();
},
// 存储布局配置全局主题样式html根标签
setLocalThemeConfigStyle() {
Local.set('themeConfigStyle', document.documentElement.style.cssText);
},
// 布局配置弹窗打开
openDrawer() {
this.$store.state.themeConfig.themeConfig.isDrawer = true;
},
// 关闭弹窗时,初始化变量
onDrawerClose() {
this.$store.state.themeConfig.themeConfig.isDrawer = false;
this.setLocalThemeConfig();
},
// 灰色模式/色弱模式
onAddFilterChange(attr) {
if (attr === 'grayscale') {
if (this.$store.state.themeConfig.themeConfig.isGrayscale) this.$store.state.themeConfig.themeConfig.isInvert = false;
} else {
if (this.$store.state.themeConfig.themeConfig.isInvert) this.$store.state.themeConfig.themeConfig.isGrayscale = false;
}
const cssAttr =
attr === 'grayscale'
? `grayscale(${this.$store.state.themeConfig.themeConfig.isGrayscale ? 1 : 0})`
: `invert(${this.$store.state.themeConfig.themeConfig.isInvert ? '80%' : '0%'})`;
const appEle = document.body;
appEle.setAttribute('style', `filter: ${cssAttr};`);
this.setLocalThemeConfig();
},
// 布局切换
onSetLayout(layout) {
Local.set('oldLayout', layout);
if (this.$store.state.themeConfig.themeConfig.layout === layout) return false;
this.$store.state.themeConfig.themeConfig.layout = layout;
this.$store.state.themeConfig.themeConfig.isDrawer = false;
this.initSetLayoutChange();
},
// 设置布局切换,重置主题样式
initSetLayoutChange() {
if (this.$store.state.themeConfig.themeConfig.layout === 'classic') {
this.onBgColorPickerChange('menuBar', '#ffffff');
this.onBgColorPickerChange('menuBarColor', '#606266');
this.onBgColorPickerChange('topBar', '#ffffff');
this.onBgColorPickerChange('topBarColor', '#606266');
} else if (this.$store.state.themeConfig.themeConfig.layout === 'transverse') {
this.onBgColorPickerChange('menuBarColor', '#ffffff');
this.onBgColorPickerChange('topBar', '#545c64');
this.onBgColorPickerChange('topBarColor', '#ffffff');
} else if (this.$store.state.themeConfig.themeConfig.layout === 'columns') {
this.onBgColorPickerChange('menuBar', '#ffffff');
this.onBgColorPickerChange('menuBarColor', '#606266');
this.onBgColorPickerChange('topBar', '#ffffff');
this.onBgColorPickerChange('topBarColor', '#606266');
} else {
this.onBgColorPickerChange('menuBar', '#545c64');
this.onBgColorPickerChange('menuBarColor', '#eaeaea');
this.onBgColorPickerChange('topBar', '#ffffff');
this.onBgColorPickerChange('topBarColor', '#606266');
}
},
// 菜单 / 顶栏背景等
onBgColorPickerChange(bg, rgb) {
document.documentElement.style.setProperty(`--prev-bg-${bg}`, rgb);
this.setLocalThemeConfigStyle();
},
// 一键复制配置
onCopyConfigClick() {
this.$store.state.themeConfig.themeConfig.isDrawer = false;
let clipboardJS = new ClipboardJS('.copy-config-btn', {
text: () => JSON.stringify(this.$store.state.themeConfig.themeConfig),
});
clipboardJS.on('success', () => {
this.$message.success('配置复制成功');
this.isDrawer = false;
clipboardJS.destroy();
});
clipboardJS.on('error', () => {
this.$message.error('配置复制失败');
});
},
// 一键恢复默认
onResetConfigClick() {
Local.clear();
window.location.reload();
Local.set('version', config.version);
},
},
};
</script>
<style scoped lang="scss">
.layout-breadcrumb-seting-bar {
height: calc(100vh - 50px);
padding: 0 15px;
::v-deep .el-scrollbar__view {
overflow-x: hidden !important;
}
.layout-breadcrumb-seting-bar-flex {
display: flex;
align-items: center;
&-label {
flex: 1;
color: var(--prev-color-text-primary);
}
}
.layout-drawer-content-flex {
overflow: hidden;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
margin: 0 -5px;
.layout-drawer-content-item {
width: 50%;
height: 70px;
cursor: pointer;
border: 1px solid transparent;
position: relative;
padding: 5px;
.el-container {
height: 100%;
.el-aside-dark {
background-color: var(--prev-color-seting-header);
}
.el-aside {
background-color: var(--prev-color-seting-aside);
}
.el-header {
background-color: var(--prev-color-seting-header);
}
.el-main {
background-color: var(--prev-color-seting-main);
}
}
.el-circular {
border-radius: 2px;
overflow: hidden;
border: 1px solid transparent;
transition: all 0.3s ease-in-out;
}
.drawer-layout-active {
border: 1px solid;
border-color: var(--prev-color-primary);
}
.layout-tips-warp,
.layout-tips-warp-active {
transition: all 0.3s ease-in-out;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border: 1px solid;
border-color: var(--prev-color-primary-light-5);
border-radius: 100%;
padding: 4px;
.layout-tips-box {
transition: inherit;
width: 30px;
height: 30px;
z-index: 9;
border: 1px solid;
border-color: var(--prev-color-primary-light-5);
border-radius: 100%;
.layout-tips-txt {
transition: inherit;
position: relative;
top: 5px;
font-size: 12px;
line-height: 1;
letter-spacing: 2px;
white-space: nowrap;
color: var(--prev-color-primary-light-5);
text-align: center;
transform: rotate(30deg);
left: -1px;
background-color: var(--prev-color-seting-main);
width: 32px;
height: 17px;
line-height: 17px;
}
}
}
.layout-tips-warp-active {
border: 1px solid;
border-color: var(--prev-color-primary);
.layout-tips-box {
border: 1px solid;
border-color: var(--prev-color-primary);
.layout-tips-txt {
color: var(--prev-color-primary) !important;
background-color: var(--prev-color-seting-main) !important;
}
}
}
&:hover {
.el-circular {
transition: all 0.3s ease-in-out;
border: 1px solid;
border-color: var(--prev-color-primary);
}
.layout-tips-warp {
transition: all 0.3s ease-in-out;
border-color: var(--prev-color-primary);
.layout-tips-box {
transition: inherit;
border-color: var(--prev-color-primary);
.layout-tips-txt {
transition: inherit;
color: var(--prev-color-primary) !important;
background-color: var(--prev-color-seting-main) !important;
}
}
}
}
}
}
.copy-config {
margin: 10px 0;
.copy-config-btn {
width: 100%;
margin-top: 15px;
}
.copy-config-btn-reset {
width: 100%;
margin: 10px 0 0;
}
}
}
</style>

View File

@@ -0,0 +1,265 @@
<template>
<div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onComponentSizeChange">
<div class="layout-navbars-breadcrumb-user-icon">
<i class="iconfont icon-ziti" :title="$t('message.user.title0')"></i>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="" :disabled="disabledSize === ''">{{ $t('message.user.dropdownDefault') }}</el-dropdown-item>
<el-dropdown-item command="medium" :disabled="disabledSize === 'medium'">{{ $t('message.user.dropdownMedium') }}</el-dropdown-item>
<el-dropdown-item command="small" :disabled="disabledSize === 'small'">{{ $t('message.user.dropdownSmall') }}</el-dropdown-item>
<el-dropdown-item command="mini" :disabled="disabledSize === 'mini'">{{ $t('message.user.dropdownMini') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onLanguageChange">
<div class="layout-navbars-breadcrumb-user-icon">
<i class="iconfont" :class="disabledI18n === 'en' ? 'icon-fuhao-yingwen' : 'icon-fuhao-zhongwen'" :title="$t('message.user.title1')"></i>
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="zh-cn" :disabled="disabledI18n === 'zh-cn'">简体中文</el-dropdown-item>
<el-dropdown-item command="en" :disabled="disabledI18n === 'en'">English</el-dropdown-item>
<el-dropdown-item command="zh-tw" :disabled="disabledI18n === 'zh-tw'">繁體中文</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
<i class="el-icon-search" :title="$t('message.user.title2')"></i>
</div>
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
<i class="icon-skin iconfont" :title="$t('message.user.title3')"></i>
</div>
<div class="layout-navbars-breadcrumb-user-icon">
<el-popover placement="bottom" trigger="click" v-model="isShowUserNewsPopover" :width="300" popper-class="el-popover-pupop-user-news">
<el-badge :is-dot="true" @click.stop="isShowUserNewsPopover = !isShowUserNewsPopover" slot="reference">
<i class="el-icon-bell" :title="$t('message.user.title4')"></i>
</el-badge>
<transition name="el-zoom-in-top">
<UserNews v-show="isShowUserNewsPopover" />
</transition>
</el-popover>
</div>
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
<i
class="iconfont"
:title="isScreenfull ? $t('message.user.title6') : $t('message.user.title5')"
:class="!isScreenfull ? 'icon-fullscreen' : 'icon-tuichuquanping'"
></i>
</div>
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onDropdownCommand">
<span class="layout-navbars-breadcrumb-user-link">
<img :src="getUserInfos.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
{{ getUserInfos.userName === '' ? 'test' : getUserInfos.userName }}
<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item>
<el-dropdown-item command="wareHouse">{{ $t('message.user.dropdown6') }}</el-dropdown-item>
<el-dropdown-item command="/personal">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
<el-dropdown-item command="/404">{{ $t('message.user.dropdown3') }}</el-dropdown-item>
<el-dropdown-item command="/401">{{ $t('message.user.dropdown4') }}</el-dropdown-item>
<el-dropdown-item divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<Search ref="searchRef" />
</div>
</template>
<script>
import screenfull from 'screenfull';
import { Session, Local } from '@/utils/storage.js';
import UserNews from '@/layout/navBars/breadcrumb/userNews.vue';
import Search from '@/layout/navBars/breadcrumb/search.vue';
export default {
name: 'layoutBreadcrumbUser',
components: { UserNews, Search },
data() {
return {
isScreenfull: false,
isShowUserNewsPopover: false,
disabledI18n: 'zh-cn',
disabledSize: '',
};
},
computed: {
// 获取用户信息
getUserInfos() {
return this.$store.state.userInfos.userInfos;
},
// 设置弹性盒子布局 flex
layoutUserFlexNum() {
let { layout, isClassicSplitMenu } = this.$store.state.themeConfig.themeConfig;
let num = '';
if (layout === 'defaults' || (layout === 'classic' && !isClassicSplitMenu) || layout === 'columns') num = 1;
else num = null;
return num;
},
},
mounted() {
if (Local.get('themeConfigPrev')) {
this.initI18n();
this.initComponentSize();
}
},
methods: {
// 搜索点击
onSearchClick() {
this.$refs.searchRef.openSearch();
},
// 布局配置点击
onLayoutSetingClick() {
this.bus.$emit('openSetingsDrawer');
},
// 全屏点击
onScreenfullClick() {
if (!screenfull.isEnabled) {
this.$message.warning('暂不不支持全屏');
return false;
}
screenfull.toggle();
screenfull.on('change', () => {
if (screenfull.isFullscreen) this.isScreenfull = true;
else this.isScreenfull = false;
});
// 监听菜单 horizontal.vue 滚动条高度更新
this.bus.$emit('updateElScrollBar');
},
// 组件大小改变
onComponentSizeChange(size) {
Local.remove('themeConfigPrev');
this.$store.state.themeConfig.themeConfig.globalComponentSize = size;
Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
this.$ELEMENT.size = size;
this.initComponentSize();
window.location.reload();
},
// 语言切换
onLanguageChange(lang) {
Local.remove('themeConfigPrev');
this.$store.state.themeConfig.themeConfig.globalI18n = lang;
Local.set('themeConfigPrev', this.$store.state.themeConfig.themeConfig);
this.$i18n.locale = lang;
this.initI18n();
},
// 初始化言语国际化
initI18n() {
switch (Local.get('themeConfigPrev').globalI18n) {
case 'zh-cn':
this.disabledI18n = 'zh-cn';
break;
case 'en':
this.disabledI18n = 'en';
break;
case 'zh-tw':
this.disabledI18n = 'zh-tw';
break;
}
},
// 初始化全局组件大小
initComponentSize() {
switch (Local.get('themeConfigPrev').globalComponentSize) {
case '':
this.disabledSize = '';
break;
case 'medium':
this.disabledSize = 'medium';
break;
case 'small':
this.disabledSize = 'small';
break;
case 'mini':
this.disabledSize = 'mini';
break;
}
},
// `dropdown 下拉菜单` 当前项点击
onDropdownCommand(path) {
if (path === 'logOut') {
setTimeout(() => {
this.$msgbox({
closeOnClickModal: false,
closeOnPressEscape: false,
title: this.$t('message.user.logOutTitle'),
message: this.$t('message.user.logOutMessage'),
showCancelButton: true,
confirmButtonText: this.$t('message.user.logOutConfirm'),
cancelButtonText: this.$t('message.user.logOutCancel'),
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
instance.confirmButtonLoading = true;
instance.confirmButtonText = this.$t('message.user.logOutExit');
setTimeout(() => {
done();
setTimeout(() => {
instance.confirmButtonLoading = false;
}, 300);
}, 700);
} else {
done();
}
},
})
.then(() => {
// 清除缓存/token等
Session.clear();
// 使用 reload 时,不需要调用 resetRoute() 重置路由
window.location.reload();
})
.catch(() => {});
}, 150);
} else if (path === 'wareHouse') {
window.open('https://gitee.com/lyt-top/vue-next-admin');
} else {
this.$router.push(path);
}
},
},
};
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-user {
display: flex;
align-items: center;
justify-content: flex-end;
&-link {
height: 100%;
display: flex;
align-items: center;
white-space: nowrap;
&-photo {
width: 25px;
height: 25px;
border-radius: 100%;
}
}
&-icon {
padding: 0 10px;
cursor: pointer;
color: var(--prev-bg-topBarColor);
height: 50px;
line-height: 50px;
display: flex;
align-items: center;
&:hover {
background: var(--prev-color-hover);
i {
display: inline-block;
animation: logoAnimation 0.3s ease-in-out;
}
}
}
& ::v-deep .el-dropdown {
color: var(--prev-bg-topBarColor);
}
& ::v-deep .el-badge {
height: 40px;
line-height: 40px;
display: flex;
align-items: center;
}
& ::v-deep .el-badge__content.is-fixed {
top: 12px;
}
}
</style>

View File

@@ -0,0 +1,126 @@
<template>
<div class="layout-navbars-breadcrumb-user-news">
<div class="head-box">
<div class="head-box-title">{{ $t('message.user.newTitle') }}</div>
<div class="head-box-btn" v-if="newsList.length > 0" @click="onAllReadClick">{{ $t('message.user.newBtn') }}</div>
</div>
<div class="content-box">
<template v-if="newsList.length > 0">
<div class="content-box-item" v-for="(v, k) in newsList" :key="k">
<div>{{ v.label }}</div>
<div class="content-box-msg">
{{ v.value }}
</div>
<div class="content-box-time">{{ v.time }}</div>
</div>
</template>
<div class="content-box-empty" v-else>
<div class="content-box-empty-margin">
<i class="el-icon-s-promotion"></i>
<div class="mt15">{{ $t('message.user.newDesc') }}</div>
</div>
</div>
</div>
<div class="foot-box" @click="onGoToGiteeClick" v-if="newsList.length > 0">{{ $t('message.user.newGo') }}</div>
</div>
</template>
<script>
export default {
name: 'layoutBreadcrumbUserNews',
data() {
return {
newsList: [
{
label: '关于版本发布的通知',
value: '基于 vue2.x + element ui正式发布时间2020年11月15日',
time: '2020-11-15',
},
{
label: '关于学习交流的通知',
value: 'QQ群号码 665452019欢迎小伙伴入群学习交流探讨',
time: '2020-11-15',
},
],
};
},
methods: {
// 全部已读点击
onAllReadClick() {
this.newsList = [];
},
// 前往通知中心点击
onGoToGiteeClick() {
window.open('https://gitee.com/lyt-top/vue-next-admin');
},
},
};
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb-user-news {
.head-box {
display: flex;
border-bottom: 1px solid var(--prev-border-color-lighter);
box-sizing: border-box;
color: var(--prev-color-text-primary);
justify-content: space-between;
height: 35px;
align-items: center;
.head-box-btn {
color: var(--prev-color-primary);
font-size: 13px;
cursor: pointer;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
.content-box {
font-size: 13px;
.content-box-item {
padding-top: 12px;
&:last-of-type {
padding-bottom: 12px;
}
.content-box-msg {
color: var(--prev-color-text-secondary);
margin-top: 5px;
margin-bottom: 5px;
}
.content-box-time {
color: var(--prev-color-text-secondary);
}
}
.content-box-empty {
height: 260px;
display: flex;
.content-box-empty-margin {
margin: auto;
text-align: center;
i {
font-size: 60px;
}
}
}
}
.foot-box {
height: 35px;
color: var(--prev-color-primary);
font-size: 13px;
cursor: pointer;
opacity: 0.8;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid var(--prev-border-color-lighter);
&:hover {
opacity: 1;
}
}
::v-deep(.el-empty__description p) {
font-size: 13px;
}
}
</style>

View File

@@ -0,0 +1,34 @@
<template>
<div class="layout-navbars-container">
<BreadcrumbIndex />
<TagsView v-if="setShowTagsView" />
</div>
</template>
<script>
import BreadcrumbIndex from '@/layout/navBars/breadcrumb/index.vue';
import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
export default {
name: 'layoutNavBars',
components: { BreadcrumbIndex, TagsView },
data() {
return {};
},
computed: {
// 设置是否显示 tagsView
setShowTagsView() {
let { layout, isTagsview } = this.$store.state.themeConfig.themeConfig;
return layout !== 'classic' && isTagsview;
},
},
};
</script>
<style scoped lang="scss">
.layout-navbars-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<div>
<transition name="el-zoom-in-center">
<ul
class="el-dropdown-menu el-popper el-dropdown-menu--medium custom-contextmenu"
:style="`top: ${dropdowns.y}px;left: ${dropdowns.x}px;`"
x-placement="bottom-end"
id="contextmenu"
v-show="isShow"
>
<li class="el-dropdown-menu__item" v-for="(v, k) in dropdownList" :key="k" @click="onCurrentContextmenuClick(v.id)">
<template v-if="!v.affix">
<i :class="v.icon"></i>
<span>{{ $t(v.txt) }}</span>
</template>
</li>
<div x-arrow class="popper__arrow" :style="{ left: `${arrowLeft}px` }"></div>
</ul>
</transition>
</div>
</template>
<script>
export default {
name: 'layoutTagsViewContextmenu',
props: {
dropdown: {
type: Object,
},
},
data() {
return {
isShow: false,
dropdownList: [
{ id: 0, txt: 'message.tagsView.refresh', affix: false, icon: 'el-icon-refresh-right' },
{ id: 1, txt: 'message.tagsView.close', affix: false, icon: 'el-icon-close' },
{ id: 2, txt: 'message.tagsView.closeOther', affix: false, icon: 'el-icon-circle-close' },
{ id: 3, txt: 'message.tagsView.closeAll', affix: false, icon: 'el-icon-folder-delete' },
],
path: {},
arrowLeft: 5,
};
},
computed: {
dropdowns() {
// 99 为 `Dropdown 下拉菜单` 的宽度
if (this.dropdown.x + 99 > document.documentElement.clientWidth) {
return {
x: document.documentElement.clientWidth - 99 - 5,
y: this.dropdown.y,
};
} else {
return this.dropdown;
}
},
},
mounted() {
// 监听页面监听进行右键菜单的关闭
document.body.addEventListener('click', this.closeContextmenu);
},
methods: {
// 当前项菜单点击
onCurrentContextmenuClick(id) {
this.$emit('currentContextmenuClick', { id, path: this.path });
},
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
openContextmenu(item) {
this.path = item.path;
item.meta.isAffix ? (this.dropdownList[1].affix = true) : (this.dropdownList[1].affix = false);
this.closeContextmenu();
setTimeout(() => {
this.isShow = true;
}, 80);
},
// 关闭右键菜单
closeContextmenu() {
this.isShow = false;
},
},
destroyed() {
// 页面卸载时,移除右键菜单监听事件
document.body.removeEventListener('click', this.closeContextmenu);
},
// 监听下拉菜单位置
watch: {
dropdown: {
handler({ x }) {
if (x + 99 > document.documentElement.clientWidth) this.arrowLeft = 99 - (document.documentElement.clientWidth - x);
else this.arrowLeft = 10;
},
deep: true,
},
},
};
</script>
<style scoped lang="scss">
.custom-contextmenu {
transform-origin: center top;
z-index: 2190;
position: fixed;
.el-dropdown-menu__item {
font-size: 12px !important;
white-space: nowrap;
i {
font-size: 12px !important;
}
}
}
</style>

View File

@@ -0,0 +1,404 @@
<template>
<div class="layout-navbars-tagsview">
<el-scrollbar ref="scrollbarRef" @wheel.native.prevent="onHandleScroll">
<ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
<li
v-for="(v, k) in tagsViewList"
:key="k"
class="layout-navbars-tagsview-ul-li"
:data-name="v.name"
:class="{ 'is-active': v.path === tagsRoutePath }"
@contextmenu.prevent="onContextmenu(v, $event)"
@click="onTagsClick(v, k)"
ref="tagsRefs"
>
<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont font14" v-if="v.path === tagsRoutePath"></i>
<i
class="layout-navbars-tagsview-ul-li-iconfont font14 is-tagsview-icon"
:class="v.meta.icon"
v-if="v.path !== tagsRoutePath && getThemeConfig.isTagsviewIcon"
></i>
<span>{{ $t(v.meta.title) }}</span>
<i
class="el-icon-refresh-right layout-navbars-tagsview-ul-li-icon ml5"
v-if="v.path === tagsRoutePath"
@click.stop="refreshCurrentTagsView(v.path)"
></i>
<i class="el-icon-close layout-navbars-tagsview-ul-li-icon ml5" v-if="!v.meta.isAffix" @click.stop="closeCurrentTagsView(v.path)"></i>
</li>
</ul>
</el-scrollbar>
<Contextmenu :dropdown="tagsDropdown" ref="tagsContextmenu" @currentContextmenuClick="onCurrentContextmenuClick" />
</div>
</template>
<script>
import Contextmenu from '@/layout/navBars/tagsView/contextmenu';
import { Session } from '@/utils/storage.js';
export default {
name: 'tagsView',
components: { Contextmenu },
data() {
return {
userInfo: {},
tagsViewList: [],
tagsDropdown: {
x: '',
y: '',
},
tagsRefsIndex: 0,
tagsRoutePath: this.$route.path,
tagsViewRoutesList: [],
};
},
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
// 动态设置 tagsView 风格样式
setTagsStyle() {
return this.$store.state.themeConfig.themeConfig.tagsStyle;
},
},
created() {
// 监听非本页面调用 0 刷新当前1 关闭当前2 关闭其它3 关闭全部
this.bus.$on('onCurrentContextmenuClick', (data) => {
this.onCurrentContextmenuClick(data);
});
},
mounted() {
this.getTagsViewRoutes();
},
methods: {
// 获取路由信息
getRoutesList() {
return this.$store.state.routesList.routesList;
},
// 当前的 tagsView 项点击时
onTagsClick(v, k) {
this.tagsRoutePath = v.path;
this.tagsRefsIndex = k;
this.$router.push(v);
},
// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
getTagsRefsIndex(path) {
if (this.tagsViewList.length > 0) {
this.tagsRefsIndex = this.tagsViewList.findIndex((item) => item.path === path);
}
},
// 鼠标滚轮滚动
onHandleScroll(e) {
this.$refs.scrollbarRef.$refs.wrap.scrollLeft += e.wheelDelta / 4;
},
// tagsView 横向滚动
tagsViewmoveToCurrentTag() {
this.$nextTick(() => {
const tagsRefs = this.$refs.tagsRefs;
if (tagsRefs.length <= 0) return false;
// 当前 li 元素
let liDom = tagsRefs[this.tagsRefsIndex];
// 当前 li 元素下标
let liIndex = this.tagsRefsIndex;
// 当前 ul 下 li 元素总长度
let liLength = tagsRefs.length;
// 最前 li
let liFirst = tagsRefs[0];
// 最后 li
let liLast = tagsRefs[tagsRefs.length - 1];
// 当前滚动条的值
let scrollRefs = this.$refs.scrollbarRef.$refs.wrap;
// 当前滚动条滚动宽度
let scrollS = scrollRefs.scrollWidth;
// 当前滚动条偏移宽度
let offsetW = scrollRefs.offsetWidth;
// 当前滚动条偏移距离
let scrollL = scrollRefs.scrollLeft;
// 上一个 tags li dom
let liPrevTag = tagsRefs[this.tagsRefsIndex - 1];
// 下一个 tags li dom
let liNextTag = tagsRefs[this.tagsRefsIndex + 1];
// 上一个 tags li dom 的偏移距离
let beforePrevL = '';
// 下一个 tags li dom 的偏移距离
let afterNextL = '';
if (liDom === liFirst) {
// 头部
scrollRefs.scrollLeft = 0;
} else if (liDom === liLast) {
// 尾部
scrollRefs.scrollLeft = scrollS - offsetW;
} else {
// 非头/尾部
if (liIndex === 0) beforePrevL = liFirst?.offsetLeft - 5;
else beforePrevL = liPrevTag?.offsetLeft - 5;
if (liIndex === liLength) afterNextL = liLast?.offsetLeft + liLast.offsetWidth + 5;
else afterNextL = liNextTag?.offsetLeft + liNextTag.offsetWidth + 5;
if (afterNextL > scrollL + offsetW) {
scrollRefs.scrollLeft = afterNextL - offsetW;
} else if (beforePrevL < scrollL) {
scrollRefs.scrollLeft = beforePrevL;
}
}
// 更新滚动条,防止不出现
this.updateScrollbar();
});
},
// 更新滚动条显示
updateScrollbar() {
this.$refs.scrollbarRef.update();
},
// 递归查找当前路径下的组件信息
filterCurrentMenu(arr, currentPath, callback) {
arr.map((item) => {
if (item.path === currentPath) {
callback(item);
return false;
}
item = Object.assign({}, item);
if (item.children) {
item.children = this.filterCurrentMenu(item.children, currentPath, callback);
}
});
},
// 数组对象去重
duplicate(arr) {
let newobj = {};
arr = arr.reduce((preVal, curVal) => {
newobj[curVal.path] ? '' : (newobj[curVal.path] = preVal.push(curVal));
return preVal;
}, []);
return arr;
},
// 获取 vuex 中的 tagsViewRoutes 列表
getTagsViewRoutes() {
this.tagsRoutePath = this.$route.path;
this.tagsViewList = [];
if (!this.$store.state.themeConfig.themeConfig.isCacheTagsView) Session.remove('tagsViewList');
this.tagsViewRoutesList = this.$store.state.tagsViewRoutes.tagsViewRoutes;
this.initTagsViewList();
},
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
addBrowserSetSession(tagsViewList) {
Session.set('tagsViewList', tagsViewList);
},
// 初始化设置了 tagsView 数据
initTagsViewList() {
if (Session.get('tagsViewList') && this.$store.state.themeConfig.themeConfig.isCacheTagsView) {
this.tagsViewList = Session.get('tagsViewList');
} else {
this.tagsViewRoutesList.map((v) => {
if (v.meta.isAffix && !v.meta.isHide) this.tagsViewList.push({ ...v });
});
this.addTagsView(this.$route.path);
}
// 初始化当前元素(li)的下标
this.getTagsRefsIndex(this.$route.path);
// 添加初始化横向滚动条移动到对应位置
this.tagsViewmoveToCurrentTag();
},
// 添加 tagsView未设置隐藏isHide也添加到在 tagsView 中
addTagsView(path, to) {
if (this.tagsViewList.some((v) => v.path === path)) return false;
const item = this.tagsViewRoutesList.find((v) => v.path === path);
if (item.meta.isLink && !item.meta.isIframe) return false;
item.query = to?.query ? to?.query : this.$route.query;
this.tagsViewList.push({ ...item });
this.addBrowserSetSession(this.tagsViewList);
},
// 右键菜单点击时显示菜单列表
onContextmenu(v, e) {
let { clientX, clientY } = e;
this.tagsDropdown.x = clientX;
this.tagsDropdown.y = clientY;
this.$refs.tagsContextmenu.openContextmenu(v);
},
// 当前项右键菜单点击
onCurrentContextmenuClick(data) {
let { id, path } = data;
let currentTag = this.tagsViewList.find((v) => v.path === path);
switch (id) {
case 0:
this.refreshCurrentTagsView(path);
this.$router.push({ path, query: currentTag.query });
break;
case 1:
this.closeCurrentTagsView(path);
break;
case 2:
this.$router.push({ path, query: currentTag.query });
this.closeOtherTagsView(path);
break;
case 3:
this.closeAllTagsView(path);
break;
}
},
// 1、刷新当前 tagsView
refreshCurrentTagsView(path) {
this.bus.$emit('onTagsViewRefreshRouterView', path);
},
// 2、关闭当前 tagsView当前项 `tags-view` icon 关闭时点击如果是设置了固定的isAffix不可以关闭
closeCurrentTagsView(path) {
this.tagsViewList.map((v, k, arr) => {
if (!v.meta.isAffix) {
if (v.path === path) {
this.tagsViewList.splice(k, 1);
setTimeout(() => {
// 最后一个
if (this.tagsViewList.length === k) this.$router.push({ path: arr[arr.length - 1].path, query: arr[arr.length - 1].query });
// 否则,跳转到下一个
else this.$router.push({ path: arr[k].path, query: arr[k].query });
}, 0);
}
}
});
this.addBrowserSetSession(this.tagsViewList);
},
// 3、关闭其它 tagsView如果是设置了固定的isAffix不进行关闭
closeOtherTagsView(path) {
this.tagsViewList = [];
this.tagsViewRoutesList.map((v) => {
if (v.meta.isAffix && !v.meta.isHide) this.tagsViewList.push({ ...v });
});
this.addTagsView(path);
},
// 4、关闭全部 tagsView如果是设置了固定的isAffix不进行关闭
closeAllTagsView(path) {
this.tagsViewList = [];
this.tagsViewRoutesList.map((v) => {
if (v.meta.isAffix && !v.meta.isHide) {
this.tagsViewList.push({ ...v });
if (this.tagsViewList.some((v) => v.path === path)) this.$router.push({ path, query: this.$route.query });
else this.$router.push({ path: v.path, query: this.$route.query });
}
});
this.addBrowserSetSession(this.tagsViewList);
},
},
watch: {
// 监听路由变化
$route: {
handler(to) {
this.tagsRoutePath = to.path;
this.addTagsView(to.path, to);
this.getTagsRefsIndex(to.path);
this.tagsViewmoveToCurrentTag();
},
deep: true,
},
},
destroyed() {
// 取消非本页面调用监听fun/tagsView
this.bus.$off('onCurrentContextmenuClick');
},
};
</script>
<style scoped lang="scss">
.layout-navbars-tagsview {
flex: 1;
background-color: var(--prev-bg-white);
border-bottom: 1px solid var(--prev-border-color-lighter);
& ::v-deep .is-vertical {
display: none !important;
}
&-ul {
list-style: none;
margin: 0;
padding: 0;
width: 100%;
height: 34px;
display: flex;
align-items: center;
white-space: nowrap;
color: var(--prev-color-text-regular);
font-size: 12px;
padding: 0 15px;
&-li {
height: 26px;
line-height: 26px;
display: flex;
align-items: center;
border: 1px solid var(--prev-border-color-lighter);
padding: 0 12px 0 15px;
margin-right: 5px;
border-radius: 2px;
position: relative;
z-index: 0;
cursor: pointer;
justify-content: space-between;
transition: all 0.3s cubic-bezier(0.2, 1, 0.3, 1);
&::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: var(--prev-color-primary);
z-index: -1;
opacity: 0;
transform: scale3d(0.7, 1, 1);
transition: transform 0.3s, opacity 0.3s;
transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
}
&:hover {
color: var(--prev-color-text-white);
transition: all 0.3s cubic-bezier(0.2, 1, 0.3, 1);
border-color: transparent;
&::before {
opacity: 1;
transform: translate3d(0, 0, 0);
border-radius: 2px;
}
.is-tagsview-icon {
color: var(--prev-color-text-white);
transition: all 0.3s cubic-bezier(0.2, 1, 0.3, 1);
}
}
&-iconfont {
position: relative;
left: -5px;
top: 1px;
color: var(--prev-color-text-white);
}
&-icon {
border-radius: 100%;
position: relative;
height: 14px;
width: 14px;
text-align: center;
line-height: 14px;
top: 1px;
}
.is-tagsview-icon {
color: var(--prev-color-text-regular);
transition: all 0.3s cubic-bezier(0.2, 1, 0.3, 1);
}
}
.is-active {
color: var(--prev-color-text-white);
transition: all 0.3s cubic-bezier(0.2, 1, 0.3, 1);
border-color: transparent;
&::before {
opacity: 1;
transform: translate3d(0, 0, 0);
border-radius: 2px;
}
}
}
& ::-webkit-scrollbar {
display: none !important;
}
// // 风格2
// .tags-style-two {
// }
// // 风格3
// .tags-style-three {
// }
// // 风格4
// .tags-style-four {
// }
}
</style>

View File

@@ -0,0 +1,138 @@
<template>
<div class="el-menu-horizontal-warp">
<el-scrollbar @wheel.native.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
<el-menu router :default-active="defaultActive" background-color="transparent" mode="horizontal" @select="onHorizontalSelect">
<template v-for="val in menuList">
<el-submenu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template slot="title">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<span>{{ $t(val.meta.title) }}</span>
</template>
<SubItem :chil="val.children" />
</el-submenu>
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<template slot="title" v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
{{ $t(val.meta.title) }}
</template>
<template slot="title" v-else>
<a :href="val.meta.isLink" target="_blank">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
{{ $t(val.meta.title) }}
</a>
</template>
</el-menu-item>
</template>
</template>
</el-menu>
</el-scrollbar>
</div>
</template>
<script>
import SubItem from '@/layout/navMenu/subItem.vue';
export default {
name: 'navMenuHorizontal',
components: { SubItem },
props: {
menuList: {
type: Array,
default: () => [],
},
},
data() {
return {
defaultActive: null,
};
},
mounted() {
this.initElMenuOffsetLeft();
this.setCurrentRouterHighlight(this.$route.path);
},
methods: {
// 设置横向滚动条可以鼠标滚轮滚动
onElMenuHorizontalScroll(e) {
const eventDelta = e.wheelDelta || -e.deltaY * 40;
this.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = this.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft + eventDelta / 4;
},
// 初始化数据,页面刷新时,滚动条滚动到对应位置
initElMenuOffsetLeft() {
this.$nextTick(() => {
let els = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
if (!els) return false;
this.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = els.offsetLeft;
});
},
// 路由过滤递归函数
filterRoutesFun(arr) {
return arr
.filter((item) => !item.meta.isHide)
.map((item) => {
item = Object.assign({}, item);
if (item.children) item.children = this.filterRoutesFun(item.children);
return item;
});
},
// 传送当前子级数据到菜单中
setSendClassicChildren(path) {
const currentPathSplit = path.split('/');
let currentData = {};
this.filterRoutesFun(this.$store.state.routesList.routesList).map((v, k) => {
if (v.path === `/${currentPathSplit[1]}`) {
v['k'] = k;
currentData['item'] = [{ ...v }];
currentData['children'] = [{ ...v }];
if (v.children) currentData['children'] = v.children;
}
});
return currentData;
},
// 菜单激活回调
onHorizontalSelect(path) {
this.bus.$emit('setSendClassicChildren', this.setSendClassicChildren(path));
},
// 设置页面当前路由高亮
setCurrentRouterHighlight(path) {
const currentPathSplit = path.split('/');
if (this.$store.state.themeConfig.themeConfig.layout === 'classic') {
this.defaultActive = `/${currentPathSplit[1]}`;
} else {
this.defaultActive = path;
}
},
},
watch: {
// 监听路由的变化
$route: {
handler(to) {
this.setCurrentRouterHighlight(to.path);
},
deep: true,
},
},
};
</script>
<style scoped lang="scss">
.el-menu-horizontal-warp {
flex: 1;
overflow: hidden;
margin-right: 30px;
::v-deep .el-scrollbar__bar.is-vertical {
display: none;
}
::v-deep .el-scrollbar__wrap {
overflow-y: hidden !important;
}
::v-deep a {
width: 100%;
}
.el-menu.el-menu--horizontal {
display: flex;
height: 100%;
width: 100%;
box-sizing: border-box;
}
}
</style>

View File

@@ -0,0 +1,41 @@
<template>
<div>
<template v-for="val in chil">
<el-submenu :index="val.path" :key="val.path" v-if="val.children && val.children.length > 0">
<template slot="title">
<i :class="val.meta.icon"></i>
<span>{{ $t(val.meta.title) }}</span>
</template>
<sub-item :chil="val.children" />
</el-submenu>
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<template v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<span>{{ $t(val.meta.title) }}</span>
</template>
<template v-else>
<a :href="val.meta.isLink" target="_blank">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
{{ $t(val.meta.title) }}
</a>
</template>
</el-menu-item>
</template>
</template>
</div>
</template>
<script>
export default {
name: 'subItem',
props: {
chil: {
type: Array,
default() {
return [];
},
},
},
};
</script>

View File

@@ -0,0 +1,73 @@
<template>
<el-menu
router
background-color="transparent"
:default-active="defaultActive"
:collapse="setIsCollapse"
:unique-opened="getThemeConfig.isUniqueOpened"
:collapse-transition="false"
>
<template v-for="val in menuList">
<el-submenu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template slot="title">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<span>{{ $t(val.meta.title) }}</span>
</template>
<SubItem :chil="val.children" />
</el-submenu>
<template v-else>
<el-menu-item :index="val.path" :key="val.path">
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
<template slot="title" v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
<span>{{ $t(val.meta.title) }}</span>
</template>
<template slot="title" v-else>
<a :href="val.meta.isLink" target="_blank">{{ $t(val.meta.title) }}</a>
</template>
</el-menu-item>
</template>
</template>
</el-menu>
</template>
<script>
import SubItem from '@/layout/navMenu/subItem.vue';
export default {
name: 'navMenuVertical',
components: { SubItem },
props: {
menuList: {
type: Array,
default() {
return [];
},
},
},
data() {
return {
defaultActive: this.$route.path,
};
},
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
// 设置左侧菜单是否展开/收起
setIsCollapse() {
return document.body.clientWidth < 1000 ? false : this.$store.state.themeConfig.themeConfig.isCollapse;
},
},
watch: {
// 监听路由的变化
$route: {
handler(to) {
this.defaultActive = to.path;
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) this.$store.state.themeConfig.themeConfig.isCollapse = false;
},
deep: true,
},
},
};
</script>

View File

@@ -0,0 +1,46 @@
<template>
<div>
<div class="layout-view-bg-white flex h100" v-loading="iframeLoading">
<iframe :src="meta.isLink" frameborder="0" height="100%" width="100%" id="iframe"></iframe>
</div>
</div>
</template>
<script>
export default {
name: 'layoutIfameView',
props: {
meta: {
type: Object,
default: () => {},
},
},
data() {
return {
iframeLoading: true,
};
},
created() {
this.bus.$on('onTagsViewRefreshRouterView', (path) => {
if (this.$route.path !== path) return false;
this.$emit('getCurrentRouteMeta');
});
},
mounted() {
this.initIframeLoad();
},
methods: {
// 初始化页面加载 loading
initIframeLoad() {
this.$nextTick(() => {
this.iframeLoading = true;
const iframe = document.getElementById('iframe');
if (!iframe) return false;
iframe.onload = () => {
this.iframeLoading = false;
};
});
},
},
};
</script>

View File

@@ -0,0 +1,83 @@
<template>
<div class="layout-scrollbar layout-link-container">
<div class="layout-view-bg-white flex layout-view-link">
<div class="layout-link-warp">
<i class="layout-link-icon iconfont icon-xingqiu"></i>
<div class="layout-link-msg">页面 "{{ $t(meta.title) }}" 已在新窗口中打开</div>
<el-button class="mt30" round size="small" @click="onGotoFullPage">
<i class="iconfont icon-lianjie"></i>
<span>立即前往体验</span>
</el-button>
</div>
</div>
</div>
</template>
<script>
import { verifyUrl } from '@/utils/toolsValidate';
export default {
name: 'layoutLinkView',
props: {
meta: {
type: Object,
default: () => {},
},
},
methods: {
// 立即前往
onGotoFullPage() {
const { origin, pathname } = window.location;
if (verifyUrl(this.meta.isLink)) window.open(this.meta.isLink);
else window.open(`${origin}${pathname}#${this.meta.isLink}`);
},
},
};
</script>
<style scoped lang="scss">
.layout-link-container {
.layout-link-warp {
margin: auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
i.layout-link-icon {
position: relative;
font-size: 100px;
color: var(--prev-color-primary);
&::after {
content: '';
position: absolute;
left: 50px;
top: 0;
width: 15px;
height: 100px;
background: linear-gradient(
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(235, 255, 255, 0.5),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.05),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01),
rgba(255, 255, 255, 0.01)
);
transform: rotate(-15deg);
animation: toRight 5s linear infinite;
}
}
.layout-link-msg {
font-size: 12px;
color: var(--prev-bg-topBarColor);
opacity: 0.7;
margin-top: 15px;
}
}
}
</style>

View File

@@ -0,0 +1,47 @@
<template>
<div class="h100">
<transition :name="setTransitionName" mode="out-in">
<keep-alive :include="keepAliveNameList">
<router-view :key="refreshRouterViewKey" />
</keep-alive>
</transition>
</div>
</template>
<script>
export default {
name: 'parent',
data() {
return {
refreshRouterViewKey: null,
keepAliveNameList: [],
keepAliveNameNewList: [],
};
},
created() {
// 页面加载前,处理缓存,页面刷新时路由缓存处理
this.keepAliveNameList = this.getKeepAliveNames();
this.bus.$on('onTagsViewRefreshRouterView', (path) => {
if (this.$route.path !== path) return false;
this.keepAliveNameList = this.getKeepAliveNames().filter((name) => this.$route.name !== name);
this.refreshRouterViewKey = this.$route.path;
this.$nextTick(() => {
this.refreshRouterViewKey = null;
this.keepAliveNameList = this.getKeepAliveNames();
});
});
},
computed: {
// 设置主界面切换动画
setTransitionName() {
return this.$store.state.themeConfig.themeConfig.animation;
},
},
methods: {
// 获取路由缓存列表name默认路由全部缓存
getKeepAliveNames() {
return this.$store.state.keepAliveNames.keepAliveNames;
},
},
};
</script>

View File

@@ -0,0 +1,149 @@
<template>
<div class="upgrade-dialog">
<el-dialog
:visible.sync="isUpgrade"
width="300px"
destroy-on-close
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<div class="upgrade-title">
<div class="upgrade-title-warp">
<span class="upgrade-title-warp-txt">{{ $t('message.upgrade.title') }}</span>
<span class="upgrade-title-warp-version">v{{ version }}</span>
</div>
</div>
<div class="upgrade-content">
{{ getThemeConfig.globalTitle }} {{ $t('message.upgrade.msg') }}
<div class="mt5">
<el-link type="primary" class="font12" href="https://gitee.com/lyt-top/vue-next-admin/blob/vue-prev-admin/CHANGELOG.md" target="_black">
CHANGELOG.md
</el-link>
</div>
<div class="upgrade-content-desc mt5">{{ $t('message.upgrade.desc') }}</div>
</div>
<div class="upgrade-btn">
<el-button round size="small" @click="onCancel">{{ $t('message.upgrade.btnOne') }}</el-button>
<el-button type="primary" round size="small" @click="onUpgrade" :loading="isLoading">{{ btnTxt }}</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { Local } from '@/utils/storage';
import config from '/package.json';
export default {
data() {
return {
isUpgrade: false,
version: config.version,
isLoading: false,
btnTxt: '',
};
},
computed: {
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
},
methods: {
// 残忍拒绝
onCancel() {
this.isUpgrade = false;
},
// 马上更新
onUpgrade() {
this.isLoading = true;
this.btnTxt = this.$t('message.upgrade.btnTwoLoading');
setTimeout(() => {
Local.clear();
window.location.reload();
Local.set('version', this.version);
}, 2000);
},
// 延迟显示,防止刷新时界面显示太快
delayShow() {
setTimeout(() => {
this.isUpgrade = true;
}, 2000);
},
},
mounted() {
this.delayShow();
setTimeout(() => {
this.btnTxt = this.$t('message.upgrade.btnTwo');
}, 200);
},
};
</script>
<style scoped lang="scss">
.upgrade-dialog {
& ::v-deep .el-dialog {
.el-dialog__body {
padding: 0 !important;
}
.el-dialog__header {
display: none !important;
}
.upgrade-title {
text-align: center;
height: 130px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&::after {
content: '';
position: absolute;
background-color: var(--prev-color-primary-light-1);
width: 130%;
height: 130px;
border-bottom-left-radius: 100%;
border-bottom-right-radius: 100%;
}
.upgrade-title-warp {
z-index: 1;
position: relative;
.upgrade-title-warp-txt {
color: var(--prev-color-text-white);
font-size: 22px;
letter-spacing: 3px;
}
.upgrade-title-warp-version {
background-color: var(--prev-color-primary-light-4);
color: var(--prev-color-text-white);
font-size: 12px;
position: absolute;
display: flex;
top: -2px;
right: -50px;
padding: 2px 4px;
border-radius: 2px;
}
}
}
.upgrade-content {
padding: 20px;
line-height: 22px;
color: var(--prev-color-text-regular);
.upgrade-content-desc {
color: var(--prev-color-text-placeholder);
font-size: 12px;
}
}
.upgrade-btn {
border-top: 1px solid var(--prev-border-color-lighter);
display: flex;
justify-content: space-around;
padding: 15px 20px;
.el-button {
width: 100%;
}
}
}
}
</style>

24
src/main.js Normal file
View File

@@ -0,0 +1,24 @@
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import Particles from 'vue-particles';
import Element from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import '@/theme/index.scss';
import { i18n } from '@/i18n/index.js';
import { globalComponentSize } from '@/utils/componentSize.js';
Vue.use(Particles);
Vue.use(Element, { i18n: (key, value) => i18n.t(key, value), size: globalComponentSize });
Vue.config.productionTip = false;
Vue.prototype.bus = new Vue();
new Vue({
router,
store,
i18n,
render: (h) => h(App),
}).$mount('#app');

275
src/router/index.js Normal file
View File

@@ -0,0 +1,275 @@
import Vue from 'vue';
import store from '../store';
import VueRouter from 'vue-router';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import { Session } from '@/utils/storage';
import { PrevLoading } from '@/utils/loading.js';
import { useMenuApi } from '@/api/menu';
const menuApi = useMenuApi();
// 解决 `element ui` 导航栏重复点菜单报错问题
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch((err) => err);
};
// 安装 VueRouter 插件
Vue.use(VueRouter);
// 定义动态路由
const dynamicRoutes = [
{
path: '/',
name: '/',
component: 'layout/index',
redirect: '/home',
meta: {
isKeepAlive: true,
},
children: [],
},
];
// 定义静态路由
const staticRoutes = [
{
path: '/login',
name: 'login',
component: () => import('@/views/login'),
meta: {
title: '登录',
},
},
{
path: '/404',
name: 'notFound',
component: () => import('@/views/error/404.vue'),
meta: {
title: 'message.staticRoutes.notFound',
},
},
{
path: '/401',
name: 'noPower',
component: () => import('@/views/error/401.vue'),
meta: {
title: 'message.staticRoutes.noPower',
},
},
];
// 加载静态路由
const createRouter = () =>
new VueRouter({
routes: staticRoutes,
});
// 创建路由
const router = createRouter();
// 加载 loading
PrevLoading.start();
// 多级嵌套数组处理成一维数组
export function formatFlatteningRoutes(arr) {
if (arr.length <= 0) return false;
for (let i = 0; i < arr.length; i++) {
if (arr[i].children) {
arr = arr.slice(0, i + 1).concat(arr[i].children, arr.slice(i + 1));
}
}
return arr;
}
// 处理 tagsViewList 数据,默认路由全部缓存
// isKeepAlive 处理 `name` 值,进行路由缓存
export function formatTwoStageRoutes(arr) {
if (arr.length <= 0) return false;
const newArr = [];
const cacheList = [];
arr.forEach((v) => {
newArr.push({ ...v });
cacheList.push(v.name);
store.dispatch('keepAliveNames/setCacheKeepAlive', cacheList);
});
return newArr;
}
// 判断路由 meta.roles 中是否包含当前登录用户权限字段
export function hasAuth(roles, route) {
if (route.meta && route.meta.roles) return roles.some((role) => route.meta.roles.includes(role));
else return true;
}
// 递归过滤有权限的路由
export function setFilterMenuFun(routes, role) {
const menu = [];
routes.forEach((route) => {
const item = { ...route };
if (hasAuth(role, item)) {
if (item.children) item.children = setFilterMenuFun(item.children, role);
menu.push(item);
}
});
return menu;
}
// 缓存多级嵌套数组处理后的一维数组(tagsView、菜单搜索中使用未过滤隐藏的(isHide))
export function setCacheTagsViewRoutes(arr) {
// 先处理有权限的路由,否则 tagsView、菜单搜索中无权限的路由也将显示
let rolesRoutes = setFilterMenuFun(arr, store.state.userInfos.userInfos.roles);
// 添加到 vuex setTagsViewRoutes 中
store.dispatch('tagsViewRoutes/setTagsViewRoutes', formatTwoStageRoutes(formatFlatteningRoutes(rolesRoutes)));
}
// 递归处理多余的 layout : <router-view>,让需要访问的组件保持在第一层 layout 层。
// 因为 `keep-alive` 只能缓存二级路由
// 默认初始化时就执行
export function keepAliveSplice(to) {
if (to.matched && to.matched.length > 2) {
to.matched.map((v, k) => {
if (v.components.default instanceof Function) {
v.components.default().then((components) => {
if (components.default.name === 'parent') {
to.matched.splice(k, 1);
router.push({ path: to.path, query: to.query });
keepAliveSplice(to);
}
});
} else {
if (v.components.default.name === 'parent') {
to.matched.splice(k, 1);
keepAliveSplice(to);
}
}
});
}
}
// 处理后端返回的 `component` 路径,拼装实现懒加载
export function loadView(path) {
/**
* 打包成一个 js、一个 css
*/
// if (path.indexOf('layout') > -1) return () => Promise.resolve(require(`@/${path}`));
// else return () => Promise.resolve(require(`@/views/${path}`));
/**
* 打包成多个 js、多个 css
*/
if (path.indexOf('layout') > -1) return () => import(`@/${path}`);
else return () => import(`@/views/${path}`);
}
// 递归处理每一项 `component` 中的路径
export function dynamicRouter(routes) {
return routes.map((view) => {
if (view.component) view.component = loadView(view.component);
if (view.children) dynamicRouter(view.children);
return view;
});
}
// 添加路由,模拟数据与方法,可自行进行修改 admin
// 添加动态路由,`{ path: '*', redirect: '/404' }` 防止页面刷新,静态路由丢失问题
// next({ ...to, replace: true }) 动态路由 addRoute 完毕后才放行,防止刷新时 NProgress 进度条加载2次
// 文档地址https://router.vuejs.org/zh/api/#router-addroutes
export function adminUser(router, to, next) {
resetRouter();
menuApi
.getMenuAdmin()
.then(async (res) => {
// 读取用户信息,获取对应权限进行判断
store.dispatch('userInfos/setUserInfos');
store.dispatch('routesList/setRoutesList', setFilterMenuFun(res.data, store.state.userInfos.userInfos.roles));
dynamicRoutes[0].children = res.data;
const awaitRoute = await dynamicRouter(dynamicRoutes);
[...awaitRoute, { path: '*', redirect: '/404' }].forEach((route) => {
router.addRoute({ ...route });
});
setCacheTagsViewRoutes(JSON.parse(JSON.stringify(res.data)));
next({ ...to, replace: true });
})
.catch(() => {});
}
// 添加路由,模拟数据与方法,可自行进行修改 test
// 添加动态路由,`{ path: '*', redirect: '/404' }` 防止页面刷新,静态路由丢失问题
export function testUser(router, to, next) {
resetRouter();
menuApi
.getMenuTest()
.then(async (res) => {
// 读取用户信息,获取对应权限进行判断
store.dispatch('userInfos/setUserInfos');
store.dispatch('routesList/setRoutesList', setFilterMenuFun(res.data, store.state.userInfos.userInfos.roles));
dynamicRoutes[0].children = res.data;
const awaitRoute = await dynamicRouter(dynamicRoutes);
[...awaitRoute, { path: '*', redirect: '/404' }].forEach((route) => {
router.addRoute({ ...route });
});
setCacheTagsViewRoutes(JSON.parse(JSON.stringify(res.data)));
next({ ...to, replace: true });
})
.catch(() => {});
}
// 重置路由
export function resetRouter() {
router.matcher = createRouter().matcher;
}
// 延迟关闭进度条
export function delayNProgressDone(time = 300) {
setTimeout(() => {
NProgress.done();
}, time);
}
// 动态加载后端返回路由路由(模拟数据)
export function getRouterList(router, to, next) {
if (!Session.get('userInfo')) return false;
if (Session.get('userInfo').userName === 'admin') adminUser(router, to, next);
else if (Session.get('userInfo').userName === 'test') testUser(router, to, next);
}
// 路由加载前
router.beforeEach((to, from, next) => {
keepAliveSplice(to);
NProgress.configure({ showSpinner: false });
if (to.meta.title && to.path !== '/login') NProgress.start();
let token = Session.get('token');
if (to.path === '/login' && !token) {
NProgress.start();
next();
delayNProgressDone();
} else {
if (!token) {
NProgress.start();
next('/login');
Session.clear();
delayNProgressDone();
} else if (token && to.path === '/login') {
next('/home');
delayNProgressDone();
} else {
if (Object.keys(store.state.routesList.routesList).length <= 0) {
getRouterList(router, to, next);
} else {
next();
delayNProgressDone(0);
}
}
}
});
// 路由加载后
router.afterEach(() => {
PrevLoading.done();
delayNProgressDone();
});
// 导出路由
export default router;

15
src/store/index.js Normal file
View File

@@ -0,0 +1,15 @@
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
let moduleFn = require.context('./modules', false, /\.js$/);
let modules = moduleFn.keys().reduce((p, c) => {
let mod = moduleFn(c).default;
mod = { ...mod, namespaced: true };
let modName = c.match(/\.\/(\w+)\.js$/)[1];
p[modName] = mod;
return p;
}, {});
export default new Vuex.Store({ modules });

View File

@@ -0,0 +1,20 @@
const keepAliveNamesModule = {
namespaced: true,
state: {
keepAliveNames: [],
},
mutations: {
// 设置路由缓存name字段
getCacheKeepAlive(state, data) {
state.keepAliveNames = data;
},
},
actions: {
// 设置路由缓存name字段
async setCacheKeepAlive({ commit }, data) {
commit('getCacheKeepAlive', data);
},
},
};
export default keepAliveNamesModule;

View File

@@ -0,0 +1,20 @@
const routesListModule = {
namespaced: true,
state: {
routesList: [],
},
mutations: {
// 设置路由,菜单中使用到
getRoutesList(state, data) {
state.routesList = data;
},
},
actions: {
// 设置路由,菜单中使用到
async setRoutesList({ commit }, data) {
commit('getRoutesList', data);
},
},
};
export default routesListModule;

View File

@@ -0,0 +1,20 @@
const tagsViewRoutesModule = {
namespaced: true,
state: {
tagsViewRoutes: [],
},
mutations: {
// 设置 TagsView 路由
getTagsViewRoutes(state, data) {
state.tagsViewRoutes = data;
},
},
actions: {
// 设置 TagsView 路由
async setTagsViewRoutes({ commit }, data) {
commit('getTagsViewRoutes', data);
},
},
};
export default tagsViewRoutesModule;

View File

@@ -0,0 +1,122 @@
/**
* 2020.05.28 by lyt 优化
* 修改一下配置时,需要每次都清理 `window.localStorage` 浏览器永久缓存,配置才会生效
*/
const themeConfigModule = {
namespaced: true,
state: {
themeConfig: {
// 是否开启布局配置抽屉
isDrawer: false,
/**
* 全局主题
*/
// 默认 primary 主题颜色
primary: '#409eff',
// 是否开启深色模式
isIsDark: false,
/**
* 菜单 / 顶栏
* 请注意:
* 需要同时修改 `/@/theme/common/var.scss` 对应的值,
* 不提供像 vue-next-admin 一样的实现
*/
// 默认顶栏导航背景颜色
topBar: '#ffffff',
// 默认顶栏导航字体颜色
topBarColor: '#606266',
// 默认菜单导航背景颜色
menuBar: '#545c64',
// 默认菜单导航字体颜色
menuBarColor: '#eaeaea',
// 默认分栏菜单背景颜色
columnsMenuBar: '#545c64',
// 默认分栏菜单字体颜色
columnsMenuBarColor: '#e6e6e6',
/**
* 界面设置
*/
// 是否开启菜单水平折叠效果
isCollapse: false,
// 是否开启菜单手风琴效果
isUniqueOpened: false,
// 是否开启固定 Header
isFixedHeader: false,
/**
* 界面显示
*/
// 是否开启侧边栏 Logo
isShowLogo: false,
// 是否开启 Breadcrumb
isBreadcrumb: true,
// 是否开启 Breadcrumb 图标
isBreadcrumbIcon: false,
// 是否开启 Tagsview
isTagsview: true,
// 是否开启 Tagsview 图标
isTagsviewIcon: false,
// 是否开启 TagsView 缓存
isCacheTagsView: false,
// 是否开启 Footer 底部版权信息
isFooter: false,
// 是否开启灰色模式
isGrayscale: false,
// 是否开启色弱模式
isInvert: false,
/**
* 其它设置
*/
// 默认 Tagsview 风格,可选 1、 tags-style-one自行扩展
// 1、需修改 @/layout/navBars/breadcrumb/setings.vue `getThemeConfig.tagsStyle` el-option
// 2、需修改 @/layout/navBars/tagsView/tagsView.vue 代码最底部注释部分 css 样式
tagsStyle: 'tags-style-one',
// 主页面切换动画:可选值"<slide-right|slide-left|opacitys>",默认 slide-right
animation: 'slide-right',
// 分栏高亮风格:可选值"<columns-round|columns-card>",默认 columns-round
columnsAsideStyle: 'columns-round',
// 分栏布局风格:可选值"<columns-horizontal|columns-vertical>",默认 columns-horizontal
columnsAsideLayout: 'columns-vertical',
/**
* 布局切换
* 注意:为了演示,切换布局时,颜色会被还原成默认,代码位置:/@/layout/navBars/breadcrumb/setings.vue
* 中的 `initSetLayoutChange(设置布局切换,重置主题样式)` 方法
*/
// 布局切换:可选值"<defaults|classic|transverse|columns>",默认 defaults
layout: 'defaults',
/**
* 全局网站标题 / 副标题
*/
// 网站主标题(菜单导航、浏览器当前网页标题)
globalTitle: 'vue-prev-admin',
// 网站副标题(登录页顶部文字)
globalViceTitle: 'SMALL@小柒',
// 网站描述(登录页顶部文字)
globalViceDes: 'vue2.x后台管理系统免费开源模板',
// 默认初始语言,可选值"<zh-cn|en|zh-tw>",默认 zh-cn
globalI18n: 'zh-cn',
// 默认全局组件大小,可选值"<|medium|small|mini>",默认 ''
globalComponentSize: '',
},
},
mutations: {
// 设置布局配置
getThemeConfig(state, data) {
state.themeConfig = data;
},
},
actions: {
// 设置布局配置
setThemeConfig({ commit }, data) {
commit('getThemeConfig', data);
},
},
};
export default themeConfigModule;

View File

@@ -0,0 +1,26 @@
import { Session } from '@/utils/storage.js';
const userInfosModule = {
namespaced: true,
state: {
userInfos: {},
},
mutations: {
// 设置用户信息
getUserInfos(state, data) {
state.userInfos = data;
},
},
actions: {
// 设置用户信息
async setUserInfos({ commit }, data) {
if (data) {
commit('getUserInfos', data);
} else {
if (Session.get('userInfo')) commit('getUserInfos', Session.get('userInfo'));
}
},
},
};
export default userInfosModule;

240
src/theme/app.scss Normal file
View File

@@ -0,0 +1,240 @@
/* 初始化样式
------------------------------- */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
outline: none;
}
:root {
--prev-bg-menuBar: #545c64;
--prev-bg-menuBarColor: #eaeaea;
--prev-bg-topBar: #ffffff;
--prev-bg-topBarColor: #606266;
--prev-bg-columnsMenuBar: #545c64;
--prev-bg-columnsMenuBarColor: #e6e6e6;
--prev-bg-main-color: #f8f8f8;
--prev-bg-color: #f5f7fa;
--prev-bg-white: #ffffff;
--prev-color-primary: #409eff;
--prev-color-text-white: #ffffff;
--prev-color-text-black: #000000;
--prev-color-text-primary: #303133;
--prev-color-text-regular: #606266;
--prev-color-text-secondary: #909399;
--prev-color-text-placeholder: #c0c4cc;
--prev-color-hover: rgba(0, 0, 0, 0.04);
--prev-color-seting-main: #e9eef3;
--prev-color-seting-aside: #d3dce6;
--prev-color-seting-header: #b3c0d1;
--prev-border-color-hover: #c0c4cc;
--prev-border-color-base: #dcdfe6;
--prev-border-color-light: #e4e7ed;
--prev-border-color-lighter: #ebeef5;
--prev-border-color-extra-light: #f2f6fc;
}
html,
body,
#app {
width: 100%;
height: 100%;
background-color: var(--prev-bg-main-color);
font-size: 14px;
}
/* 主布局样式
------------------------------- */
.layout-container {
width: 100%;
height: 100%;
.layout-aside {
background: var(--prev-bg-menuBar);
box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
height: inherit;
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
overflow-x: hidden !important;
.el-scrollbar__view {
overflow: hidden;
}
}
.layout-header {
padding: 0 !important;
}
.layout-main {
padding: 0 !important;
overflow: hidden;
width: 100%;
background-color: var(--prev-bg-main-color);
}
.el-scrollbar {
width: 100%;
}
.layout-view-bg-white {
background: var(--prev-bg-white);
width: 100%;
height: 100%;
border-radius: 4px;
border: 1px solid var(--prev-border-color-lighter);
}
.layout-el-aside-br-color {
border-right: 1px solid rgb(238, 238, 238);
}
.layout-aside-width-default {
width: 220px !important;
transition: width 0.3s ease;
}
.layout-aside-width64 {
width: 64px !important;
transition: width 0.3s ease;
}
.layout-aside-width1 {
width: 1px !important;
transition: width 0.3s ease;
}
.layout-scrollbar {
@extend .el-scrollbar;
padding: 15px;
}
.layout-mian-height-50 {
height: calc(100vh - 50px);
}
.layout-columns-warp {
flex: 1;
display: flex;
overflow: hidden;
}
.layout-hide {
display: none;
}
}
/* 进度条颜色
------------------------------- */
#nprogress .bar {
background: var(--prev-color-primary) !important;
}
/* flex 弹性布局
------------------------------- */
.flex {
display: flex;
}
.flex-auto {
flex: 1;
}
.flex-center {
@extend .flex;
flex-direction: column;
width: 100%;
overflow: hidden;
}
.flex-margin {
margin: auto;
}
.flex-warp {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
margin: 0 -5px;
.flex-warp-item {
padding: 5px;
.flex-warp-item-box {
width: 100%;
height: 100%;
}
}
}
/* 宽高 100%
------------------------------- */
.w100 {
width: 100% !important;
}
.h100 {
height: 100% !important;
}
.vh100 {
height: 100vh !important;
}
.max100vh {
max-height: 100vh !important;
}
.min100vh {
min-height: 100vh !important;
}
/* 颜色值
------------------------------- */
.color-primary {
color: var(--prev-color-primary);
}
.color-success {
color: var(--prev-color-success);
}
.color-warning {
color: var(--prev-color-warning);
}
.color-danger {
color: var(--prev-color-danger);
}
.color-info {
color: var(--prev-color-info);
}
/* 溢出省略号
------------------------------- */
.one-text-overflow {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.two-text-overflow {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.overflow {
overflow: hidden !important;
}
/* 字体大小全局样式
------------------------------- */
@for $i from 10 through 32 {
.font#{$i} {
font-size: #{$i}px !important;
}
}
/* 外边距、内边距全局样式
------------------------------- */
@for $i from 5 through 35 {
.mt#{$i} {
margin-top: #{$i}px !important;
}
.mr#{$i} {
margin-right: #{$i}px !important;
}
.mb#{$i} {
margin-bottom: #{$i}px !important;
}
.ml#{$i} {
margin-left: #{$i}px !important;
}
.pt#{$i} {
padding-top: #{$i}px !important;
}
.pr#{$i} {
padding-right: #{$i}px !important;
}
.pb#{$i} {
padding-bottom: #{$i}px !important;
}
.pl#{$i} {
padding-left: #{$i}px !important;
}
}

1
src/theme/base.scss Normal file
View File

@@ -0,0 +1 @@
@import 'common/transition.scss';

View File

@@ -0,0 +1,102 @@
/* 页面切换动画
------------------------------- */
.slide-right-enter-active,
.slide-right-leave-active,
.slide-left-enter-active,
.slide-left-leave-active {
will-change: transform;
transition: all 0.3s ease;
}
// slide-right
.slide-right-enter {
opacity: 0;
transform: translateX(-20px);
}
.slide-right-leave-active {
opacity: 0;
transform: translateX(20px);
}
// slide-left
.slide-left-enter-from {
@extend .slide-right-leave-active;
}
.slide-left-leave-to {
@extend .slide-right-enter;
}
// opacitys
.opacitys-enter-active,
.opacitys-leave-active {
will-change: transform;
transition: all 0.3s ease;
}
.opacitys-enter,
.opacitys-leave-active {
opacity: 0;
}
/* Breadcrumb 面包屑过渡动画
------------------------------- */
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all 0.3s;
}
.breadcrumb-enter,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(20px);
}
.breadcrumb-move {
transition: all 0.3s;
}
.breadcrumb-leave-active {
position: absolute;
}
/* logo 过渡动画
------------------------------- */
@keyframes logoAnimation {
0% {
transform: scale(0);
}
80% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
/* 404、401 过渡动画
------------------------------- */
@keyframes error-num {
0% {
transform: translateY(60px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
@keyframes error-img {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
/* 左右左 link.vue
------------------------------- */
@keyframes toRight {
0% {
left: -5px;
}
50% {
left: 100%;
}
100% {
left: -5px;
}
}

109
src/theme/dark.scss Normal file
View File

@@ -0,0 +1,109 @@
/* 深色模式样式
------------------------------- */
[data-theme='dark'] {
--prev-bg-menuBar: #191919 !important;
--prev-bg-menuBarColor: #dadada !important;
--prev-bg-topBar: #191919 !important;
--prev-bg-topBarColor: #dadada !important;
--prev-bg-columnsMenuBar: #191919 !important;
--prev-bg-columnsMenuBarColor: #dadada !important;
--prev-bg-main-color: #1f1f1f !important;
--prev-bg-color: rgba(0, 0, 0, 0.3) !important;
--prev-bg-white: #191919 !important;
--prev-color-text-black: #ffffff !important;
--prev-color-text-primary: #dadada !important;
--prev-color-text-regular: #dadada !important;
--prev-color-text-secondary: #a3a3a3 !important;
--prev-color-hover: rgba(0, 0, 0, 0.3) !important;
--prev-color-seting-main: #505050 !important;
--prev-color-seting-aside: #3c3c3c !important;
--prev-color-seting-header: #303030 !important;
--prev-border-color-hover: #616161 !important;
--prev-border-color-base: #333333 !important;
--prev-border-color-light: #333333 !important;
--prev-border-color-lighter: #333333 !important;
--prev-border-color-extra-light: #333333 !important;
// menu
.layout-aside {
border-right: 1px solid var(--prev-border-color-lighter) !important;
}
// drawer
.el-drawer {
border-left: 1px solid var(--prev-border-color-lighter) !important;
}
// button
.el-button--default {
background: var(--prev-bg-white);
color: var(--prev-color-text-primary);
border-color: var(--prev-border-color-lighter);
&:hover,
&:focus {
color: var(--prev-color-primary) !important;
background: var(--prev-color-primary-light-8) !important;
border-color: var(--prev-color-primary-light-6) !important;
}
&:focus {
border-color: var(--prev-color-primary-light-1) !important;
}
&:active {
border-color: var(--prev-color-primary-light-6) !important;
}
}
// tag
.el-tag.el-tag--info {
background-color: var(--prev-bg-white) !important;
border-color: var(--prev-border-color-light) !important;
color: var(--prev-color-text-regular) !important;
}
// switch
.el-switch:not(.is-checked) {
.el-switch__core {
border-color: var(--prev-border-color-base) !important;
background-color: var(--prev-border-color-base) !important;
}
}
// TimePicker
.el-time-spinner__item.active:not(.disabled) {
color: var(--prev-color-primary) !important;
}
// date
.el-date-table td.in-range div,
.el-date-table td.in-range div:hover,
.el-date-table.is-week-mode .el-date-table__row.current div,
.el-date-table.is-week-mode .el-date-table__row:hover div,
.el-date-table td.selected div,
.el-month-table td.in-range div,
.el-month-table td.in-range div:hover {
background-color: var(--prev-bg-color) !important;
}
// transfer
.el-transfer-panel,
.el-transfer-panel .el-transfer-panel__header {
background-color: var(--prev-bg-color) !important;
}
// loading
.el-loading-mask {
background-color: var(--prev-bg-color) !important;
}
// dropdown
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
background-color: var(--prev-color-hover) !important;
}
// dialog
.el-dialog,
.el-calendar {
border: 1px solid var(--prev-border-color-lighter);
}
}

219
src/theme/element.scss Normal file
View File

@@ -0,0 +1,219 @@
/* 防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)
------------------------------- */
.el-scrollbar {
overflow: hidden;
position: relative;
height: 100%;
}
.el-scrollbar__wrap {
overflow: auto !important;
overflow-x: hidden !important;
max-height: 100%; /*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
}
.el-select-dropdown .el-scrollbar__wrap {
overflow-x: scroll !important;
}
.el-select-dropdown__wrap {
max-height: 274px !important; /*修复Select 选择器高度问题*/
}
.el-autocomplete-suggestion__wrap {
max-height: 280px !important;
}
/* Button 按钮
------------------------------- */
// 第三方字体图标大小
.el-button i.iconfont,
.el-button i.fa {
font-size: 14px !important;
margin-right: 5px;
}
.el-button--medium i.iconfont,
.el-button--medium i.fa {
font-size: 14px !important;
margin-right: 5px;
}
.el-button--small i.iconfont,
.el-button--small i.fa {
font-size: 12px !important;
margin-right: 5px;
}
.el-button--mini i.iconfont,
.el-button--mini i.fa {
font-size: 12px !important;
margin-right: 5px;
}
/* Dialog 对话框
------------------------------- */
.el-overlay,
.el-dialog__wrapper {
display: flex;
align-items: center;
justify-content: center;
.el-dialog {
margin: 0 auto !important;
.el-dialog__body {
padding: 20px !important;
}
}
}
.el-dialog__body {
max-height: calc(90vh - 111px) !important;
overflow-y: auto;
overflow-x: hidden;
}
/* Alert 警告
------------------------------- */
.el-alert--warning.is-light {
border: 1px solid rgba(230, 162, 60, 0.3) !important;
}
.el-alert--success.is-light {
border: 1px solid rgba(103, 194, 58, 0.3) !important;
}
.el-alert--info.is-light {
border: 1px solid rgba(144, 147, 153, 0.3) !important;
}
.el-alert--error.is-light {
border: 1px solid rgba(245, 108, 108, 0.3) !important;
}
/* Table 表格
------------------------------- */
.el-table-column--selection {
.el-checkbox {
margin-right: unset !important;
}
}
.el-table::before,
.el-table--group::after,
.el-table--border::after {
z-index: 99 !important;
}
/* 下拉选择器/时间选择器滚动条
------------------------------- */
.el-select-dropdown .el-scrollbar__wrap,
.el-picker-panel .el-scrollbar__wrap {
overflow-x: scroll !important;
}
/* NavMenu 导航菜单
------------------------------- */
// 默认样式修改
.el-menu {
border-right: none !important;
}
.el-menu-item,
.el-submenu__title {
height: 50px !important;
line-height: 50px !important;
color: var(--prev-bg-menuBarColor) !important;
transition: none !important;
}
// horizontal 水平方向时
.el-menu--horizontal > .el-menu-item.is-active,
.el-menu--horizontal > .el-submenu.is-active .el-submenu__title {
border-bottom: 3px solid !important;
border-bottom-color: var(--prev-color-primary) !important;
color: var(--prev-color-primary) !important;
}
.el-menu--horizontal .el-menu-item:not(.is-disabled):focus,
.el-menu--horizontal .el-menu-item:not(.is-disabled):hover,
.el-menu--horizontal > .el-submenu:focus .el-submenu__title,
.el-menu--horizontal > .el-submenu:hover .el-submenu__title,
.el-menu--horizontal .el-menu .el-menu-item.is-active,
.el-menu--horizontal .el-menu .el-submenu.is-active > .el-submenu__title {
color: var(--prev-color-primary) !important;
}
.el-menu.el-menu--horizontal {
border-bottom: none !important;
}
.el-menu--horizontal > .el-menu-item,
.el-menu--horizontal > .el-submenu .el-submenu__title {
color: var(--bg-topBarColor) !important;
}
// 外部链接时
.el-menu-item a,
.el-menu-item a:hover,
.el-menu-item i,
.el-submenu__title i {
color: inherit;
text-decoration: none;
}
.el-menu-item a {
width: 86%;
display: inline-block;
}
// 默认 hover 时
.el-menu-item:hover,
.el-submenu__title:hover {
color: var(--prev-color-primary) !important;
background-color: transparent !important;
i {
color: var(--prev-color-primary) !important;
}
}
// 高亮时
.el-menu-item.is-active {
color: var(--prev-color-primary) !important;
}
.el-active-extend {
color: #ffffff !important;
background-color: var(--prev-color-primary) !important;
i {
color: #ffffff !important;
}
}
#add-is-active {
@extend .el-active-extend;
&:hover {
@extend .el-active-extend;
}
}
// 菜单收起时且是a链接
.is-dark a {
color: #ffffff !important;
text-decoration: none;
}
// 菜单收起时鼠标经过背景颜色/字体颜色
.el-menu--vertical {
background: var(--prev-bg-menuBar) !important;
}
.el-menu--horizontal {
.el-menu {
background: var(--bg-topBar) !important;
}
.el-menu-item,
.el-submenu__title {
color: var(--bg-topBarColor);
}
}
// 第三方图标字体间距/大小设置
.el-menu-item .iconfont,
.el-submenu .iconfont,
.el-menu-item .fa,
.el-submenu__title .fa {
font-size: 14px !important;
display: inline-block;
vertical-align: middle;
margin-right: 5px;
width: 24px;
text-align: center;
}
// element plus 本身字体图标
.el-submenu [class^='el-icon-'],
.el-menu-item [class^='el-icon-'] {
font-size: 14px !important;
}
// 去掉离开浏览器时,菜单的默认高亮
.el-menu-item:focus {
background-color: transparent !important;
}
/* Alert 警告
------------------------------- */
.el-alert__title {
word-break: break-all;
}

7
src/theme/index.scss Normal file
View File

@@ -0,0 +1,7 @@
@import './base.scss';
@import './app.scss';
@import './other.scss';
@import './element.scss';
@import './media/media.scss';
@import './variables.scss';
@import './dark.scss';

56
src/theme/loading.scss Normal file
View File

@@ -0,0 +1,56 @@
.loading-prev {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 2;
background-color: var(--prev-bg-white);
}
.loading-prev .loading-prev-box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.loading-prev .loading-prev-box-warp {
width: 80px;
height: 80px;
}
.loading-prev .loading-prev-box-warp .loading-prev-box-item {
width: 33.333333%;
height: 33.333333%;
background: var(--prev-color-primary);
float: left;
animation: loading-prev-animation 1.2s infinite ease;
border-radius: 1px;
}
.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(7) {
animation-delay: 0s;
}
.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(4),
.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(8) {
animation-delay: 0.1s;
}
.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(1),
.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(5),
.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(9) {
animation-delay: 0.2s;
}
.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(2),
.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(6) {
animation-delay: 0.3s;
}
.loading-prev .loading-prev-box-warp .loading-prev-box-item:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes loading-prev-animation {
0%,
70%,
100% {
transform: scale3D(1, 1, 1);
}
35% {
transform: scale3D(0, 0, 1);
}
}

View File

@@ -0,0 +1,12 @@
@import './index.scss';
/* 页面宽度小于800px
------------------------------- */
@media screen and (max-width: 800px) {
.el-dialog {
width: 90%;
}
.el-dialog.is-fullscreen {
width: 100% !important;
}
}

View File

@@ -0,0 +1,35 @@
@import './index.scss';
/* 页面宽度小于768px
------------------------------- */
@media screen and (max-width: $sm) {
.error {
.error-flex {
flex-direction: column-reverse !important;
height: auto !important;
width: 100% !important;
}
.right,
.left {
flex: unset !important;
display: flex !important;
}
.left-item {
margin: auto !important;
}
.right img {
max-width: 450px !important;
@extend .left-item;
}
}
}
/* 页面宽度大于768px小于992px
------------------------------- */
@media screen and (min-width: $sm) and (max-width: $md) {
.error {
.error-flex {
padding-left: 30px !important;
}
}
}

19
src/theme/media/form.scss Normal file
View File

@@ -0,0 +1,19 @@
@import './index.scss';
/* 页面宽度小于576px
------------------------------- */
@media screen and (max-width: $xs) {
.el-form-item__label {
width: 100% !important;
text-align: left !important;
}
.el-form-item__content {
margin-left: 0 !important;
}
.el-form-item {
display: unset !important;
}
.login-form .el-form-item {
display: block !important;
}
}

35
src/theme/media/home.scss Normal file
View File

@@ -0,0 +1,35 @@
@import './index.scss';
/* 页面宽度小于1200px
------------------------------- */
@media screen and (max-width: $lg) {
.home-recommend-row {
.home-recommend {
margin-bottom: 15px;
}
& .el-col:last-of-type,
& .el-col:nth-last-child(2) {
.home-recommend {
margin-bottom: 0;
}
}
}
.home-lg {
margin-bottom: 15px;
}
}
/* 页面宽度小于992px
------------------------------- */
@media screen and (max-width: $md) {
.home-recommend-row {
& .el-col:nth-last-child(2) {
margin-bottom: 15px;
}
& .el-col:last-of-type {
.home-recommend {
margin-bottom: 0;
}
}
}
}

View File

@@ -0,0 +1,37 @@
/* 栅格布局(媒体查询变量)
* $xs <768px 响应式栅格
* $sm ≥768px 响应式栅格
* $md ≥992px 响应式栅格
* $lg ≥1200px 响应式栅格
* $xl ≥1920px 响应式栅格
------------------------------- */
$xs: 576px;
$sm: 768px;
$md: 992px;
$lg: 1200px;
$xl: 1920px;
/* 页面宽度小于576px
------------------------------- */
@media screen and (max-width: $xs) {
}
/* 页面宽度小于768px
------------------------------- */
@media screen and (max-width: $sm) {
}
/* 页面宽度大于768px小于992px
------------------------------- */
@media screen and (min-width: $sm) and (max-width: $md) {
}
/* 页面宽度大于992px小于1200px
------------------------------- */
@media screen and (min-width: $md) and (max-width: $lg) {
}
/* 页面宽度大于1920px
------------------------------- */
@media screen and (min-width: $xl) {
}

View File

@@ -0,0 +1,55 @@
@import './index.scss';
/* 页面宽度小于576px
------------------------------- */
@media screen and (max-width: $xs) {
// MessageBox 弹框
.el-message-box {
width: 80% !important;
}
}
/* 页面宽度小于768px
------------------------------- */
@media screen and (max-width: $sm) {
// Breadcrumb 面包屑
.layout-navbars-breadcrumb-hide {
display: none;
}
// 外链视图
.layout-view-link {
a {
max-width: 80%;
text-align: center;
}
}
// 菜单搜索
.layout-search-dialog {
.el-autocomplete {
width: 80% !important;
}
}
}
/* 页面宽度小于1000px
------------------------------- */
@media screen and (max-width: 1000px) {
// 布局配置
.layout-drawer-content-flex {
position: relative;
&::after {
content: '手机版不支持切换布局';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
text-align: center;
height: 140px;
line-height: 140px;
background: rgba(255, 255, 255, 0.9);
color: #666666;
}
}
}

View File

@@ -0,0 +1,32 @@
@import './index.scss';
/* 页面宽度小于576px
------------------------------- */
@media screen and (max-width: $xs) {
.login-weaper {
height: 420px !important;
.login-left {
display: none !important;
}
.login-right {
width: 100% !important;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
.login-main {
width: 94% !important;
}
}
}
}
/* 页面宽度大于576px小于992px
------------------------------- */
@media screen and (min-width: $xs) and (max-width: $md) {
.login-left {
display: none !important;
}
.login-right {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
}

View File

@@ -0,0 +1,7 @@
@import './login.scss';
@import './error.scss';
@import './home.scss';
@import './layout.scss';
@import './scrollbar.scss';
@import './dialog.scss';
@import './form.scss';

View File

@@ -0,0 +1,13 @@
@import './index.scss';
/* 页面宽度小于768px
------------------------------- */
@media screen and (max-width: $sm) {
// element plus scrollbar
.el-scrollbar__bar.is-vertical {
width: 2px !important;
}
.el-scrollbar__bar.is-horizontal {
height: 2px !important;
}
}

0
src/theme/other.scss Normal file
View File

952
src/theme/variables.scss Normal file
View File

@@ -0,0 +1,952 @@
/* Button 按钮
------------------------------- */
// text
.el-button--text {
color: var(--prev-color-primary);
&:focus,
&:hover {
color: var(--prev-color-primary-light-3);
}
}
.el-button--text:active {
color: var(--prev-color-primary-light-3);
}
// default
.el-button--default:hover,
.el-button--default:focus {
color: var(--prev-color-primary);
background: var(--prev-color-primary-light-8);
border-color: var(--prev-color-primary-light-6);
}
.el-button--default.is-plain:hover,
.el-button--default.is-plain:focus {
color: var(--prev-color-primary);
background: var(--prev-bg-white);
border-color: var(--prev-color-primary-light-1);
}
.el-button--default:active {
color: var(--prev-color-primary);
background: var(--prev-bg-white);
border-color: var(--prev-color-primary-light-1);
}
// primary
.el-button--primary {
color: var(--prev-color-text-white) !important;
background: var(--prev-color-primary) !important;
border-color: var(--prev-color-primary) !important;
&:hover,
&:focus {
color: var(--prev-color-text-white) !important;
background: var(--prev-color-primary-light-3) !important;
border-color: var(--prev-color-primary-light-3) !important;
}
}
.el-button--primary.is-plain {
color: var(--prev-color-primary) !important;
background: var(--prev-color-primary-light-8) !important;
border-color: var(--prev-color-primary-light-6) !important;
&:hover,
&:focus {
color: var(--prev-color-text-white) !important;
background: var(--prev-color-primary) !important;
border-color: var(--prev-color-primary) !important;
}
}
.el-button--primary.is-disabled,
.el-button--primary.is-disabled:active,
.el-button--primary.is-disabled:focus,
.el-button--primary.is-disabled:hover {
color: var(--prev-color-primary) !important;
background: var(--prev-color-primary-light-7) !important;
border-color: var(--prev-color-primary-light-7) !important;
}
.el-button--primary.is-active,
.el-button--primary:active {
color: var(--prev-color-text-white) !important;
background: var(--prev-color-primary) !important;
border-color: var(--prev-color-primary) !important;
}
.el-button.is-disabled.is-plain,
.el-button.is-disabled.is-plain:focus,
.el-button.is-disabled.is-plain:hover {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-lighter);
color: var(--prev-color-text-placeholder);
}
/* Link 文字链接
------------------------------- */
// default
.el-link.el-link--default:hover {
color: var(--prev-color-primary-light-3);
}
// primary
.el-link.el-link--primary {
color: var(--prev-color-primary);
&:hover {
color: var(--prev-color-primary-light-3);
}
}
.el-link.el-link--default::after,
.el-link.is-underline:hover::after,
.el-link.el-link--primary.is-underline:hover::after,
.el-link.el-link--primary::after {
border-color: var(--prev-color-primary);
}
/* Radio 单选框
------------------------------- */
.el-radio,
.el-checkbox {
color: var(--prev-color-text-regular);
}
.el-radio__input.is-checked + .el-radio__label,
.el-radio-button__inner:hover {
color: var(--prev-color-primary);
}
.el-radio__input.is-checked .el-radio__inner {
background-color: var(--prev-color-primary);
border-color: var(--prev-color-primary);
}
.el-radio-button__orig-radio:checked + .el-radio-button__inner {
color: var(--prev-color-text-white);
background-color: var(--prev-color-primary);
border-color: var(--prev-color-primary);
box-shadow: -1px 0 0 0 var(--prev-color-primary);
}
.el-radio.is-bordered.is-checked,
.el-radio__inner:hover {
border-color: var(--prev-color-primary);
}
.el-radio-button__inner,
.el-checkbox-button__inner {
background-color: var(--prev-bg-white);
color: var(--prev-color-text-regular);
border-color: var(--prev-border-color-base);
}
.el-radio-button:first-child .el-radio-button__inner,
.el-checkbox-button:first-child .el-checkbox-button__inner {
border-left-color: var(--prev-border-color-base);
}
.el-radio.is-bordered,
.el-checkbox.is-bordered {
border-color: var(--prev-border-color-base);
}
/* Checkbox 多选框
------------------------------- */
.el-checkbox__input.is-checked + .el-checkbox__label,
.el-checkbox-button__inner:hover {
color: var(--prev-color-primary);
}
.el-checkbox__input.is-checked .el-checkbox__inner {
background-color: var(--prev-color-primary);
border-color: var(--prev-color-primary);
}
.el-checkbox__input.is-focus .el-checkbox__inner,
.el-checkbox__inner:hover,
.el-checkbox.is-bordered.is-checked,
.el-checkbox-button.is-focus .el-checkbox-button__inner {
border-color: var(--prev-color-primary);
}
.el-checkbox-button.is-checked .el-checkbox-button__inner {
color: var(--prev-color-text-white);
background-color: var(--prev-color-primary);
border-color: var(--prev-color-primary);
box-shadow: -1px 0 0 0 var(--prev-color-primary);
}
.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner {
border-left-color: var(--prev-color-primary);
}
.el-checkbox__input.is-checked .el-checkbox__inner,
.el-checkbox__input.is-indeterminate .el-checkbox__inner {
background-color: var(--prev-color-primary);
border-color: var(--prev-color-primary);
}
.el-checkbox-button.is-disabled .el-checkbox-button__inner {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-base);
}
/* Input 输入框、InputNumber 计数器
------------------------------- */
// input-number
.el-input__inner:focus,
.el-input-number__decrease:hover:not(.is-disabled) ~ .el-input .el-input__inner:not(.is-disabled),
.el-input-number__increase:hover:not(.is-disabled) ~ .el-input .el-input__inner:not(.is-disabled),
.el-textarea__inner:focus {
border-color: var(--prev-color-primary) !important;
}
.el-input-number__increase:hover,
.el-input-number__decrease:hover {
color: var(--prev-color-primary);
}
.el-input-number__decrease,
.el-input-number__increase {
background-color: var(--prev-bg-color);
border-color: var(--prev-border-color-base) !important;
}
// input
.el-input__inner,
.el-textarea__inner {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-base);
color: var(--prev-color-text-regular);
&:hover {
border-color: var(--prev-border-color-hover);
}
}
.el-input.is-disabled .el-input__inner,
.el-textarea.is-disabled .el-textarea__inner {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-base);
}
.el-input-group__append,
.el-input-group__prepend {
background-color: var(--prev-bg-color);
color: var(--prev-color-text-regular);
border-color: var(--prev-border-color-base);
}
.el-input .el-input__count .el-input__count-inner {
background-color: var(--prev-bg-color);
}
// autocomplete
.el-autocomplete-suggestion {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-base);
}
.el-autocomplete-suggestion__wrap {
max-height: 280px !important;
}
// scss 循环
$positions: 'top', 'right', 'bottom', 'left';
@each $i in $positions {
.el-popper[x-placement^='#{$i}'] .popper__arrow {
border-#{$i}-color: var(--prev-border-color-base);
&::after {
border-#{$i}-color: var(--prev-bg-white);
}
}
}
.el-autocomplete-suggestion li {
color: var(--prev-color-text-regular);
}
.el-autocomplete-suggestion li.highlighted,
.el-autocomplete-suggestion li:hover {
background-color: var(--prev-color-hover);
}
/* Select 选择器
------------------------------- */
.el-range-editor.is-active,
.el-range-editor.is-active:hover,
.el-select .el-input.is-focus .el-input__inner,
.el-select .el-input__inner:focus {
border-color: var(--prev-color-primary);
}
.el-select-dropdown__item.selected {
color: var(--prev-color-primary);
}
.el-select-dropdown {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-light);
}
.el-select-dropdown__item {
color: var(--prev-color-text-regular);
}
.el-select-dropdown__item.hover,
.el-select-dropdown__item:hover {
background-color: var(--prev-color-hover);
}
.el-select-dropdown__item.is-disabled:hover {
background-color: var(--prev-bg-white);
}
.el-select .el-input.is-disabled .el-input__inner:hover {
border-color: var(--prev-border-color-light);
}
.el-select:hover .el-input__inner {
border-color: var(--prev-border-color-hover);
}
.el-select-dropdown.is-multiple .el-select-dropdown__item.selected {
background-color: var(--prev-bg-white);
}
.el-select-dropdown.is-multiple .el-select-dropdown__item.selected.hover {
background-color: var(--prev-color-hover);
}
.el-select-group__wrap:not(:last-of-type)::after {
background: var(--prev-border-color-light);
}
/* Cascader 级联选择器
------------------------------- */
.el-cascader .el-input .el-input__inner:focus,
.el-cascader .el-input.is-focus .el-input__inner {
border-color: var(--prev-color-primary);
}
.el-cascader-node.in-active-path,
.el-cascader-node.is-active,
.el-cascader-node.is-selectable.in-checked-path {
color: var(--prev-color-primary);
}
.el-cascader__dropdown {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-light);
}
.el-cascader-menu {
border-color: var(--prev-border-color-light);
color: var(--prev-color-text-regular);
}
.el-cascader-node:not(.is-disabled):focus,
.el-cascader-node:not(.is-disabled):hover {
background-color: var(--prev-color-hover);
}
/* Switch 开关
------------------------------- */
.el-switch.is-checked .el-switch__core {
border-color: var(--prev-color-primary);
background-color: var(--prev-color-primary);
}
.el-switch__label.is-active {
color: var(--prev-color-primary);
}
/* Slider 滑块
------------------------------- */
.el-slider__bar {
background-color: var(--prev-color-primary);
}
.el-slider__button {
border-color: var(--prev-color-primary);
}
.el-slider__runway {
background-color: var(--prev-border-color-light);
}
.el-slider__marks-text {
color: var(--prev-color-text-secondary);
}
/* TimePicker 时间选择器
------------------------------- */
.el-time-panel__btn.confirm,
.el-time-spinner__arrow:hover,
.time-select-item.selected:not(.disabled) {
color: var(--prev-color-primary);
}
.el-picker-panel {
border-color: var(--prev-border-color-light);
background-color: var(--prev-bg-white);
color: var(--prev-color-text-regular);
}
.time-select-item:hover,
.el-time-spinner__item:hover:not(.disabled):not(.active),
.el-time-spinner__wrapper.is-arrow .el-time-spinner__item:hover:not(.disabled):not(.active) {
background-color: var(--prev-color-hover);
}
.el-time-panel {
border-color: var(--prev-border-color-light);
background-color: var(--prev-bg-white);
}
.el-time-panel__footer,
.el-time-panel__content::after,
.el-time-panel__content::before,
.el-time-range-picker__body {
border-color: var(--prev-border-color-light);
}
.el-time-panel__btn,
.el-date-editor .el-range-separator {
color: var(--prev-color-text-primary);
}
.el-date-editor .el-range-input {
background-color: var(--prev-bg-white);
color: var(--prev-color-text-primary);
}
/* DatePicker 日期选择器
------------------------------- */
.el-date-table td.today span,
.el-date-table td.available:hover,
.el-date-picker__header-label.active,
.el-date-picker__header-label:hover,
.el-picker-panel__icon-btn:hover,
.el-year-table td.today .cell,
.el-year-table td .cell:hover,
.el-year-table td.current:not(.disabled) .cell,
.el-month-table td .cell:hover,
.el-month-table td.today .cell,
.el-month-table td.current:not(.disabled) .cell,
.el-picker-panel__shortcut:hover {
color: var(--prev-color-primary);
}
.el-date-table td.current:not(.disabled) span,
.el-date-table td.selected span {
color: var(--prev-color-text-white);
background-color: var(--prev-color-primary);
}
.el-date-table td.end-date span,
.el-date-table td.start-date span,
.el-month-table td.end-date .cell,
.el-month-table td.start-date .cell {
background-color: var(--prev-color-primary);
}
.el-date-table td.in-range div,
.el-date-table td.in-range div:hover,
.el-date-table.is-week-mode .el-date-table__row.current div,
.el-date-table.is-week-mode .el-date-table__row:hover div,
.el-date-table td.selected div {
background-color: var(--prev-color-primary-light-9);
}
.el-date-table th,
.el-date-picker__header--bordered,
.el-date-range-picker__content.is-left,
.el-date-picker__time-header,
.el-date-range-picker__time-header {
border-color: var(--prev-border-color-lighter);
}
.el-date-table th,
.el-date-picker__header-label,
.el-picker-panel__shortcut,
.el-month-table td .cell,
.el-year-table td .cell {
color: var(--prev-color-text-regular);
}
.el-date-table td.next-month,
.el-date-table td.prev-month {
color: var(--prev-border-color-hover);
}
.el-picker-panel__icon-btn {
color: var(--prev-color-text-primary);
}
.el-date-table td.disabled div {
background-color: var(--prev-bg-color);
}
.el-picker-panel [slot='sidebar'],
.el-picker-panel__sidebar,
.el-picker-panel__footer {
border-color: var(--prev-border-color-light);
background-color: var(--prev-bg-white);
}
.el-month-table td.end-date .cell,
.el-month-table td.start-date .cell {
color: var(--prev-color-text-white);
}
/* Upload 上传
------------------------------- */
.el-upload-list__item.is-success .el-upload-list__item-name:focus,
.el-upload-list__item.is-success .el-upload-list__item-name:hover,
.el-upload-list__item .el-icon-close-tip,
.el-upload-dragger .el-upload__text em {
color: var(--prev-color-primary);
}
.el-upload--picture-card,
.el-upload-dragger {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-light);
i {
color: var(--prev-color-text-regular);
}
}
.el-upload--picture-card:hover,
.el-upload:focus {
color: var(--prev-color-primary);
border-color: var(--prev-color-primary);
}
.el-upload-dragger:hover,
.el-upload:focus .el-upload-dragger {
border-color: var(--prev-color-primary);
}
.el-upload__tip,
.el-upload-list__item,
.el-upload-dragger .el-upload__text,
.el-upload-list__item-name,
.el-upload-list__item .el-icon-close {
color: var(--prev-color-text-regular);
}
.el-upload-list__item:hover {
background-color: var(--prev-bg-color);
}
/* ColorPicker 颜色选择器
------------------------------- */
.el-color-picker__trigger {
border-color: var(--prev-border-color-light);
}
/* Transfer 穿梭框
------------------------------- */
.el-transfer-panel__item:hover {
color: var(--prev-color-primary);
}
.el-transfer-panel,
.el-transfer-panel .el-transfer-panel__header {
border-color: var(--prev-border-color-lighter);
}
.el-transfer-panel .el-transfer-panel__footer {
border-color: var(--prev-border-color-lighter);
background-color: var(--prev-bg-white);
}
.el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label {
color: var(--prev-color-text-primary);
}
/* Form 表单
------------------------------- */
.el-form {
.el-form-item:last-of-type {
margin-bottom: 0 !important;
}
}
.el-form-item__label {
color: var(--prev-color-text-regular);
}
/* Table 表格
------------------------------- */
.el-table {
color: var(--prev-color-text-regular);
}
.el-table .descending .sort-caret.descending {
border-top-color: var(--prev-color-primary);
}
.el-table .ascending .sort-caret.ascending {
border-bottom-color: var(--prev-color-primary);
}
.el-table thead {
color: var(--prev-color-text-secondary);
}
.el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf,
.el-table--border,
.el-table--group {
border-color: var(--prev-border-color-lighter) !important;
}
.el-table th.el-table__cell,
.el-table tr {
background-color: var(--prev-bg-white);
}
.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell,
.el-table--enable-row-hover .el-table__body tr:hover > td.el-table__cell {
background-color: var(--prev-bg-color);
}
.el-table--border::after,
.el-table--group::after,
.el-table::before {
background-color: var(--prev-border-color-lighter);
}
/* Tag 标签
------------------------------- */
// primary
.el-tag {
color: var(--prev-color-primary);
background-color: var(--prev-color-primary-light-8);
border-color: var(--prev-color-primary-light-6);
}
.el-tag .el-tag__close {
color: var(--prev-color-primary);
&:hover {
color: var(--prev-color-text-white);
background-color: var(--prev-color-primary);
}
}
.el-tag--dark {
color: var(--prev-color-text-white);
background-color: var(--prev-color-primary);
}
.el-tag--dark .el-tag__close {
color: var(--prev-color-text-white);
&:hover {
background-color: var(--prev-color-primary-light-3);
}
}
.el-tag--plain {
color: var(--prev-color-primary);
background-color: var(--prev-bg-white);
border-color: var(--prev-color-primary-light-3);
}
/* Progress 进度条
------------------------------- */
// primary
.el-progress-bar__inner {
background-color: var(--prev-color-primary) !important;
}
.el-progress-bar__outer {
background-color: var(--prev-border-color-lighter);
}
.el-progress__text {
color: var(--prev-color-text-regular);
}
/* Tree 树形控件
------------------------------- */
.el-tree {
background-color: var(--prev-bg-white);
color: var(--prev-color-text-regular);
}
.el-tree-node__content:hover,
.el-tree-node:focus > .el-tree-node__content {
background-color: var(--prev-bg-color);
}
/* Pagination 分页
------------------------------- */
.el-pager li.active,
.el-pager li:hover,
.el-pagination button:hover,
.el-pagination.is-background .el-pager li:not(.disabled):hover {
color: var(--prev-color-primary);
}
.el-pagination__sizes .el-input .el-input__inner:hover {
border-color: var(--prev-color-primary);
}
.el-pagination.is-background .el-pager li:not(.disabled).active {
background-color: var(--prev-color-primary);
color: var(--prev-color-text-white);
}
.el-pagination__total,
.el-pagination__jump {
color: var(--prev-color-text-regular);
}
.el-pagination button:disabled,
.el-pagination .btn-next,
.el-pagination .btn-prev {
background-color: var(--prev-bg-white);
}
.el-pagination .btn-next,
.el-pagination .btn-prev {
color: var(--prev-color-text-primary);
}
.el-pager li {
background-color: var(--prev-bg-white);
color: var(--prev-color-text-primary);
}
/* Badge 标记
------------------------------- */
// primary
.el-badge__content--primary {
background-color: var(--prev-color-primary);
}
/* Loading 加载
------------------------------- */
.el-loading-spinner .path {
stroke: var(--prev-color-primary);
}
.el-loading-spinner .el-loading-text,
.el-loading-spinner i {
color: var(--prev-color-primary);
}
/* Message 消息提示
------------------------------- */
// default/info
.el-message {
min-width: unset !important;
padding: 15px !important;
}
/* MessageBox 弹框
------------------------------- */
.el-message-box__headerbtn:focus .el-message-box__close,
.el-message-box__headerbtn:hover .el-message-box__close {
color: var(--prev-color-primary);
}
.el-message-box {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-lighter);
}
.el-message-box__title {
color: var(--prev-color-text-primary);
}
.el-message-box__content {
color: var(--prev-color-text-regular);
}
/* Notification 通知
------------------------------- */
.el-notification {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-lighter);
.el-notification__title {
color: var(--prev-color-text-primary);
}
.el-notification__content {
color: var(--prev-color-text-regular);
}
}
/* Tabs 标签页
------------------------------- */
.el-tabs__item.is-active,
.el-tabs__item:hover,
.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active,
.el-tabs--border-card > .el-tabs__header .el-tabs__item:not(.is-disabled):hover {
color: var(--prev-color-primary);
}
.el-tabs__active-bar {
background-color: var(--prev-color-primary);
}
.el-tabs__nav-wrap::after {
height: 1px !important;
}
.el-tabs__item {
color: var(--prev-color-text-primary);
}
.el-tabs__nav-wrap::after {
background-color: var(--prev-border-color-light);
}
.el-tabs--card > .el-tabs__header .el-tabs__item.is-active,
.el-tabs--card > .el-tabs__header .el-tabs__item,
.el-tabs--card > .el-tabs__header,
.el-tabs--card > .el-tabs__header .el-tabs__nav {
border-color: var(--prev-bg-color);
}
.el-tabs--border-card {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-base);
}
.el-tabs--border-card > .el-tabs__header {
background-color: var(--prev-bg-color);
border-color: var(--prev-border-color-light);
}
.el-tabs--border-card > .el-tabs__header .el-tabs__item.is-active {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-base);
}
/* Breadcrumb 面包屑
------------------------------- */
.el-breadcrumb__inner a:hover,
.el-breadcrumb__inner.is-link:hover {
color: var(--prev-color-primary);
}
.el-breadcrumb__inner a,
.el-breadcrumb__inner.is-link {
color: var(--bg-topBarColor);
font-weight: normal;
}
.el-breadcrumb__inner a,
.el-breadcrumb__inner.is-link {
color: var(--prev-color-text-black);
opacity: 0.7;
}
.el-breadcrumb__separator {
color: var(--prev-border-color-hover);
}
/* PageHeader 页头
------------------------------- */
.el-page-header__left {
color: var(--prev-color-text-black);
&::after {
background-color: var(--prev-border-color-base);
}
}
.el-page-header__content {
color: var(--prev-color-text-primary);
}
/* Dropdown 下拉菜单
------------------------------- */
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: var(--prev-color-primary);
background-color: var(--prev-color-primary-light-9);
}
.el-dropdown-menu {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-lighter);
}
.el-dropdown-menu__item {
color: var(--prev-color-text-regular);
}
.el-dropdown-menu__item--divided {
border-color: var(--prev-border-color-lighter);
}
.el-dropdown-menu__item--divided:before {
background-color: var(--prev-bg-white);
}
/* Steps 步骤条
------------------------------- */
// default
.el-step__title.is-finish,
.el-step__description.is-finish,
.el-step__head.is-finish {
color: var(--prev-color-primary);
}
.el-step__head.is-finish {
border-color: var(--prev-color-primary);
}
/* Dialog 对话框
------------------------------- */
.el-dialog__headerbtn:focus .el-dialog__close,
.el-dialog__headerbtn:hover .el-dialog__close {
color: var(--prev-color-primary);
}
.el-dialog {
background-color: var(--prev-bg-white);
}
.el-dialog__title {
color: var(--prev-color-text-primary);
}
.el-dialog__body {
color: var(--prev-color-text-regular);
}
/* Popover 弹出框
------------------------------- */
.el-popover {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-lighter);
color: var(--prev-color-text-regular);
}
.el-popover__title {
color: var(--prev-color-text-primary);
}
/* Card 卡片
------------------------------- */
.el-card {
color: var(--prev-color-text-primary);
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-lighter);
}
.el-card__header {
padding: 15px 20px;
border-bottom-color: var(--prev-border-color-lighter);
}
/* Collapse 折叠面板
------------------------------- */
.el-collapse {
border-color: var(--prev-border-color-lighter);
}
.el-collapse-item__header {
color: var(--prev-color-text-primary);
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-lighter);
}
.el-collapse-item__wrap {
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-lighter);
}
.el-collapse-item__content {
color: var(--prev-color-text-primary);
}
/* Timeline 时间线
------------------------------- */
// primary
.el-timeline-item__node--primary {
background-color: var(--prev-color-primary);
}
.el-timeline-item__content {
color: var(--prev-color-text-primary);
}
/* Divider 分割线
------------------------------- */
.el-divider {
background-color: var(--prev-border-color-base);
}
/* Calendar 日历
------------------------------- */
.el-calendar-table td {
color: var(--prev-color-text-black);
}
.el-calendar-table td.is-today {
color: var(--prev-color-primary) !important;
background-color: var(--prev-color-primary-light-9);
}
.el-calendar-table .el-calendar-day:hover,
.el-calendar-table td.is-selected {
background-color: var(--prev-color-primary-light-9);
color: var(--prev-color-primary) !important;
}
.el-calendar {
background-color: var(--prev-bg-white);
}
.el-calendar__title {
color: var(--prev-color-text-black);
}
.el-calendar__header,
.el-calendar-table tr:first-child td,
.el-calendar-table td,
.el-calendar-table tr td:first-child {
border-color: var(--prev-border-color-lighter);
}
.el-calendar-table thead th {
color: var(--prev-color-text-regular);
}
.el-calendar-table:not(.is-range) td.next,
.el-calendar-table:not(.is-range) td.prev {
color: var(--prev-color-text-placeholder);
}
.el-calendar__button-group {
.el-button {
color: var(--prev-color-text-regular);
background-color: var(--prev-bg-white);
border-color: var(--prev-border-color-base);
&:focus,
&:hover {
color: var(--prev-color-primary) !important;
background: var(--prev-color-primary-light-8) !important;
border-color: var(--prev-color-primary-light-6) !important;
}
&:active {
color: var(--prev-color-primary-light-3);
}
}
}
/* Backtop 回到顶部
------------------------------- */
.el-backtop {
color: var(--prev-color-primary);
&:hover {
background-color: var(--prev-color-primary-light-9);
}
}
/* scrollbar
------------------------------- */
.el-scrollbar__wrap {
overflow-x: hidden !important;
max-height: 100%; /*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
}
.el-select-dropdown .el-scrollbar__wrap {
overflow-x: scroll !important;
}
/* Drawer 抽屉
------------------------------- */
.el-drawer,
.el-divider__text {
background-color: var(--prev-bg-white);
}
.el-divider__text {
color: var(--prev-color-text-primary);
}
.el-drawer__close-btn:hover {
color: var(--prev-color-primary);
}
.el-drawer__body {
width: 100%;
height: 100%;
overflow: auto;
}
.el-drawer__header {
padding: 0 15px !important;
height: 50px;
display: flex;
align-items: center;
margin-bottom: 0 !important;
border-bottom: 1px solid var(--prev-border-color-lighter);
color: var(--prev-color-text-primary);
}

View File

@@ -0,0 +1,4 @@
import { Local } from '@/utils/storage.js';
// 全局组件大小
export const globalComponentSize = Local.get('themeConfigPrev') ? Local.get('themeConfigPrev').globalComponentSize : '';

134
src/utils/formatTime.js Normal file
View File

@@ -0,0 +1,134 @@
/*
* 年(Y) 可用1-4个占位符
* 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
* 星期(W) 可用1-3个占位符
* 季度(q为阿拉伯数字Q为中文数字)可用1或4个占位符
*
* let date = new Date()
* formatDate(date, "YYYY-mm-dd HH:MM:SS") // 2020-02-09 14:04:23
* formatDate(date, "YYYY-mm-dd HH:MM:SS Q") // 2020-02-09 14:09:03 一
* formatDate(date, "YYYY-mm-dd HH:MM:SS WWW") // 2020-02-09 14:45:12 星期日
* formatDate(date, "YYYY-mm-dd HH:MM:SS QQQQ") // 2020-02-09 14:09:36 第一季度
* formatDate(date, "YYYY-mm-dd HH:MM:SS WWW QQQQ") // 2020-02-09 14:46:12 星期日 第一季度
*/
export function formatDate(date, format) {
let we = date.getDay(); // 星期
let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
const opt = {
'Y+': date.getFullYear().toString(), // 年
'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始要+1)
'd+': date.getDate().toString(), // 日
'H+': date.getHours().toString(), // 时
'M+': date.getMinutes().toString(), // 分
'S+': date.getSeconds().toString(), // 秒
'q+': qut, // 季度
};
const week = {
// 中文数字 (星期)
'0': '日',
'1': '一',
'2': '二',
'3': '三',
'4': '四',
'5': '五',
'6': '六',
};
const quarter = {
// 中文数字(季度)
'1': '一',
'2': '二',
'3': '三',
'4': '四',
};
if (/(W+)/.test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
}
if (/(Q+)/.test(format)) {
// 输入一个Q只输出一个中文数字输入4个Q则拼接上字符串
format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
}
for (let k in opt) {
let r = new RegExp('(' + k + ')').exec(format);
if (r) {
// 若输入的长度不为1则前面补零
format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
}
}
return format;
}
/**
* 10秒 10 * 1000
* 1分 60 * 1000
* 1小时 60 * 60 * 1000
* 24小时60 * 60 * 24 * 1000
* 3天 60 * 60* 24 * 1000 * 3
*
* let data = new Date()
* formatPast(data) // 刚刚
* formatPast(data - 11 * 1000) // 11秒前
* formatPast(data - 2 * 60 * 1000) // 2分钟前
* formatPast(data - 60 * 60 * 2 * 1000) // 2小时前
* formatPast(data - 60 * 60 * 2 * 1000) // 2小时前
* formatPast(data - 60 * 60 * 71 * 1000) // 2天前
* formatPast("2020-06-01") // 2020-06-01
* formatPast("2020-06-01", "YYYY-mm-dd HH:MM:SS WWW QQQQ") // 2020-06-01 08:00:00 星期一 第二季度
*/
export function formatPast(param, format = 'YYYY-mm-dd') {
// 传入格式处理、存储转换值
let t, s;
// 获取js 时间戳
let time = new Date().getTime();
// 是否是对象
typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
// 当前时间戳 - 传入时间戳
time = Number.parseInt(time - t);
if (time < 10000) {
// 10秒内
return '刚刚';
} else if (time < 60000 && time >= 10000) {
// 超过10秒少于1分钟内
s = Math.floor(time / 1000);
return `${s}秒前`;
} else if (time < 3600000 && time >= 60000) {
// 超过1分钟少于1小时
s = Math.floor(time / 60000);
return `${s}分钟前`;
} else if (time < 86400000 && time >= 3600000) {
// 超过1小时少于24小时
s = Math.floor(time / 3600000);
return `${s}小时前`;
} else if (time < 259200000 && time >= 86400000) {
// 超过1天少于3天内
s = Math.floor(time / 86400000);
return `${s}天前`;
} else {
// 超过3天
let date = typeof param === 'string' || 'object' ? new Date(param) : param;
return formatDate(date, format);
}
}
/**
* formatAxis(new Date()) // 上午好
*/
export function formatAxis(param) {
let hour = new Date(param).getHours();
if (hour < 6) {
return '凌晨好';
} else if (hour < 9) {
return '早上好';
} else if (hour < 12) {
return '上午好';
} else if (hour < 14) {
return '中午好';
} else if (hour < 17) {
return '下午好';
} else if (hour < 19) {
return '傍晚好';
} else if (hour < 22) {
return '晚上好';
} else {
return '夜里好';
}
}

View File

@@ -0,0 +1,99 @@
import Vue from 'vue';
// 获取阿里字体图标
const getAlicdnIconfont = () => {
return new Promise((resolve, reject) => {
Vue.nextTick(() => {
const styles = document.styleSheets;
let sheetsList = [];
let sheetsIconList = [];
for (let i = 0; i < styles.length; i++) {
if (styles[i].href && styles[i].href.indexOf('at.alicdn.com') > -1) {
sheetsList.push(styles[i]);
}
}
for (let i = 0; i < sheetsList.length; i++) {
for (let j = 0; j < sheetsList[i].cssRules.length; j++) {
if (sheetsList[i].cssRules[j].selectorText && sheetsList[i].cssRules[j].selectorText.indexOf('.icon-') > -1) {
sheetsIconList.push(
`${sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length).replace(/\\:\\:before/gi, '')}`
);
}
}
}
if (sheetsIconList.length > 0) resolve(sheetsIconList);
else reject('未获取到值,请刷新重试');
});
});
};
// 初始化获取 css 样式,获取 element plus 自带图标
const getElementPlusIconfont = () => {
return new Promise((resolve, reject) => {
Vue.nextTick(() => {
const styles = document.styleSheets;
let sheetsIconList = [];
for (let i = 0; i < styles.length; i++) {
for (let j = 0; j < styles[i].cssRules.length; j++) {
if (styles[i].cssRules[j].selectorText && styles[i].cssRules[j].selectorText.indexOf('.el-icon-') === 0) {
sheetsIconList.push(
`${styles[i].cssRules[j].selectorText.substring(1, styles[i].cssRules[j].selectorText.length).replace(/\\:\\:before/gi, '')}`
);
}
}
}
if (sheetsIconList.length > 0) resolve(sheetsIconList);
else reject('未获取到值,请刷新重试');
});
});
};
// 初始化获取 css 样式,这里使用 fontawesome 的图标
const getAwesomeIconfont = () => {
return new Promise((resolve, reject) => {
Vue.nextTick(() => {
const styles = document.styleSheets;
let sheetsList = [];
let sheetsIconList = [];
for (let i = 0; i < styles.length; i++) {
if (styles[i].href && styles[i].href.indexOf('netdna.bootstrapcdn.com') > -1) {
sheetsList.push(styles[i]);
}
}
for (let i = 0; i < sheetsList.length; i++) {
for (let j = 0; j < sheetsList[i].cssRules.length; j++) {
if (
sheetsList[i].cssRules[j].selectorText &&
sheetsList[i].cssRules[j].selectorText.indexOf('.fa-') === 0 &&
sheetsList[i].cssRules[j].selectorText.indexOf(',') === -1
) {
sheetsIconList.push(
`${sheetsList[i].cssRules[j].selectorText.substring(1, sheetsList[i].cssRules[j].selectorText.length).replace(/\\:\\:before/gi, '')}`
);
}
}
}
if (sheetsIconList.length > 0) resolve(sheetsIconList);
else reject('未获取到值,请刷新重试');
});
});
};
// 定义导出方法集合
const initIconfont = {
// iconfont
ali: () => {
return getAlicdnIconfont();
},
// element plus
ele: () => {
return getElementPlusIconfont();
},
// fontawesome
awe: () => {
return getAwesomeIconfont();
},
};
// 导出方法
export default initIconfont;

46
src/utils/loading.js Normal file
View File

@@ -0,0 +1,46 @@
import Vue from 'vue';
import loadingCss from '@/theme/loading.scss';
// 定义方法
export const PrevLoading = {
// 载入 css
setCss: () => {
let link = document.createElement('link');
link.rel = 'stylesheet';
link.href = loadingCss;
link.crossOrigin = 'anonymous';
document.getElementsByTagName('head')[0].appendChild(link);
},
// 创建 loading
start: () => {
const bodys = document.body;
const div = document.createElement('div');
div.setAttribute('class', 'loading-prev');
const htmls = `
<div class="loading-prev-box">
<div class="loading-prev-box-warp">
<div class="loading-prev-box-item"></div>
<div class="loading-prev-box-item"></div>
<div class="loading-prev-box-item"></div>
<div class="loading-prev-box-item"></div>
<div class="loading-prev-box-item"></div>
<div class="loading-prev-box-item"></div>
<div class="loading-prev-box-item"></div>
<div class="loading-prev-box-item"></div>
<div class="loading-prev-box-item"></div>
</div>
</div>
`;
div.innerHTML = htmls;
bodys.insertBefore(div, bodys.childNodes[0]);
},
// 移除 loading
done: () => {
Vue.nextTick(() => {
setTimeout(() => {
const el = document.querySelector('.loading-prev');
el && el.parentNode?.removeChild(el);
}, 1000);
});
},
};

65
src/utils/request.js Normal file
View File

@@ -0,0 +1,65 @@
import store from '@/store';
import router, { resetRouter } from '@/router/index';
import axios from 'axios';
import { Message, MessageBox } from 'element-ui';
import { Session } from '@/utils/storage';
// 创建 axios 实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 50000,
headers: { 'Content-Type': 'application/json' },
});
// 添加请求拦截器
service.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么 token
if (Session.get('token')) {
config.headers.common['Authorization'] = `${Session.get('token')}`;
}
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
service.interceptors.response.use(
(response) => {
// 对响应数据做点什么
const res = response.data;
if (res.code && res.code !== 0) {
// `token` 过期或者账号已在别处登录
if (res.code === 401 || res.code === 4001) {
// 清除浏览器全部临时缓存
Session.clear();
router.push('/login');
store.commit('setMenuData', {});
resetRouter(); // 重置路由
MessageBox.alert('你已被登出,请重新登录', '提示', {})
.then(() => {})
.catch(() => {});
}
return Promise.reject(service.interceptors.response.error);
} else {
return response.data;
}
},
(error) => {
// 对响应错误做点什么
if (error.message.indexOf('timeout') != -1) {
Message.error('网络超时');
} else if (error.message == 'Network Error') {
Message.error('网络连接错误');
} else {
Message.error(error.response.data.message);
}
return Promise.reject(error);
}
);
// 导出 axios 实例
export default service;

39
src/utils/setIconfont.js Normal file
View File

@@ -0,0 +1,39 @@
// 字体图标 url
const cssCdnUrlList = ['//at.alicdn.com/t/font_2348547_zjuiclx86c.css', '//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css'];
// 第三方 js url
const jsCdnUrlList = [];
// 动态设置字体图标
export function setCssCdn() {
if (cssCdnUrlList.length <= 0) return false;
cssCdnUrlList.map((v) => {
let link = document.createElement('link');
link.rel = 'stylesheet';
link.href = v;
link.crossOrigin = 'anonymous';
document.getElementsByTagName('head')[0].appendChild(link);
});
}
// 批量设置第三方js
export function setJsCdn() {
if (jsCdnUrlList.length <= 0) return false;
jsCdnUrlList.map((v) => {
let link = document.createElement('script');
link.src = v;
document.body.appendChild(link);
});
}
// 设置执行函数
const setIntroduction = {
cssCdn: () => {
setCssCdn();
},
jsCdn: () => {
setJsCdn();
},
};
// 导出函数方法
export default setIntroduction;

41
src/utils/storage.js Normal file
View File

@@ -0,0 +1,41 @@
// 1、window.localStorage 浏览器永久缓存
export const Local = {
// 设置永久缓存
set(key, val) {
window.localStorage.setItem(key, JSON.stringify(val));
},
// 获取永久缓存
get(key) {
let json = window.localStorage.getItem(key);
return JSON.parse(json);
},
// 移除永久缓存
remove(key) {
window.localStorage.removeItem(key);
},
// 移除全部永久缓存
clear() {
window.localStorage.clear();
},
};
// 2、window.sessionStorage 浏览器临时缓存
export const Session = {
// 设置临时缓存
set(key, val) {
window.sessionStorage.setItem(key, JSON.stringify(val));
},
// 获取临时缓存
get(key) {
let json = window.sessionStorage.getItem(key);
return JSON.parse(json);
},
// 移除临时缓存
remove(key) {
window.sessionStorage.removeItem(key);
},
// 移除全部临时缓存
clear() {
window.sessionStorage.clear();
},
};

63
src/utils/theme.js Normal file
View File

@@ -0,0 +1,63 @@
import { Message } from 'element-ui';
/**
* 颜色转换函数
* @method hexToRgb hex 颜色转 rgb 颜色
* @method rgbToHex rgb 颜色转 Hex 颜色
* @method getDarkColor 加深颜色值
* @method getLightColor 变浅颜色值
*/
export function useChangeColor() {
// str 颜色值字符串
const hexToRgb = (str) => {
let hexs = '';
let reg = /^#?[0-9A-Fa-f]{6}$/;
if (!reg.test(str)) {
Message.warning('输入错误的hex');
return '';
}
str = str.replace('#', '');
hexs = str.match(/../g);
for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16);
return hexs;
};
// r 代表红色 | g 代表绿色 | b 代表蓝色
const rgbToHex = (r, g, b) => {
let reg = /^\d{1,3}$/;
if (!reg.test(r) || !reg.test(g) || !reg.test(b)) {
Message.warning('输入错误的rgb颜色值');
return '';
}
let hexs = [r.toString(16), g.toString(16), b.toString(16)];
for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`;
return `#${hexs.join('')}`;
};
// color 颜色值字符串 | level 变浅的程度限0-1之间
const getDarkColor = (color, level) => {
let reg = /^#?[0-9A-Fa-f]{6}$/;
if (!reg.test(color)) {
Message.warning('输入错误的hex颜色值');
return '';
}
let rgb = useChangeColor().hexToRgb(color);
for (let i = 0; i < 3; i++) rgb[i] = Math.floor(rgb[i] * (1 - level));
return useChangeColor().rgbToHex(rgb[0], rgb[1], rgb[2]);
};
// color 颜色值字符串 | level 加深的程度限0-1之间
const getLightColor = (color, level) => {
let reg = /^#?[0-9A-Fa-f]{6}$/;
if (!reg.test(color)) {
Message.warning('输入错误的hex颜色值');
return '';
}
let rgb = useChangeColor().hexToRgb(color);
for (let i = 0; i < 3; i++) rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]);
return useChangeColor().rgbToHex(rgb[0], rgb[1], rgb[2]);
};
return {
hexToRgb,
rgbToHex,
getDarkColor,
getLightColor,
};
}

283
src/utils/toolsValidate.js Normal file
View File

@@ -0,0 +1,283 @@
/**
* 2020.11.29 lyt 整理
* 工具类集合,适用于平时开发
*/
/**
* 验证百分比(不可以小数)
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyNumberPercentage(val) {
// 匹配空格
let v = val.replace(/(^\s*)|(\s*$)/g, '');
// 只能是数字和小数点,不能是其他输入
v = v.replace(/[^\d]/g, '');
// 不能以0开始
v = v.replace(/^0/g, '');
// 数字超过100赋值成最大值100
v = v.replace(/^[1-9]\d\d{1,3}$/, '100');
// 返回结果
return v;
}
/**
* 验证百分比(可以小数)
* @param val 当前值字符串
* @returns 返回处理后的字符串
*/
export function verifyNumberPercentageFloat(val) {
let v = verifyNumberIntegerAndFloat(val);
// 数字超过100赋值成最大值100
v = v.replace(/^[1-9]\d\d{1,3}$/, '100');
// 超过100之后不给再输入值
v = v.replace(/^100\.$/, '100');
// 返回结果
return v;
}
// 小数或整数(不可以负数)
export function verifyNumberIntegerAndFloat(val) {
// 匹配空格
let v = val.replace(/(^\s*)|(\s*$)/g, '');
// 只能是数字和小数点,不能是其他输入
v = v.replace(/[^\d.]/g, '');
// 以0开始只能输入一个
v = v.replace(/^0{2}$/g, '0');
// 保证第一位只能是数字,不能是点
v = v.replace(/^\./g, '');
// 小数只能出现1位
v = v.replace('.', '$#$').replace(/\./g, '').replace('$#$', '.');
// 小数点后面保留2位
v = v.replace(/^(\\-)*(\d+)\.(\d\d).*$/, '$1$2.$3');
// 返回结果
return v;
}
// 正整数验证
export function verifiyNumberInteger(val) {
// 匹配空格
let v = val.replace(/(^\s*)|(\s*$)/g, '');
// 去掉 '.' , 防止贴贴的时候出现问题 如 0.1.12.12
v = v.replace(/[\\.]*/g, '');
// 去掉以 0 开始后面的数, 防止贴贴的时候出现问题 如 00121323
v = v.replace(/(^0[\d]*)$/g, '0');
// 首位是0,只能出现一次
v = v.replace(/^0\d$/g, '0');
// 只匹配数字
v = v.replace(/[^\d]/g, '');
// 返回结果
return v;
}
// 去掉中文及空格
export function verifyCnAndSpace(val) {
// 匹配中文与空格
let v = val.replace(/[\u4e00-\u9fa5\s]+/g, '');
// 匹配空格
v = v.replace(/(^\s*)|(\s*$)/g, '');
// 返回结果
return v;
}
// 去掉英文及空格
export function verifyEnAndSpace(val) {
// 匹配英文与空格
let v = val.replace(/[a-zA-Z]+/g, '');
// 匹配空格
v = v.replace(/(^\s*)|(\s*$)/g, '');
// 返回结果
return v;
}
// 禁止输入空格
export function verifyAndSpace(val) {
// 匹配空格
let v = val.replace(/(^\s*)|(\s*$)/g, '');
// 返回结果
return v;
}
// 金额用 `,` 区分开
export function verifyNumberComma(val) {
// 调用小数或整数(不可以负数)方法
let v = verifyNumberIntegerAndFloat(val);
// 字符串转成数组
v = v.toString().split('.');
// \B 匹配非单词边界,两边都是单词字符或者两边都是非单词字符
v[0] = v[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
// 数组转字符串
v = v.join('.');
// 返回结果
return v;
}
// 匹配文字变色(搜索时)
export function verifyTextColor(val, text = '', color = 'red') {
// 返回内容,添加颜色
let v = text.replace(new RegExp(val, 'gi'), `<span style='color: ${color}'>${val}</span>`);
// 返回结果
return v;
}
// 数字转中文大写
export function verifyNumberCnUppercase(val, unit = '仟佰拾亿仟佰拾万仟佰拾元角分', v = '') {
// 当前内容字符串添加 2个0为什么??
val += '00';
// 返回某个指定的字符串值在字符串中首次出现的位置,没有出现,则该方法返回 -1
let lookup = val.indexOf('.');
// substring不包含结束下标内容substr包含结束下标内容
if (lookup >= 0) val = val.substring(0, lookup) + val.substr(lookup + 1, 2);
// 根据内容 val 的长度,截取返回对应大写
unit = unit.substr(unit.length - val.length);
// 循环截取拼接大写
for (let i = 0; i < val.length; i++) {
v += '零壹贰叁肆伍陆柒捌玖'.substr(val.substr(i, 1), 1) + unit.substr(i, 1);
}
// 正则处理
v = v
.replace(/零角零分$/, '整')
.replace(/零[仟佰拾]/g, '零')
.replace(/零{2,}/g, '零')
.replace(/零([亿|万])/g, '$1')
.replace(/零+元/, '元')
.replace(/亿零{0,3}万/, '亿')
.replace(/^元/, '零元');
// 返回结果
return v;
}
// 手机号码
export function verifyPhone(val) {
// false: 手机号码不正确
if (!/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\d{8}$/.test(val)) return false;
// true: 手机号码正确
else return true;
}
// 国内电话号码
export function verifyTelPhone(val) {
// false: 国内电话号码不正确
if (!/\d{3}-\d{8}|\d{4}-\d{7}/.test(val)) return false;
// true: 国内电话号码正确
else return true;
}
// 登录账号 (字母开头允许5-16字节允许字母数字下划线)
export function verifyAccount(val) {
// false: 登录账号不正确
if (!/^[a-zA-Z][a-zA-Z0-9_]{4,15}$/.test(val)) return false;
// true: 登录账号正确
else return true;
}
// 密码 (以字母开头长度在6~16之间只能包含字母、数字和下划线)
export function verifyPassword(val) {
// false: 密码不正确
if (!/^[a-zA-Z]\w{5,15}$/.test(val)) return false;
// true: 密码正确
else return true;
}
// 强密码 (字母+数字+特殊字符长度在6-16之间)
export function verifyPasswordPowerful(val) {
// false: 强密码不正确
if (
!/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\\.*]+$)(?![\d!@#$%^&\\.*]+$)[a-zA-Z\d!@#$%^&\\.*]{6,16}$/.test(val)
)
return false;
// true: 强密码正确
else return true;
}
// 密码强度
export function verifyPasswordStrength(val) {
let v = '';
// 弱:纯数字,纯字母,纯特殊字符
if (/^(?:\d+|[a-zA-Z]+|[!@#$%^&\\.*]+){6,16}$/.test(val)) v = '弱';
// 中:字母+数字,字母+特殊字符,数字+特殊字符
if (/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\\.*]+$)[a-zA-Z\d!@#$%^&\\.*]{6,16}$/.test(val)) v = '中';
// 强:字母+数字+特殊字符
if (
/^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&\\.*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&\\.*]+$)(?![\d!@#$%^&\\.*]+$)[a-zA-Z\d!@#$%^&\\.*]{6,16}$/.test(val)
)
v = '强';
// 返回结果
return v;
}
// IP地址
export function verifyIPAddress(val) {
// false: IP地址不正确
if (
!/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/.test(
val
)
)
return false;
// true: IP地址正确
else return true;
}
// 邮箱
export function verifyEmail(val) {
// false: 邮箱不正确
if (
!/^(([^<>()\\[\]\\.,;:\s@"]+(\.[^<>()\\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
val
)
)
return false;
// true: 邮箱正确
else return true;
}
// 身份证
export function verifyIdCard(val) {
// false: 身份证不正确
if (!/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(val)) return false;
// true: 身份证正确
else return true;
}
// 姓名
export function verifyFullName(val) {
// false: 姓名不正确
if (!/^[\u4e00-\u9fa5]{1,6}(·[\u4e00-\u9fa5]{1,6}){0,2}$/.test(val)) return false;
// true: 姓名正确
else return true;
}
// 邮政编码
export function verifyPostalCode(val) {
// false: 邮政编码不正确
if (!/^[1-9][0-9]{5}$/.test(val)) return false;
// true: 邮政编码正确
else return true;
}
// url
export function verifyUrl(val) {
// false: url不正确
if (
!/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
val
)
)
return false;
// true: url正确
else return true;
}
// 车牌号
export function verifyCarNum(val) {
// false: 车牌号不正确
if (
!/^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$/.test(
val
)
)
return false;
// true车牌号正确
else return true;
}

95
src/views/error/401.vue Normal file
View File

@@ -0,0 +1,95 @@
<template>
<div class="error">
<div class="error-flex">
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">401</div>
<div class="left-item-animation left-item-title">{{ $t('message.noAccess.accessTitle') }}</div>
<div class="left-item-animation left-item-msg">{{ $t('message.noAccess.accessMsg') }}</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" round @click="onSetAuth">{{ $t('message.noAccess.accessBtn') }}</el-button>
</div>
</div>
</div>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/3333f265772a4fa89287993500ecbf96.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
</div>
</div>
</div>
</template>
<script>
import { Session } from '@/utils/storage';
export default {
name: 'noAuth',
methods: {
// 重新授权
onSetAuth() {
// 清除缓存/token等
Session.clear();
// 使用 reload 时,不需要调用 resetRoute() 重置路由
window.location.reload();
},
},
};
</script>
<style scoped lang="scss">
.error {
height: 100%;
background-color: white;
display: flex;
.error-flex {
margin: auto;
display: flex;
height: 350px;
width: 900px;
.left {
flex: 1;
height: 100%;
align-items: center;
display: flex;
.left-item {
.left-item-animation {
opacity: 0;
animation-name: error-num;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
.left-item-num {
color: #d6e0f6;
font-size: 55px;
}
.left-item-title {
font-size: 20px;
color: #333333;
margin: 15px 0 5px 0;
animation-delay: 0.1s;
}
.left-item-msg {
color: #c0bebe;
font-size: 12px;
margin-bottom: 30px;
animation-delay: 0.2s;
}
.left-item-btn {
animation-delay: 0.2s;
}
}
}
.right {
flex: 1;
opacity: 0;
animation-name: error-img;
animation-duration: 2s;
animation-fill-mode: forwards;
img {
width: 100%;
height: 100%;
}
}
}
}
</style>

91
src/views/error/404.vue Normal file
View File

@@ -0,0 +1,91 @@
<template>
<div class="error">
<div class="error-flex">
<div class="left">
<div class="left-item">
<div class="left-item-animation left-item-num">404</div>
<div class="left-item-animation left-item-title">{{ $t('message.notFound.foundTitle') }}</div>
<div class="left-item-animation left-item-msg">{{ $t('message.notFound.foundMsg') }}</div>
<div class="left-item-animation left-item-btn">
<el-button type="primary" round @click="onGoHome">{{ $t('message.notFound.foundBtn') }}</el-button>
</div>
</div>
</div>
<div class="right">
<img
src="https://img-blog.csdnimg.cn/9eb1d85a417f4ed1ba7107f149ce3da1.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAbHl0LXRvcA==,size_16,color_FFFFFF,t_70,g_se,x_16"
/>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'noFound',
methods: {
// 去首页
onGoHome() {
this.$router.push('/');
},
},
};
</script>
<style scoped lang="scss">
.error {
height: 100%;
background-color: white;
display: flex;
.error-flex {
margin: auto;
display: flex;
height: 350px;
width: 900px;
.left {
flex: 1;
height: 100%;
align-items: center;
display: flex;
.left-item {
.left-item-animation {
opacity: 0;
animation-name: error-num;
animation-duration: 0.5s;
animation-fill-mode: forwards;
}
.left-item-num {
color: #d6e0f6;
font-size: 55px;
}
.left-item-title {
font-size: 20px;
color: #333333;
margin: 15px 0 5px 0;
animation-delay: 0.1s;
}
.left-item-msg {
color: #c0bebe;
font-size: 12px;
margin-bottom: 30px;
animation-delay: 0.2s;
}
.left-item-btn {
animation-delay: 0.2s;
}
}
}
.right {
flex: 1;
opacity: 0;
animation-name: error-img;
animation-duration: 2s;
animation-fill-mode: forwards;
img {
width: 100%;
height: 100%;
}
}
}
}
</style>

View File

@@ -0,0 +1,89 @@
<template>
<div>
<el-card shadow="hover" header="在线签名演示">
<el-alert
title="感谢优秀的 `sign-canvas`项目地址https://github.com/langyuxiansheng/vue-sign-canvas"
type="success"
:closable="false"
class="mb15"
></el-alert>
<el-button type="primary" size="small" icon="el-icon-edit" @click="onSignCanvasClick">点击进行签名</el-button>
</el-card>
<el-dialog title="在线签名" :visible.sync="signVisible" :close-on-click-modal="true" :close-on-press-escape="true" :width="signDialogWidth">
<sign-canvas class="sign-canvas" ref="SignCanvas" :options="signOptions" v-model="signValue" />
<div slot="footer" class="dialog-footer">
<el-button size="small" @click="onCancelSign">取消</el-button>
<el-button type="danger" size="small" @click="onCanvasClear">清空</el-button>
<el-button type="primary" size="small" @click="onSaveAsImg">保存</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import SignCanvas from 'sign-canvas';
export default {
name: 'funSignCanvas',
components: {
SignCanvas,
},
data() {
return {
signVisible: false,
signDialogWidth: '',
signValue: '',
signOptions: {
lastWriteSpeed: 1,
lastWriteWidth: 2,
lineCap: 'round',
lineJoin: 'round',
canvasWidth: 729,
canvasHeight: 460,
isShowBorder: false,
bgColor: '#E6E6E6',
borderWidth: 1,
borderColor: '#ff787f',
writeWidth: 5,
maxWriteWidth: 30,
minWriteWidth: 5,
writeColor: '#101010',
isSign: true,
imgType: 'png',
},
};
},
mounted() {
this.initSignConfig();
window.addEventListener('resize', this.initSignConfig);
},
methods: {
// 初始化配置信息
initSignConfig() {
this.signDialogWidth = `${document.body.offsetWidth / 2 + 40}px`;
this.signOptions.canvasWidth = document.body.offsetWidth / 2;
this.signOptions.canvasHeight = document.body.offsetHeight / 2;
},
// 打开签名弹窗
onSignCanvasClick() {
this.signVisible = true;
},
// 取消签名
onCancelSign() {
this.signVisible = false;
this.onCanvasClear();
},
// 清空签名
onCanvasClear() {
this.$refs.SignCanvas.canvasClear();
},
// 保存签名
onSaveAsImg() {
const img = this.$refs.SignCanvas.saveAsImg();
console.log(img);
},
},
destroyed() {
window.removeEventListener('resize', this.initSignConfig);
},
};
</script>

View File

@@ -0,0 +1,121 @@
<template>
<div>
<el-card shadow="hover" header="tagsView 非当前页演示">
<el-form :model="formInline" size="small" label-width="40px" class="tags-view-form">
<el-row :gutter="35">
<el-col :xs="24" :sm="8" :md="8" :lg="6" :xl="4" class="tags-view-form-col">
<el-form-item label="功能">
<el-select v-model="formInline.selectId" placeholder="请选择" class="w100">
<el-option v-for="item in selectOptions" :key="item.value" :label="item.label" :value="item.value"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="6" :xl="4" class="tags-view-form-col">
<el-form-item label="路径">
<el-input v-model="formInline.path" placeholder="路径如:/fun/tagsView"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="8" :md="8" :lg="6" :xl="4">
<el-form-item>
<el-button type="primary" @click="onImplementClick" icon="el-icon-thumb">点击执行</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<el-card shadow="hover" header="tagsView 当前页演示" class="mt15">
<div class="flex-warp">
<div class="flex-warp-item">
<div class="flex-warp-item-box">
<el-button type="primary" size="small" icon="el-icon-refresh-right" @click="refreshCurrentTagsView">刷新当前页 </el-button>
</div>
</div>
<div class="flex-warp-item">
<div class="flex-warp-item-box">
<el-button type="info" size="small" icon="el-icon-close" @click="closeCurrentTagsView">关闭当前页</el-button>
</div>
</div>
<div class="flex-warp-item">
<div class="flex-warp-item-box">
<el-button type="warning" size="small" icon="el-icon-circle-close" @click="closeOtherTagsView">关闭其它 </el-button>
</div>
</div>
<div class="flex-warp-item">
<div class="flex-warp-item-box">
<el-button type="danger" size="small" icon="el-icon-folder-delete" @click="closeAllTagsView">全部关闭 </el-button>
</div>
</div>
</div>
</el-card>
</div>
</template>
<script>
export default {
name: 'funTagsView',
data() {
return {
formInline: {
path: '',
selectId: 0,
},
selectOptions: [
{
value: 0,
label: '刷新当前',
},
{
value: 1,
label: '关闭当前',
},
{
value: 2,
label: '关闭其它',
},
{
value: 3,
label: '关闭全部',
},
],
};
},
methods: {
// 0 刷新当前1 关闭当前2 关闭其它3 关闭全部
// 1、刷新当前 tagsView
refreshCurrentTagsView() {
this.bus.$emit('onCurrentContextmenuClick', {
id: 0,
path: this.$route.path,
});
},
// 2、关闭当前 tagsView
closeCurrentTagsView() {
this.bus.$emit('onCurrentContextmenuClick', {
id: 1,
path: this.$route.path,
});
},
// 3、关闭其它 tagsView
closeOtherTagsView() {
this.bus.$emit('onCurrentContextmenuClick', {
id: 2,
path: this.$route.path,
});
},
// 4、关闭全部 tagsView
closeAllTagsView() {
this.bus.$emit('onCurrentContextmenuClick', {
id: 3,
path: this.$route.path,
});
},
// 执行点击
onImplementClick() {
this.bus.$emit('onCurrentContextmenuClick', {
id: this.formInline.selectId,
path: this.formInline.path,
});
},
},
};
</script>

170
src/views/home/index.scss Normal file
View File

@@ -0,0 +1,170 @@
.home {
width: 100%;
overflow: hidden;
.home-card-more {
float: right;
padding: 3px 0;
font-size: 13px;
}
.home-card-time {
float: right;
font-size: 13px;
width: 130px;
margin-top: -4px;
}
.user-item {
height: 198px;
display: flex;
align-items: center;
.user-item-left {
width: 100px;
height: 130px;
border-radius: 4px;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
.user-item-right {
flex: 1;
padding: 15px;
.right-title {
font-size: 20px;
}
.right-l-v {
font-size: 13px;
display: flex;
.right-label {
color: var(--prev-color-text-regular);
width: 40px;
}
.right-value {
flex: 1;
}
}
}
}
.info {
height: 198px;
.info-scroll {
height: 100%;
overflow: hidden;
.info-ul {
list-style: none;
.info-item {
display: flex;
font-size: 13px;
color: var(--prev-color-text-regular);
height: 28px;
line-height: 28px;
&:hover {
color: var(--prev-color-primary);
cursor: pointer;
}
.info-item-left {
flex: 1;
flex-shrink: 0;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.info-item-right {
width: 60px;
text-align: right;
}
}
}
}
}
.home-recommend-row {
.home-recommend {
position: relative;
height: 100px;
color: var(--prev-color-text-white);
border-radius: 4px;
overflow: hidden;
cursor: pointer;
&:hover {
i {
right: 0px !important;
bottom: 0px !important;
transition: all ease 0.3s;
}
}
i {
position: absolute;
right: -10px;
bottom: -10px;
font-size: 70px;
transform: rotate(-30deg);
transition: all ease 0.3s;
}
.home-recommend-auto {
padding: 15px;
position: absolute;
left: 0;
top: 5%;
.home-recommend-msg {
font-size: 12px;
margin-top: 10px;
}
}
}
}
.charts {
width: 100%;
height: 282.6px;
display: flex;
padding: 12px 15px;
.charts-left {
flex: 1;
height: 100%;
}
.charts-right {
flex: 1;
height: 100%;
}
}
.home-charts {
height: 282.6px;
.home-charts-item {
background-color: var(--prev-bg-main-color);
padding: 19px 15px;
border-radius: 2px;
display: flex;
align-items: center;
margin-bottom: 12px;
cursor: pointer;
&:last-of-type {
margin-bottom: 0;
}
&:hover {
.home-charts-item-right {
i {
transform: rotate(45deg);
transition: all ease 0.3s;
}
}
}
.home-charts-item-left {
flex: 1;
.home-charts-item-title {
font-size: 13px;
}
.home-charts-item-num {
font-size: 20px;
margin-top: 5px;
}
}
.home-charts-item-right {
i {
font-size: 20px;
padding: 8px;
border-radius: 100%;
transition: all ease 0.3s;
}
}
}
}
}

405
src/views/home/index.vue Normal file
View File

@@ -0,0 +1,405 @@
<template>
<div class="home">
<!-- 用户信息 -->
<el-row :gutter="15">
<el-col :md="24" :lg="16" :xl="16" class="mb15">
<el-card shadow="hover">
<div slot="header">
<span>{{ $t('message.card.title1') }}</span>
</div>
<div class="user-item">
<div class="user-item-left">
<img :src="getUserInfos.photo" />
</div>
<div class="user-item-right overflow">
<el-row>
<el-col :span="24" class="right-title mb15 one-text-overflow"
>{{ currentTime }}{{ getUserInfos.userName }}{{ dailyMessage }}
</el-col>
<el-col :span="24">
<el-col :xs="12" :sm="12" :md="8" class="right-l-v">
<div class="right-label">昵称</div>
<div class="right-value">小柒</div>
</el-col>
<el-col :xs="12" :sm="12" :md="16" class="right-l-v">
<div class="right-label">身份</div>
<div class="right-value">{{ userInfo.userName === 'admin' ? '超级管理' : '普通用户' }}</div>
</el-col>
</el-col>
<el-col :span="24" class="mt5">
<el-col :xs="12" :sm="12" :md="8" class="right-l-v">
<div class="right-label one-text-overflow">IP</div>
<div class="right-value one-text-overflow">192.168.1.1</div>
</el-col>
<el-col :xs="12" :sm="12" :md="16" class="right-l-v">
<div class="right-label one-text-overflow">时间</div>
<div class="right-value one-text-overflow">{{ userInfo.time }}</div>
</el-col>
</el-col>
<el-col :span="24" class="mt15">
<el-button size="small" icon="el-icon-edit-outline">修改信息 </el-button>
<el-button size="small" icon="el-icon-position" type="primary">发布活动</el-button>
</el-col>
</el-row>
</div>
</div>
</el-card>
</el-col>
<el-col :md="24" :lg="8" :xl="8" class="mb15">
<el-card shadow="hover">
<div slot="header">
<span>{{ $t('message.card.title2') }}</span>
<el-button class="home-card-more" type="text" @click="onOpenGitee">{{ $t('message.card.title3') }}</el-button>
</div>
<div class="info">
<Scroll :data="newsInfoList" class="info-scroll" :class-option="optionSingleHeight">
<ul class="info-ul">
<li v-for="(v, k) in newsInfoList" :key="k" class="info-item" @click="onNewsInfoListClick(v)">
<div class="info-item-left" v-text="v.title"></div>
<div class="info-item-right" v-text="v.date"></div>
</li>
</ul>
</Scroll>
</div>
</el-card>
</el-col>
</el-row>
<!-- 推荐 -->
<el-card shadow="hover">
<div slot="header">
<span>{{ $t('message.card.title4') }}</span>
<el-button class="home-card-more" type="text" @click="onOpenGitee">{{ $t('message.card.title5') }}</el-button>
</div>
<el-row :gutter="15" class="home-recommend-row">
<el-col :sm="24" :md="12" :lg="6" :xl="6" v-for="(v, k) in recommendList" :key="k">
<div class="home-recommend" :style="{ 'background-color': v.bg }">
<i :class="v.icon" :style="{ color: v.iconColor }"></i>
<div class="home-recommend-auto">
<div>{{ v.title }}</div>
<div class="home-recommend-msg">{{ v.msg }}</div>
</div>
</div>
</el-col>
</el-row>
</el-card>
<!-- charts -->
<el-row :gutter="15" class="mt15">
<el-col :md="24" :lg="8" :xl="8" class="mb15">
<el-card shadow="hover">
<div slot="header">
<span>{{ $t('message.card.title6') }}</span>
</div>
<div class="charts">
<div class="charts-right">
<div ref="homeStockRef" class="h100"></div>
</div>
</div>
</el-card>
</el-col>
<el-col :md="24" :lg="16" :xl="16" class="mb15">
<el-card shadow="hover">
<div slot="header">
<span>{{ $t('message.card.title7') }}</span>
</div>
<div class="charts">
<div class="charts-left mr7">
<div ref="homeLaboratoryRef" class="h100"></div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 履约超时预警 -->
<el-row :gutter="15">
<el-col :md="24" :lg="16" :xl="16" class="home-lg">
<el-card shadow="hover">
<div slot="header">
<span>{{ $t('message.card.title8') }}</span>
</div>
<div class="charts">
<div class="charts-left mr7">
<div ref="homeOvertimeRef" class="h100"></div>
</div>
</div>
</el-card>
</el-col>
<el-col :md="24" :lg="8" :xl="8">
<el-card shadow="hover">
<div slot="header">
<span>{{ $t('message.card.title9') }}</span>
</div>
<div class="home-charts">
<div class="home-charts-item" v-for="(v, k) in chartsRightList" :key="k">
<div class="home-charts-item-left">
<div class="home-charts-item-title">{{ v.title }}</div>
<div class="home-charts-item-num" :style="{ color: v.color }" :id="`titleNum${k + 1}`"></div>
</div>
<div class="home-charts-item-right">
<i :class="v.icon" :style="{ 'background-color': v.iconBg, color: v.color }"></i>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import * as echarts from 'echarts';
import Scroll from 'vue-seamless-scroll';
import { CountUp } from 'countup.js';
import { Session } from '@/utils/storage';
import { formatAxis, formatDate } from '@/utils/formatTime';
import { recommendList, chartsRightList, newsInfoList, dailyMessage } from './mock';
export default {
name: 'home',
components: { Scroll },
data() {
return {
recommendList,
chartsRightList,
newsInfoList,
userInfo: {},
dailyMessage: {},
charts: {
theme: '',
bgColor: '',
},
global: {
homeChartOne: null,
homeChartTwo: null,
homeCharThree: null,
dispose: [null, '', undefined],
},
};
},
created() {
this.initUserInfo();
this.initDailyMessage();
},
computed: {
currentTime() {
return formatAxis(new Date());
},
optionSingleHeight() {
return {
singleHeight: 28,
limitMoveNum: 8,
waitTime: 2000,
};
},
getUserInfos() {
return this.$store.state.userInfos.userInfos;
},
},
mounted() {
this.initHomeStock();
this.initHomeLaboratory();
this.initHomeOvertime();
this.initNumCountUp();
},
methods: {
// 初始化数字滚动
initNumCountUp() {
this.$nextTick(() => {
new CountUp('titleNum1', Math.random() * 100000).start();
new CountUp('titleNum2', Math.random() * 100000).start();
new CountUp('titleNum3', Math.random() * 100000).start();
});
},
// 库存作业
initHomeStock() {
if (!this.global.dispose.some((b) => b === this.global.homeChartOne)) this.global.homeChartOne.dispose();
this.global.homeChartOne = echarts.init(this.$refs.homeStockRef, this.charts.theme);
const option = {
backgroundColor: this.charts.bgColor,
grid: {
top: 50,
right: 20,
bottom: 30,
left: 30,
},
tooltip: {
trigger: 'item',
},
legend: {
left: 'center',
},
series: [
{
name: '邮件营销',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
data: [
{ value: 580, name: '邮件营销' },
{ value: 300, name: '视频广告' },
{ value: 230, name: '联盟广告' },
],
top: 30,
},
],
};
this.global.homeChartOne.setOption(option);
window.addEventListener('resize', () => {
this.global.homeChartOne.resize();
});
},
// 履约情况
initHomeLaboratory() {
if (!this.global.dispose.some((b) => b === this.global.homeChartTwo)) this.global.homeChartTwo.dispose();
this.global.homeChartTwo = echarts.init(this.$refs.homeLaboratoryRef, this.charts.theme);
const option = {
backgroundColor: this.charts.bgColor,
grid: {
top: 50,
right: 20,
bottom: 30,
left: 30,
},
tooltip: {
trigger: 'axis',
},
legend: {
data: ['预购队列', '最新成交价'],
right: 13,
},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
},
yAxis: [
{
type: 'value',
name: '价格',
},
],
series: [
{
name: '预购队列',
type: 'bar',
data: [5, 20, 36, 10, 10, 20],
},
{
name: '最新成交价',
type: 'line',
data: [15, 20, 16, 20, 30, 8],
},
],
};
this.global.homeChartTwo.setOption(option);
window.addEventListener('resize', () => {
this.global.homeChartTwo.resize();
});
},
// 缺货监控
initHomeOvertime() {
if (!this.global.dispose.some((b) => b === this.global.homeCharThree)) this.global.homeCharThree.dispose();
this.global.homeCharThree = echarts.init(this.$refs.homeOvertimeRef, this.charts.theme);
const option = {
backgroundColor: this.charts.bgColor,
grid: {
top: 50,
right: 20,
bottom: 30,
left: 30,
},
tooltip: {
trigger: 'axis',
},
legend: {
data: ['订单数量', '超时数量', '在线数量', '预警数量'],
right: 13,
},
xAxis: {
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
},
yAxis: [
{
type: 'value',
name: '数量',
},
],
series: [
{
name: '订单数量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20, 11, 13, 10, 9, 17, 19],
},
{
name: '超时数量',
type: 'bar',
data: [15, 12, 26, 15, 11, 16, 31, 13, 5, 16, 13, 15],
},
{
name: '在线数量',
type: 'line',
data: [15, 20, 16, 20, 30, 8, 16, 19, 12, 18, 19, 14],
},
{
name: '预警数量',
type: 'line',
data: [10, 10, 13, 12, 15, 18, 19, 10, 12, 15, 11, 17],
},
],
};
this.global.homeCharThree.setOption(option);
window.addEventListener('resize', () => {
this.global.homeCharThree.resize();
});
},
// 随机语录
initDailyMessage() {
this.dailyMessage = dailyMessage[Math.floor(Math.random() * dailyMessage.length)];
},
// 初始化登录信息
initUserInfo() {
if (!Session.get('userInfo')) return false;
this.userInfo = Session.get('userInfo');
this.userInfo.time = formatDate(new Date(this.userInfo.time), 'YYYY-mm-dd HH:MM:SS');
},
// 消息通知当前项点击
onNewsInfoListClick(v) {
window.open(v.link);
},
// 跳转到 gitee
onOpenGitee() {
window.open('https://gitee.com/lyt-top/vue-next-admin');
},
},
watch: {
// 监听 vuex 数据变化
'$store.state.themeConfig.themeConfig.isIsDark': {
handler(isIsDark) {
this.$nextTick(() => {
this.charts.theme = isIsDark ? 'dark' : '';
this.charts.bgColor = isIsDark ? 'transparent' : '';
setTimeout(() => {
this.initHomeStock();
}, 500);
setTimeout(() => {
this.initHomeLaboratory();
}, 700);
setTimeout(() => {
this.initHomeOvertime();
}, 1000);
});
},
deep: true,
immediate: true,
},
},
};
</script>
<style scoped lang="scss">
@import './index.scss';
</style>

103
src/views/home/mock.js Normal file
View File

@@ -0,0 +1,103 @@
// 首页模拟数据
export const recommendList = [
{
title: '优惠券',
msg: '现金券、折扣券、营销必备',
icon: 'el-icon-food',
bg: '#48D18D',
iconColor: '#64d89d',
},
{
title: '多人拼团',
msg: '社交电商、开辟流量',
icon: 'el-icon-shopping-bag-1',
bg: '#F95959',
iconColor: '#F86C6B',
},
{
title: '分销中心',
msg: '轻松招募分销员,成功推广奖励',
icon: 'el-icon-school',
bg: '#8595F4',
iconColor: '#92A1F4',
},
{
title: '秒杀',
msg: '超低价抢购引导更多销量',
icon: 'el-icon-alarm-clock',
bg: '#FEBB50',
iconColor: '#FDC566',
},
];
export const chartsRightList = [
{
title: '距离60分钟订单数量',
num: 188,
icon: 'el-icon-document',
iconBg: '#C3E9FE',
color: '#1890FF',
},
{
title: '距离30分钟订单数量',
num: 15,
icon: 'el-icon-tickets',
iconBg: '#F1EBC5',
color: '#FDB850',
},
{
title: '已超时数量',
num: 6,
icon: 'el-icon-document-delete',
iconBg: '#F2D7C4',
color: '#F8958C',
},
];
export const newsInfoList = [
{
title: '[发布] 2021年02月28日发布基于 vue3.x + vite v1.0.0 版本',
date: '02/28',
link: 'https://gitee.com/lyt-top/vue-next-admin',
},
{
title: '[发布] 2021年04月15日发布 vue2.x + webpack 重构版本',
date: '04/15',
link: 'https://gitee.com/lyt-top/vue-next-admin/tree/vue-prev-admin/',
},
{
title: '[重构] 2021年04月10日 重构 vue2.x + webpack v1.0.0 版本',
date: '04/10',
link: 'https://gitee.com/lyt-top/vue-next-admin/tree/vue-prev-admin/',
},
{
title: '[预览] 2020年12月08日基于 vue3.x 版本后台模板的预览',
date: '12/08',
link: 'http://lyt-top.gitee.io/vue-next-admin-preview/#/login',
},
{
title: '[预览] 2020年11月15日基于 vue2.x 版本后台模板的预览',
date: '11/15',
link: 'https://lyt-top.gitee.io/vue-prev-admin-preview/#/login',
},
];
export const dailyMessage = [
'祝你开心每一天!',
'忙碌了一周,停一停脚步!',
'世间美好,与你环环相扣!',
'永远相信美好的事情即将发生!',
'每一天,遇见更好的自己!',
'保持热爱,奔赴山海!',
'生活明朗,万物可爱!',
'愿每一天醒来都是美好的开始!',
'没有希望的地方,就没有奋斗!',
'我最珍贵的时光都行走在路上!',
'成功,往往住在失败的隔壁!',
'人只要不失去方向,就不会失去自己!',
'每条堵住的路,都有一个出口!',
'没有谁能击垮你,除非你自甘堕落!',
'微笑着的人并非没有痛苦!',
'生活变的再糟糕,也不妨碍我变得更好!',
'你要悄悄努力,然后惊艳众人!',
];

346
src/views/login/index.vue Normal file
View File

@@ -0,0 +1,346 @@
<template>
<div class="login">
<div class="login-weaper">
<div class="login-left">
<div class="login-time">{{ time.txt }}</div>
<div class="login-left-box">
<div>
<div class="logo">{{ getThemeConfig.globalViceTitle }}</div>
<h2 class="title">{{ getThemeConfig.globalViceDes }}</h2>
<div class="msg">
<div class="msg-author">
<span>{{ quotations.name }}</span>
<span>{{ quotations.comeFrom }}</span>
</div>
<div class="msg-txt">{{ quotations.content }}</div>
</div>
</div>
</div>
</div>
<div class="login-right">
<div class="login-main">
<h4 class="login-title">{{ getThemeConfig.globalTitle }}</h4>
<el-form class="el-form login-form">
<el-form-item style="margin-left: 0px" prop="userName">
<el-input
type="text"
:placeholder="$t('message.login.placeholder1')"
prefix-icon="el-icon-user"
v-model="ruleForm.userName"
clearable
autocomplete="off"
>
</el-input>
</el-form-item>
<el-form-item style="margin-left: 0px" prop="password">
<el-input
type="password"
:placeholder="$t('message.login.placeholder2')"
prefix-icon="el-icon-lock"
v-model="ruleForm.password"
autocomplete="off"
:show-password="true"
>
</el-input>
</el-form-item>
<el-form-item style="margin-left: 0px" prop="code">
<div class="el-row" span="24">
<div class="el-col el-col-16">
<el-input
type="text"
maxlength="4"
:placeholder="$t('message.login.placeholder3')"
prefix-icon="el-icon-position"
v-model="ruleForm.code"
clearable
autocomplete="off"
></el-input>
</div>
<div class="el-col el-col-8">
<div class="login-code">
<span class="login-code-img">1234</span>
</div>
</div>
</div>
</el-form-item>
<el-form-item style="margin: 40px 0px 0">
<el-button type="primary" class="login-submit" @click="submitForm" :loading="submit.loading">
<span>{{ $t('message.login.btnText') }}</span>
</el-button>
</el-form-item>
</el-form>
<div class="login-menu">
<a href="javascript:;">{{ $t('message.login.link.one1') }}</a>
<a href="javascript:;">{{ $t('message.login.link.one2') }}</a>
</div>
</div>
</div>
</div>
<div class="vue-particles">
<vue-particles color="#dedede" shapeType="star" linesColor="#dedede" hoverMode="grab" clickMode="push" style="height: 100%"></vue-particles>
</div>
</div>
</template>
<script>
import { Session } from '@/utils/storage';
import { formatDate, formatAxis } from '@/utils/formatTime';
import { PrevLoading } from '@/utils/loading.js';
import { quotationsList } from './mock';
export default {
name: 'login',
data() {
return {
quotationsList,
quotations: {},
isView: false,
submit: {
loading: false,
},
ruleForm: {
userName: 'admin',
password: '123456',
code: '1234',
},
time: {
txt: '',
fun: null,
},
};
},
computed: {
// 获取当前时间
currentTime() {
return formatAxis(new Date());
},
// 获取布局配置信息
getThemeConfig() {
return this.$store.state.themeConfig.themeConfig;
},
},
created() {
this.initTime();
},
mounted() {
this.initRandomQuotations();
},
methods: {
// 随机语录
initRandomQuotations() {
this.quotations = this.quotationsList[Math.floor(Math.random() * this.quotationsList.length)];
},
// 初始化左上角时间显示
initTime() {
this.time.txt = formatDate(new Date(), 'YYYY-mm-dd HH:MM:SS WWW QQQQ');
this.time.fun = setInterval(() => {
this.time.txt = formatDate(new Date(), 'YYYY-mm-dd HH:MM:SS WWW QQQQ');
}, 1000);
},
// 登录按钮点击
submitForm() {
this.submit.loading = true;
setTimeout(() => {
let defaultRoles = [];
let defaultAuthBtnList = [];
// admin 页面权限标识,对应路由 meta.roles
let adminRoles = ['admin'];
// admin 按钮权限标识
let adminAuthBtnList = ['btn.add', 'btn.del', 'btn.edit', 'btn.link'];
// common 页面权限标识,对应路由 meta.roles
let testAuthPageList = ['common'];
// test 按钮权限标识
let testAuthBtnList = ['btn.add', 'btn.link'];
if (this.ruleForm.userName === 'admin') {
defaultRoles = adminRoles;
defaultAuthBtnList = adminAuthBtnList;
} else {
defaultRoles = testAuthPageList;
defaultAuthBtnList = testAuthBtnList;
}
const userInfos = {
userName: this.ruleForm.userName === 'admin' ? 'admin' : 'test',
photo:
this.ruleForm.userName === 'admin'
? 'https://img0.baidu.com/it/u=1833472230,3849481738&fm=253&fmt=auto?w=200&h=200'
: 'https://img2.baidu.com/it/u=2187913762,2708298335&fm=253&fmt=auto&app=138&f=JPEG?w=200&h=200',
time: new Date().getTime(),
roles: defaultRoles,
authBtnList: defaultAuthBtnList,
};
// 存储 token 到浏览器缓存
Session.set('token', Math.random().toString(36).substr(0));
// 存储用户信息到浏览器缓存
Session.set('userInfo', userInfos);
// 存储用户信息到vuex
this.$store.dispatch('userInfos/setUserInfos', userInfos);
PrevLoading.start();
this.$router.push('/');
setTimeout(() => {
this.$message.success(`${this.currentTime}${this.$t('message.login.signInText')}`);
}, 300);
}, 300);
},
},
destroyed() {
clearInterval(this.time.fun);
},
};
</script>
<style scoped lang="scss">
.login {
height: 100%;
width: 100%;
overflow: hidden;
display: flex;
position: relative;
.vue-particles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(ellipse at top left, rgba(105, 155, 200, 1) 0%, rgba(181, 197, 216, 1) 57%);
}
.login-weaper {
margin: auto;
height: 500px;
display: flex;
box-sizing: border-box;
position: relative;
z-index: 1;
border: none;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
.login-left {
width: 491px;
padding: 20px;
font-size: 16px;
color: var(--prev-color-text-white);
position: relative;
background-color: var(--prev-color-primary);
display: flex;
flex-direction: column;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
.login-time {
width: 100%;
color: var(--prev-color-text-white);
opacity: 0.9;
font-size: 14px;
overflow: hidden;
}
.login-left-box {
flex: 1;
overflow: hidden;
position: relative;
z-index: 1;
display: flex;
align-items: center;
padding: 80px 45px;
.logo {
font-size: 22px;
margin-bottom: 15px;
}
.title {
color: var(--prev-color-text-white);
font-weight: 300;
letter-spacing: 2px;
font-size: 16px;
}
.msg {
color: var(--prev-color-text-white);
font-size: 13px;
margin-top: 35px;
.msg-author {
opacity: 0.6;
span:last-of-type {
margin-left: 15px;
}
}
.msg-txt {
margin-top: 15px;
line-height: 22px;
}
}
}
}
.login-right {
width: 491px;
padding: 20px;
position: relative;
align-items: center;
display: flex;
background-color: var(--prev-bg-white);
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
.login-main {
margin: 0 auto;
width: 70%;
.login-title {
color: var(--prev-color-text-primary);
margin-bottom: 40px;
font-weight: 500;
font-size: 22px;
text-align: center;
letter-spacing: 4px;
}
.login-form {
margin: 10px 0;
i {
color: var(--prev-color-text-primary);
}
.el-form-item {
margin-bottom: 20px !important;
.login-code {
display: flex;
align-items: center;
justify-content: space-around;
margin: 0 0 0 10px;
user-select: none;
.login-code-img {
margin-top: 2px;
width: 100px;
height: 38px;
border: 1px solid var(--prev-border-color-base);
color: var(--prev-color-text-primary);
font-size: 14px;
font-weight: 700;
letter-spacing: 5px;
line-height: 38px;
text-indent: 5px;
text-align: center;
cursor: pointer;
transition: all ease 0.2s;
border-radius: 4px;
&:hover {
border-color: var(--prev-border-color-hover);
transition: all ease 0.2s;
}
}
}
.login-submit {
width: 100%;
letter-spacing: 2px;
}
}
}
.login-menu {
margin-top: 30px;
width: 100%;
text-align: left;
a {
color: var(--prev-color-text-secondary);
font-size: 12px;
margin: 0 8px;
text-decoration: none;
&:hover {
color: var(--prev-color-primary);
text-decoration: underline;
}
}
}
}
}
}
}
</style>

38
src/views/login/mock.js Normal file
View File

@@ -0,0 +1,38 @@
export const quotationsList = [
{
name: '颜渊',
comeFrom: '论语',
content:
'自己不喜欢的,就不要强加给别人。饥寒是自己不喜欢的,不要把它强加给别人;耻辱是自己不喜欢的,也不要把它强加给别人。将心比心,推己及人,从自己的利与害想到对别人的利与害,多替别人着想,这是终生应该奉行的原则。',
},
{
name: '荀子',
comeFrom: '劝学',
content:
'木料经过木工用墨线(木工用具)划直线加工以后,就变直了;金属物品在磨刀石上磨砺后,就能锋利。人经过学习磨练,自我反省,就会变得聪慧明智,不犯错误,也越来越坚强。',
},
{
name: '里仁',
comeFrom: '论语',
content:
'见到贤人,就应该想着向他学习;看见不贤的人,便应该自己反省,对不如自己的人喜欢讥笑、轻视,因而沾沾自喜;对比自己强的人喜欢贬低,甚至嫉妒、畏惧退缩,害怕与他们交往:这都是不正确的态度。',
},
{
name: '述而',
comeFrom: '论语',
content:
'君子心地平坦宽广,小人却经常局促忧愁。君子襟怀坦白,安贫乐业,与人为善,知足常乐,所以能坦荡荡。小人欲念太多,患得患失,忧心忡忡,怨天尤人,局促不安,所以常心怀戚戚。',
},
{
name: '老子',
comeFrom: '第六十四章',
content:
'千里遥远的路程是从脚下第一步开始的。任何事情的成功都是从头开始,从小到大逐渐积累的。万事开头难,没有个开头就不会有结果。任何事情都要从一点一滴的小事开始做起。',
},
{
name: '朱熹',
comeFrom: '训学斋规',
content:
'读书有三到,谓心到,眼到,口到。心不在此,则眼看不仔细,心眼既不专一,却只漫浪诵读,决不能记,久也不能久也。三到之中,心到最急,心既到矣,眼口岂不到乎?',
},
];

Some files were not shown because too many files have changed in this diff Show More