Commit c035ad52 by zym

第一次提交

0 parents
Showing with 9023 additions and 0 deletions
root = true
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=true
indent_style=space
indent_size=2
max_line_length = 100
[*.{yml,yaml,json}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
\ No newline at end of file
# 测试环境配置信息
NODE_ENV = 'development'
VITE_APP_WEB_URL = ''
VITE_PUBLIC_PATH = /
\ No newline at end of file
# 生产环境配置信息
NODE_ENV = 'production'
VITE_APP_WEB_URL = ''
\ No newline at end of file
[{"/Users/hujiangjun/Desktop/hu-snail/my-vue-app/src/App.vue":"1","/Users/hujiangjun/Desktop/hu-snail/my-vue-app/src/components/HelloWorld.vue":"2","/Users/hujiangjun/Desktop/hu-snail/my-vue-app/src/views/index/index.vue":"3"},{"size":313,"mtime":1627908847966},{"size":721,"mtime":1628173242310},{"size":181,"mtime":1627910538782}]
\ No newline at end of file
src/assets
src/icons
public
dist
node_modules
index.html
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
/docs
.husky
.local
/bin
Dockerfile
\ No newline at end of file
module.exports = {
root: true,
env: {
node: true,
},
extends: ['plugin:vue/vue3-essential', '@vue/prettier'],
parserOptions: {
parser: 'babel-eslint',
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'vue/no-v-html': 'off',
},
overrides: [
{
files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'],
env: {
jest: true,
},
},
],
};
node_modules
.DS_Store
dist-ssr
dist
*.local
\ No newline at end of file
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*
\ No newline at end of file
/dist/*
/public/*
public/*
\ No newline at end of file
module.exports = {
extends: ['stylelint-config-recess-order', 'stylelint-config-prettier'],
};
{
"git.enableSmartCommit": true,
"i18n-ally.localesPaths": [
"src/locales",
"src/locales/lang"
]
}
\ No newline at end of file
MIT License
Copyright (c) 2021-present, hu-snail
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.
<div align="center"><img src="https://gitee.com/hu-snail/vue3-admin-element-template/raw/master/src/assets/logo.png"/></div>
<h1 align="center">Vue3 admin element template</h1>
> Vue3-admin-element-template 是 Vue3-admin-element的精简版本,去掉了完整版本中丰富的组件库。项目使用的是`Composition Api`、和`<script setup>`新语法糖风格编写。
## 前言
`Vue3-admin-element-template`项目是`js`版本,相信有部分刚入门的程序员不熟悉`ts`编写,在github上查找了关于vue3中后台模板,大部分都是基于ts版本。所以决定自己写一个基于js版本的中后台模板,当然如果您很熟练ts建议您使用它!不熟悉的建议您开始纳入学习计划。后期准备用ts和[Naiveui](https://www.naiveui.com/zh-CN/light)开发一套开箱即用的中后台模板。
## 简介
`Vue3-admin-element-template`是一个免费开源的中后台模板你,使用了最新的`vue3``vite2` 等主流技术开发,开箱即用的中后台前端解决方案。
项目目的:
- 学习`Vue3`相关Api
- 掌握`Vite2`插件机制、构建配置
提示:项目使用<script setup> 新语法编写,会有黄色警告信息,不用理会!!!
在线预览地址:https://hu-snail.gitee.io/vue3-admin-element-template
## 特性
- 最新技术栈:vue3.2.12/vite2
- Javascript版本
- 可自定义主题
- vue-i18n国际化方案
- Mock 数据方案
- 权限控制
## vue3资源库
一个收集vue3资源的网站,包含web端/移动端/小程序等
在线预览地址:[vue3-resource](https://hu-snail.github.io/vue3-resource)
## 文档
正在编写中......
## 准备
- `Node`: 版本建议 >= 12.0.0 [下载链接](https://nodejs.org/zh-cn/download/)
- `Git`: [版本管理工具](https://www.git-scm.com/download)
- `Visual Studio Code`: [最新版本](https://code.visualstudio.com/Download/)
- [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur) - vue开发必备
- [Eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)- 脚本代码检查
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) - 代码格式化
- [Stylelin](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) - css格式化
## 安装使用
- 获取代码
```sh
git clone https://github.com/hu-snail/vue3-admin-element-template.git
```
- 安装依赖
```sh
yarn install
```
- 运行
```sh
yarn serve
```
- 打包
```sh
yarn build
```
- 本地预览
```sh
yarn preview
```
- 打包/预览
```sh
yarn build:preview
```
## 预览
在线预览地址:https://hu-snail.gitee.io/vue3-admin-element-template
测试账号:admin/admin 密码可以随意输入
![截图1](https://gitee.com/hu-snail/vue3-admin-element-template/raw/master/src/assets/demo/01.png)
![截图2](https://gitee.com/hu-snail/vue3-admin-element-template/raw/master/src/assets/demo/02.png)
![截图3](https://gitee.com/hu-snail/vue3-admin-element-template/raw/master/src/assets/demo/03.png)
![截图4](https://gitee.com/hu-snail/vue3-admin-element-template/raw/master/src/assets/demo/04.png)
## 浏览器支持
本地开发推荐使用`Chrome 80+` 浏览器
支持现代浏览器, 不支持 IE
## 维护者
[@hu-snail](https://github.com/hu-snail/)
## 捐赠
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持!
<div style="display: flex; justify-content: space-between;"><img width="48%" src="https://gitee.com/hu-snail/vue3-admin-element-template/raw/master/src/assets/wx.jpeg"/><img width="48%" src="https://gitee.com/hu-snail/vue3-admin-element-template/raw/master/src/assets/zfb.jpeg"/></div>
## License
[MIT © hu-snail-2021](https://github.com/hu-snail/vue3-admin-element-template/blob/master/LICENSE)
No preview for this file type
No preview for this file type
module.exports = {
presets: ['@vue/cli-plugin-babel/preset'],
plugins: [
[
'component',
{
libraryName: 'element-plus',
styleLibraryName: 'theme-chalk',
},
'import',
{
libraryName: '@icon-park/vue-next',
libraryDirectory: 'es/icons',
camel2DashComponentName: false,
},
],
],
};
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'feat', // 增加新功能
'fix', // 修复问题/BUG
'perf', // 优化/性能提升
'style', // 代码风格相关无影响运行结果的
'docs', // 文档/注释
'test', // 测试相关
'refactor', // 重构
'build', // 生产
'ci', // 持续集成
'chore', // 依赖更新/脚手架配置修改等
'revert', // 撤销修改
'wip', // 开发中
'workflow', // 工作流改进
'types', // 类型修改
'release', // 发布正式版
],
],
},
};
#!/usr/bin/env sh
# 确保脚本抛出遇到的错误
set -e
# 生成静态文件
yarn build
# 进入生成的文件夹
cd dist
# 如果是发布到自定义域名
# echo 'www.example.com' > CNAME
git init
git add -A
git commit -m 'deploy'
# 如果发布到 https://<USERNAME>.github.io
# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git master
# 如果发布到 https://<USERNAME>.github.io/<REPO>
# git push -f git@github.com:<USERNAME>/<REPO>.git master:gh-pages
# 把上面的 <USERNAME> 换成你自己的 Github 用户名,<REPO> 换成仓库名,比如我这里就是:
git push -f git@github.com:hu-snail/vue3-admin-element-template.git master:gh-pages
cd -
\ No newline at end of file
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/static/loading.css" />
</head>
<body>
<div id="app">
<div class="app-loading">
<div class="app-loading-wrap">
<div class="app-loading-dots">
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
</div>
<div class="app-loading-title">加载中</div>
</div>
</div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
File mode changed
const icons = [
{
name: 'icon-home',
title: '首页',
},
{
name: 'add-text',
title: '文字大小',
},
{
name: 'code',
title: '代码',
},
{
name: 'like',
title: '喜欢',
},
{
name: 'chart-line',
title: '折线图',
},
{
name: 'xigua',
title: '西瓜视频',
},
{
name: 'performance',
title: '演出',
},
{
name: 'pic',
title: '图片',
},
{
name: 'search',
title: '搜索',
},
{
name: 'tailoring',
title: '裁剪',
},
{
name: 'tailoring-two',
title: '裁剪',
},
{
name: 'move-one',
title: '拖拽',
},
{
name: 'scan-code',
title: '二维码',
},
];
const props = [
{
param: 'theme',
type: 'string',
default: 'outline',
all: 'outline,filled,two-tone,multi-color',
desc: '主题,outline 线性 filled 填充 two-tone 双色 multi-color 多色 ',
},
{
param: 'size',
type: 'string | number',
default: '24',
all: '-',
desc: '图标大小',
},
{
param: 'fill',
type: 'string | arrary',
default: '#333',
all: '-',
desc: '图标颜色,双色 ["#333" ,"#2F88FF"] 多色 ["#333" ,"#2F88FF" ,"#FFF" ,"#305c69"]',
},
{
param: 'strokeWidth',
type: 'number',
default: '4',
all: '-',
desc: '线段粗细',
},
{
param: 'strokeLinejoin',
type: 'string',
default: '-',
all: 'miter, bevel',
desc: '拐点类型, 参考IconPark',
},
{
param: 'strokeLinecap',
type: 'string',
default: '-',
all: 'butt,square',
desc: '端点类型 参考 IconPark',
},
];
export default [
{
url: '/api/icon',
type: 'get',
response() {
return { code: 200, msg: 'success', data: { icons, props } };
},
},
];
const prefix = 'https://gitee.com/hu-snail/vue3-admin-element-template/raw/master/src/assets/';
const list = [
{
title: 'Nutui',
desc: '京东风格的轻量级移动端 Vue 组件库',
url: 'https://nutui.jd.com/#/range',
logo: 'index/nutui.png',
},
{
title: 'Vant',
desc: '轻量、可靠的移动端 Vue 组件库',
url: 'https://vant-contrib.gitee.io/vant/v3/#/zh-CN',
logo: 'index/vant.png',
},
{
title: 'Ant Design',
desc: '为 Web 应用提供了丰富的基础 UI 组件',
url: 'https://antdv.com/docs/vue/introduce-cn/',
logo: 'index/antd.svg',
},
{
title: 'Vite 中文',
desc: '下一代前端开发与构建工具',
url: 'https://cn.vitejs.dev/',
logo: 'index/vite.svg',
},
{
title: 'Vue3 文档',
desc: '渐进式JavaScript框架vue3中文文档',
url: 'https://vue3js.cn/docs/zh/',
logo: 'logo.png',
},
{
title: 'ElementPlus',
desc: '一套基于 Vue 3.0 的桌面端组件库',
url: 'https://element-plus.gitee.io/#/zh-CN/component/installation',
logo: 'index/element+.svg',
},
{
title: 'Iconpark',
desc: '2400+基础图标,29种图标分类,提供更多的选择',
url: 'https://iconpark.oceanengine.com/home',
logo: 'index/iconpark.svg',
},
{
title: 'Naiveui',
desc: '一个 Vue 3 组件库,使用 TypeScript',
url: 'https://www.naiveui.com/zh-CN/light',
logo: 'index/naive.svg',
},
{
title: 'Echarts5.0',
desc: '一个基于 JavaScript 的开源可视化图表库',
url: 'https://echarts.apache.org/zh/index.html',
logo: 'index/echarts.png',
},
{
title: 'XGplayer',
desc: '带解析器、能节省流量的 Web 视频播放器',
url: 'http://v2.h5player.bytedance.com/',
logo: 'index/xgplayer.png',
},
{
title: 'VueUse',
desc: 'VUE组合实用程序的集合',
url: 'https://vueuse.org/',
logo: 'index/vueuse.svg',
},
{
title: 'Vue3 源码',
desc: '深入学习了解vue3源码',
url: 'https://vue3js.cn/start/',
logo: 'logo.png',
},
];
const orderList = [
{
key: 'planned',
value: 5021,
status: 'primary',
},
{
key: 'finished',
value: 3204,
status: 'success',
},
{
key: 'unfinished',
value: 1817,
status: 'error',
},
];
const skillList = [
{
title: 'JavaScript',
percentage: 50,
color: '#67c23a',
},
{
title: 'HTML',
percentage: 90,
color: '#e6a23c',
},
{
title: 'CSS',
percentage: 70,
color: '',
},
{
title: 'Vue',
percentage: 80,
color: '#f56c6d',
},
{
title: 'Node',
percentage: 60,
color: '#a650fe',
},
];
export default [
{
url: '/api/getResouceList',
type: 'get',
response() {
return { code: 200, msg: 'success', data: { list, prefix, orderList, skillList } };
},
},
];
const data = [
{
path: '/',
component: 'Layout',
redirect: 'index',
children: [
{
path: '/index',
name: 'Index',
component: '',
meta: {
title: '首页',
icon: 'home',
affix: true,
noKeepAlive: true,
},
},
],
},
{
path: '/comp',
component: 'Layout',
name: 'Comp',
meta: { title: '组件', icon: '' },
children: [
{
path: '/iconPark',
name: 'IconPark',
component: '',
meta: {
title: '图标',
},
children: [
{
path: '/iconPark2',
name: 'IconPark2',
component: '',
meta: {
title: '图标2211',
},
},
],
},
{
path: '/iconPark233',
name: 'IconPark3',
component: () => '',
meta: {
title: '图标2233',
},
},
{
path: '/iconPark234',
name: 'IconPark3',
component: () => '',
meta: {
title: '测试',
},
},
],
},
];
export default [
{
url: '/api/menu/navigate',
type: 'post',
response() {
return { code: 200, msg: 'success', data: data };
},
},
];
const accessTokens = {
admin: 'admin-accessToken',
editor: 'editor-accessToken',
test: 'test-accessToken',
};
export default [
{
url: '/api/publicKey',
type: 'post',
response() {
return {
code: 200,
msg: 'success',
data: {
mockServer: true,
publicKey:
'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBT2vr+dhZElF73FJ6xiP181txKWUSNLPQQlid6DUJhGAOZblluafIdLmnUyKE8mMHhT3R+Ib3ssZcJku6Hn72yHYj/qPkCGFv0eFo7G+GJfDIUeDyalBN0QsuiE/XzPHJBuJDfRArOiWvH0BXOv5kpeXSXM8yTt5Na1jAYSiQ/wIDAQAB',
},
};
},
},
{
url: '/api/login',
method: 'post',
response: (config) => {
const { username } = config.body;
const accessToken = accessTokens[username];
if (!accessToken) {
return {
code: 500,
msg: '帐户或密码不正确。',
};
}
return {
code: 200,
msg: 'success',
data: { accessToken },
};
},
},
{
url: '/api/register',
type: 'post',
response() {
return {
code: 200,
msg: '模拟注册成功',
};
},
},
{
url: '/api/userInfo',
type: 'get',
response(config) {
const { accessToken } = config.body;
let permissions = ['admin'];
let username = 'admin';
if ('admin-accessToken' === accessToken) {
permissions = ['admin'];
username = 'admin';
}
if ('editor-accessToken' === accessToken) {
permissions = ['editor'];
username = 'editor';
}
if ('test-accessToken' === accessToken) {
permissions = ['admin', 'editor'];
username = 'test';
}
return {
code: 200,
msg: 'success',
data: {
permissions,
username,
'avatar|1': [
'https://i.gtimg.cn/club/item/face/img/2/15922_100.gif',
'https://i.gtimg.cn/club/item/face/img/8/15918_100.gif',
],
},
};
},
},
{
url: '/api/logout',
type: 'post',
response() {
return {
code: 200,
msg: 'success',
};
},
},
];
{
"name": "my-vue-app",
"version": "1.0.0",
"author": {
"name": "hu-snail",
"email": "1217437592@qq.com",
"url": "https://github.com/hu-snail"
},
"scripts": {
"lint": "eslint \"src/**/*.{js,vue}\"",
"serve": "cross-env NODE_ENV=development vite",
"build": "cross-env NODE_ENV=production vite build",
"preview": "cross-env vite preview",
"build:preview": "vite build --mode production && vite preview",
"dev:mock": "cross-env USE_MOCK=true vite",
"build:mock": "cross-env USE_CHUNK_MOCK=true vite build && vite preview",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:style": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
"lint:pretty": "pretty-quick --staged",
"deploy-gh": "GH=1 bash deploy-gh.sh"
},
"dependencies": {
"@element-plus/icons-vue": "^1.1.4",
"@icon-park/vue-next": "^1.4.0",
"@soerenmartius/vue3-clipboard": "^0.1.2",
"axios": "^0.21.1",
"echarts": "^5.1.2",
"element-plus": "^2.2.5",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"qs": "^6.10.1",
"screenfull": "^5.1.0",
"vue": "^3.2.33",
"vue-i18n": "^9.1.10",
"vue-router": "^4.0.15",
"vue3-count-to": "^1.0.11",
"vuex": "^4.0.2"
},
"devDependencies": {
"@intlify/vite-plugin-vue-i18n": "^3.4.0",
"@types/node": "^16.7.1",
"@vitejs/plugin-legacy": "^1.5.3",
"@vitejs/plugin-vue": "^1.9.2",
"@vue/compiler-sfc": "^3.2.11",
"@vue/eslint-config-prettier": "^6.0.0",
"autoprefixer": "^10.3.1",
"babel-eslint": "^10.1.0",
"cross-env": "^7.0.3",
"eslint": "^7.32.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.15.0",
"lint-staged": "^11.1.1",
"postcss": "^8.3.6",
"prettier": "^2.3.2",
"sass": "^1.37.0",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-recess-order": "^2.4.0",
"stylelint-config-standard": "^22.0.0",
"svg-sprite-loader": "^6.0.9",
"unplugin-auto-import": "^0.7.2",
"unplugin-icons": "^0.14.3",
"unplugin-vue-components": "^0.19.5",
"vite": "^2.5.10",
"vite-plugin-babel-import": "^2.0.5",
"vite-plugin-element-plus": "^0.0.12",
"vite-plugin-mock": "^2.9.4",
"vite-plugin-optimize-persist": "^0.1.2",
"vite-plugin-package-config": "^0.1.1",
"vite-plugin-style-import": "^1.2.1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/hu-snail/vue3-admin-element-template.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/hu-snail/vue3-admin-element-template/issues"
},
"homepage": "https://github.com/hu-snail/vue3-admin-element-template",
"engines": {
"node": "^12 || >=14"
},
"vite": {
"optimizeDeps": {
"include": [
"@element-plus/icons-vue",
"@icon-park/vue-next",
"@icon-park/vue-next/es/all",
"@soerenmartius/vue3-clipboard",
"axios",
"echarts/charts",
"echarts/components",
"echarts/core",
"echarts/renderers",
"element-plus",
"element-plus/es",
"element-plus/es/components/avatar/style/css",
"element-plus/es/components/avatar/style/index",
"element-plus/es/components/backtop/style/css",
"element-plus/es/components/backtop/style/index",
"element-plus/es/components/breadcrumb-item/style/css",
"element-plus/es/components/breadcrumb-item/style/index",
"element-plus/es/components/breadcrumb/style/css",
"element-plus/es/components/breadcrumb/style/index",
"element-plus/es/components/button-group/style/css",
"element-plus/es/components/button-group/style/index",
"element-plus/es/components/button/style/css",
"element-plus/es/components/button/style/index",
"element-plus/es/components/card/style/css",
"element-plus/es/components/card/style/index",
"element-plus/es/components/checkbox-button/style/css",
"element-plus/es/components/checkbox-button/style/index",
"element-plus/es/components/checkbox-group/style/css",
"element-plus/es/components/checkbox-group/style/index",
"element-plus/es/components/checkbox/style/css",
"element-plus/es/components/checkbox/style/index",
"element-plus/es/components/col/style/css",
"element-plus/es/components/col/style/index",
"element-plus/es/components/config-provider/style/css",
"element-plus/es/components/config-provider/style/index",
"element-plus/es/components/container/style/css",
"element-plus/es/components/container/style/index",
"element-plus/es/components/date-picker/style/css",
"element-plus/es/components/date-picker/style/index",
"element-plus/es/components/descriptions-item/style/css",
"element-plus/es/components/descriptions-item/style/index",
"element-plus/es/components/descriptions/style/css",
"element-plus/es/components/descriptions/style/index",
"element-plus/es/components/divider/style/css",
"element-plus/es/components/divider/style/index",
"element-plus/es/components/drawer/style/css",
"element-plus/es/components/drawer/style/index",
"element-plus/es/components/dropdown-item/style/css",
"element-plus/es/components/dropdown-item/style/index",
"element-plus/es/components/dropdown-menu/style/css",
"element-plus/es/components/dropdown-menu/style/index",
"element-plus/es/components/dropdown/style/css",
"element-plus/es/components/dropdown/style/index",
"element-plus/es/components/form-item/style/css",
"element-plus/es/components/form-item/style/index",
"element-plus/es/components/form/style/css",
"element-plus/es/components/form/style/index",
"element-plus/es/components/header/style/css",
"element-plus/es/components/header/style/index",
"element-plus/es/components/icon/style/css",
"element-plus/es/components/icon/style/index",
"element-plus/es/components/input/style/css",
"element-plus/es/components/input/style/index",
"element-plus/es/components/main/style/css",
"element-plus/es/components/main/style/index",
"element-plus/es/components/menu-item/style/css",
"element-plus/es/components/menu-item/style/index",
"element-plus/es/components/menu/style/css",
"element-plus/es/components/menu/style/index",
"element-plus/es/components/message/style/css",
"element-plus/es/components/option/style/css",
"element-plus/es/components/option/style/index",
"element-plus/es/components/pagination/style/css",
"element-plus/es/components/pagination/style/index",
"element-plus/es/components/popover/style/css",
"element-plus/es/components/popover/style/index",
"element-plus/es/components/progress/style/css",
"element-plus/es/components/progress/style/index",
"element-plus/es/components/radio-button/style/css",
"element-plus/es/components/radio-button/style/index",
"element-plus/es/components/radio-group/style/css",
"element-plus/es/components/radio-group/style/index",
"element-plus/es/components/radio/style/css",
"element-plus/es/components/radio/style/index",
"element-plus/es/components/row/style/css",
"element-plus/es/components/row/style/index",
"element-plus/es/components/scrollbar/style/css",
"element-plus/es/components/scrollbar/style/index",
"element-plus/es/components/select/style/css",
"element-plus/es/components/select/style/index",
"element-plus/es/components/sub-menu/style/css",
"element-plus/es/components/sub-menu/style/index",
"element-plus/es/components/switch/style/css",
"element-plus/es/components/switch/style/index",
"element-plus/es/components/tab-pane/style/css",
"element-plus/es/components/tab-pane/style/index",
"element-plus/es/components/table-column/style/css",
"element-plus/es/components/table-column/style/index",
"element-plus/es/components/table/style/css",
"element-plus/es/components/table/style/index",
"element-plus/es/components/tabs/style/css",
"element-plus/es/components/tabs/style/index",
"element-plus/lib/locale/lang/en",
"element-plus/lib/locale/lang/zh-cn",
"js-cookie",
"lodash/debounce",
"nprogress",
"qs",
"screenfull",
"vue",
"vue-i18n",
"vue-router",
"vue3-count-to",
"vuex"
]
}
}
}
module.exports = {
plugins: {
// 兼容浏览器,添加前缀
autoprefixer: {
overrideBrowserslist: [
'Android 4.1',
'iOS 7.1',
'Chrome > 31',
'ff > 31',
'ie >= 8',
//'last 2 versions', // 所有主流浏览器最近2个版本
],
grid: true,
},
},
};
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: true,
vueIndentScriptAndStyle: true,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
trailingComma: 'es5',
jsxBracketSameLine: false,
jsxSingleQuote: false,
arrowParens: 'always',
insertPragma: false,
requirePragma: false,
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
rangeStart: 0,
};
No preview for this file type
html[data-theme='dark'] .app-loading {
background-color: #2c344a;
}
html[data-theme='dark'] .app-loading .app-loading-title {
color: rgba(255, 255, 255, 0.85);
}
.app-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background-color: #f4f7f9;
}
.app-loading .app-loading-wrap {
position: absolute;
top: 50%;
left: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
-webkit-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0);
}
.app-loading .dots {
display: flex;
align-items: center;
justify-content: center;
padding: 98px;
}
.app-loading .app-loading-title {
display: flex;
align-items: center;
justify-content: center;
margin-top: 30px;
font-size: 30px;
color: rgba(0, 0, 0, 0.85);
}
.app-loading .app-loading-logo {
display: block;
width: 90px;
margin: 0 auto;
margin-bottom: 20px;
}
.dot {
position: relative;
box-sizing: border-box;
display: inline-block;
width: 48px;
height: 48px;
margin-top: 30px;
font-size: 32px;
transform: rotate(45deg);
animation: antRotate 1.2s infinite linear;
}
.dot i {
position: absolute;
display: block;
width: 20px;
height: 20px;
background-color: #00a69c;
border-radius: 100%;
opacity: 0.3;
transform: scale(0.75);
transform-origin: 50% 50%;
animation: antSpinMove 1s infinite linear alternate;
}
.dot i:nth-child(1) {
top: 0;
left: 0;
}
.dot i:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.dot i:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.dot i:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
@keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@-webkit-keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes antSpinMove {
to {
opacity: 1;
}
}
@-webkit-keyframes antSpinMove {
to {
opacity: 1;
}
}
<template>
<el-config-provider :locale="localLanguage">
<el-scrollbar height="100vh" ref="scroll">
<router-view></router-view>
</el-scrollbar>
</el-config-provider>
</template>
<script setup>
import { onMounted, computed, ref, watch } from 'vue';
import { useStore } from 'vuex';
import i18n from '@/locales';
import { useRouter } from 'vue-router';
const locale = i18n.global.locale;
const store = useStore();
const localLanguage = computed(() => {
const isDev = process.env.NODE_ENV === 'development';
if (isDev) return i18n.global.messages.value[locale.value];
else return i18n.global.messages[locale];
});
const scroll = ref(null);
const router = useRouter();
onMounted(() => {
changeBodyWidth();
window.addEventListener('resize', changeResize);
});
watch(
() => router.currentRoute.value,
() => {
scroll.value.setScrollTop(0);
}
);
const changeBodyWidth = () => {
const flag = document.body.getBoundingClientRect().width - 1 < 992;
store.dispatch('setting/changeMobile', flag);
};
const changeResize = () => {
changeBodyWidth();
};
</script>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
font-size: $base-font-size-default;
color: #2c3e50;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>
import request from '@/utils/request.js';
export const getIcons = () => {
return request({
url: '/icon',
method: 'get',
});
};
import request from '@/utils/request.js';
export const getResouceList = () => {
return request({
url: '/getResouceList',
method: 'get',
});
};
import request from '@/utils/request.js';
export const getRouterList = (data) => {
return request({
url: '/menu/navigate',
method: 'post',
data,
});
};
import request from '@/utils/request.js';
import { setting } from '@/config/setting';
const { tokenName } = setting;
export const login = async (data) => {
return request({
url: '/login',
method: 'post',
data,
});
};
export const getUserInfo = (accessToken) => {
return request({
url: '/userInfo',
method: 'get',
data: {
[tokenName]: accessToken,
},
});
};
export const logout = () => {
return request({
url: '/logout',
method: 'post',
});
};
export const register = async () => {
return request({
url: '/register',
method: 'post',
});
};
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 800 800" style="enable-background:new 0 0 800 800;" xml:space="preserve">
<style type="text/css">
.st0{fill:#F3F6FF;}
.st1{fill:#DFE7FF;}
.st2{fill:url(#SVGID_1_);}
.st3{fill:#FFFFFF;}
.st4{fill:url(#SVGID_2_);}
.st5{fill:#F7F8F8;}
.st6{fill:url(#SVGID_3_);}
.st7{fill:url(#SVGID_4_);}
.st8{fill:url(#SVGID_5_);}
.st9{fill:url(#SVGID_6_);}
.st10{fill:url(#SVGID_7_);}
.st11{fill:url(#SVGID_8_);}
.st12{fill:url(#SVGID_9_);}
.st13{fill:#B2C6FF;}
.st14{fill:url(#SVGID_10_);}
.st15{fill:url(#SVGID_11_);}
.st16{fill:url(#SVGID_12_);}
.st17{fill:url(#SVGID_13_);}
.st18{fill:url(#SVGID_14_);}
.st19{fill:url(#SVGID_15_);}
.st20{fill:url(#SVGID_16_);}
.st21{fill:#D0DEFF;}
.st22{fill:url(#SVGID_17_);}
.st23{fill:url(#SVGID_18_);}
.st24{fill:#7899FF;}
.st25{fill:#ECF1FF;}
.st26{fill:url(#SVGID_19_);}
.st27{fill:url(#SVGID_20_);}
.st28{fill:url(#SVGID_21_);}
.st29{fill:url(#SVGID_22_);}
.st30{fill:url(#SVGID_23_);}
.st31{fill:url(#SVGID_24_);}
.st32{fill:url(#SVGID_25_);}
.st33{fill:url(#SVGID_26_);}
.st34{fill:url(#SVGID_27_);}
.st35{fill:url(#SVGID_28_);}
.st36{fill:url(#SVGID_29_);}
.st37{fill:url(#SVGID_30_);}
.st38{fill:url(#SVGID_31_);}
.st39{fill:url(#SVGID_32_);}
.st40{fill:url(#SVGID_33_);}
.st41{fill:url(#SVGID_34_);}
.st42{fill:url(#SVGID_35_);}
.st43{fill:url(#SVGID_36_);}
.st44{fill:url(#SVGID_37_);}
.st45{fill:url(#SVGID_38_);}
.st46{fill:url(#SVGID_39_);}
.st47{fill:url(#SVGID_40_);}
.st48{fill:url(#SVGID_41_);}
.st49{fill:url(#SVGID_42_);}
.st50{fill:url(#SVGID_43_);}
.st51{fill:url(#SVGID_44_);}
.st52{fill:url(#SVGID_45_);}
.st53{fill:url(#SVGID_46_);}
.st54{fill:url(#SVGID_47_);}
.st55{fill:url(#SVGID_48_);}
.st56{fill:url(#SVGID_49_);}
.st57{fill:url(#SVGID_50_);}
.st58{fill:url(#SVGID_51_);}
.st59{fill:url(#SVGID_52_);}
.st60{fill:url(#SVGID_53_);}
.st61{fill:url(#SVGID_54_);}
.st62{fill:url(#SVGID_55_);}
.st63{fill:url(#SVGID_56_);}
.st64{fill:url(#SVGID_57_);}
.st65{fill:url(#SVGID_58_);}
.st66{fill:url(#SVGID_59_);}
.st67{fill:url(#SVGID_60_);}
.st68{fill:url(#SVGID_61_);}
.st69{fill:url(#SVGID_62_);}
.st70{fill:url(#SVGID_63_);}
.st71{fill:url(#SVGID_64_);}
.st72{fill:url(#SVGID_65_);}
.st73{fill:url(#SVGID_66_);}
.st74{fill:url(#SVGID_67_);}
.st75{fill:url(#SVGID_68_);}
.st76{fill:url(#SVGID_69_);}
.st77{fill:url(#SVGID_70_);}
.st78{fill:url(#SVGID_71_);}
.st79{fill:#EBF0FF;}
.st80{fill:url(#SVGID_72_);}
.st81{fill:url(#SVGID_73_);}
.st82{fill:url(#SVGID_74_);}
.st83{fill:url(#SVGID_75_);}
.st84{fill:url(#SVGID_76_);}
.st85{fill:url(#SVGID_77_);}
.st86{fill:url(#SVGID_78_);}
.st87{fill:url(#SVGID_79_);}
.st88{fill:url(#SVGID_80_);}
.st89{fill:url(#SVGID_81_);}
.st90{fill:url(#SVGID_82_);}
.st91{fill:url(#SVGID_83_);}
.st92{fill:url(#SVGID_84_);}
.st93{fill:url(#SVGID_85_);}
.st94{fill:url(#SVGID_86_);}
.st95{fill:url(#SVGID_87_);}
.st96{fill:url(#SVGID_88_);}
.st97{fill:url(#SVGID_89_);}
.st98{fill:url(#SVGID_90_);}
.st99{fill:url(#SVGID_91_);}
.st100{fill:url(#SVGID_92_);}
.st101{fill:url(#SVGID_93_);}
.st102{fill:url(#SVGID_94_);}
.st103{fill:url(#SVGID_95_);}
.st104{fill:url(#SVGID_96_);}
.st105{fill:url(#SVGID_97_);}
.st106{fill:url(#SVGID_98_);}
.st107{fill:url(#SVGID_99_);}
.st108{fill:url(#SVGID_100_);}
.st109{fill:url(#SVGID_101_);}
.st110{fill:url(#SVGID_102_);}
.st111{fill:url(#SVGID_103_);}
.st112{fill:url(#SVGID_104_);}
.st113{fill:url(#SVGID_105_);}
.st114{fill:url(#SVGID_106_);}
.st115{fill:url(#SVGID_107_);}
.st116{fill:url(#SVGID_108_);}
.st117{fill:url(#SVGID_109_);}
.st118{fill:url(#SVGID_110_);}
.st119{fill:url(#SVGID_111_);}
.st120{fill:url(#SVGID_112_);}
.st121{fill:url(#SVGID_113_);}
.st122{fill:url(#SVGID_114_);}
.st123{fill:url(#SVGID_115_);}
.st124{fill:url(#SVGID_116_);}
.st125{fill:url(#SVGID_117_);}
.st126{fill:url(#SVGID_118_);}
.st127{fill:url(#SVGID_119_);}
.st128{fill:url(#SVGID_120_);}
.st129{fill:url(#SVGID_121_);}
.st130{fill:url(#SVGID_122_);}
.st131{fill:url(#SVGID_123_);}
.st132{fill:url(#SVGID_124_);}
.st133{fill:url(#SVGID_125_);}
.st134{fill:url(#SVGID_126_);}
.st135{fill:url(#SVGID_127_);}
.st136{fill:url(#SVGID_128_);}
.st137{fill:url(#SVGID_129_);}
.st138{fill:url(#SVGID_130_);}
.st139{fill:url(#SVGID_131_);}
.st140{fill:url(#SVGID_132_);}
.st141{fill:url(#SVGID_133_);}
.st142{fill:url(#SVGID_134_);}
.st143{fill:url(#SVGID_135_);}
.st144{fill:url(#SVGID_136_);}
.st145{fill:url(#SVGID_137_);}
.st146{fill:url(#SVGID_138_);}
.st147{fill:url(#SVGID_139_);}
.st148{fill:url(#SVGID_140_);}
.st149{fill:url(#SVGID_141_);}
.st150{fill:url(#SVGID_142_);}
.st151{fill:url(#SVGID_143_);}
.st152{fill:url(#SVGID_144_);}
.st153{fill:url(#SVGID_145_);}
.st154{fill:url(#SVGID_146_);}
.st155{fill:url(#SVGID_147_);}
.st156{fill:url(#SVGID_148_);}
.st157{fill:url(#SVGID_149_);}
.st158{fill:url(#SVGID_150_);}
.st159{fill:url(#SVGID_151_);}
.st160{fill:url(#SVGID_152_);}
.st161{fill:url(#SVGID_153_);}
.st162{fill:url(#SVGID_154_);}
.st163{fill:url(#SVGID_155_);}
.st164{fill:url(#SVGID_156_);}
.st165{fill:url(#SVGID_157_);}
.st166{fill:url(#SVGID_158_);}
.st167{fill:url(#SVGID_159_);}
.st168{fill:url(#SVGID_160_);}
.st169{fill:url(#SVGID_161_);}
.st170{fill:url(#SVGID_162_);}
.st171{fill:url(#SVGID_163_);}
.st172{fill:url(#SVGID_164_);}
.st173{fill:url(#SVGID_165_);}
.st174{fill:url(#SVGID_166_);}
.st175{fill:url(#SVGID_167_);}
.st176{fill:url(#SVGID_168_);}
.st177{fill:url(#SVGID_169_);}
.st178{fill:url(#SVGID_170_);}
.st179{fill:url(#SVGID_171_);}
.st180{fill:url(#SVGID_172_);}
.st181{fill:url(#SVGID_173_);}
.st182{fill:url(#SVGID_174_);}
.st183{fill:url(#SVGID_175_);}
.st184{fill:url(#SVGID_176_);}
.st185{fill:url(#SVGID_177_);}
.st186{fill:url(#SVGID_178_);}
.st187{fill:url(#SVGID_179_);}
.st188{fill:url(#SVGID_180_);}
.st189{fill:url(#SVGID_181_);}
.st190{fill:url(#SVGID_182_);}
.st191{fill:url(#SVGID_183_);}
.st192{fill:#9DB6FF;}
.st193{fill:url(#SVGID_184_);}
.st194{fill:url(#SVGID_185_);}
.st195{fill:url(#SVGID_186_);}
.st196{fill:url(#SVGID_187_);}
.st197{fill:url(#SVGID_188_);}
.st198{fill:url(#SVGID_189_);}
.st199{fill:url(#SVGID_190_);}
.st200{fill:url(#SVGID_191_);}
.st201{fill:url(#SVGID_192_);}
.st202{fill:url(#SVGID_193_);}
.st203{fill:#9AB3FF;}
.st204{fill:url(#SVGID_194_);}
.st205{fill:url(#SVGID_195_);}
.st206{fill:url(#SVGID_196_);}
.st207{fill:url(#SVGID_197_);}
.st208{fill:url(#SVGID_198_);}
.st209{fill:url(#SVGID_199_);}
.st210{fill:url(#SVGID_200_);}
.st211{fill:url(#SVGID_201_);}
.st212{fill:url(#SVGID_202_);}
.st213{fill:url(#SVGID_203_);}
.st214{fill:url(#SVGID_204_);}
.st215{fill:url(#SVGID_205_);}
.st216{fill:url(#SVGID_206_);}
.st217{fill:url(#SVGID_207_);}
.st218{fill:url(#SVGID_208_);}
.st219{fill:url(#SVGID_209_);}
.st220{fill:url(#SVGID_210_);}
.st221{fill:url(#SVGID_211_);}
.st222{fill:url(#SVGID_212_);}
.st223{fill:url(#SVGID_213_);}
.st224{fill:url(#SVGID_214_);}
.st225{fill:url(#SVGID_215_);}
.st226{fill:url(#SVGID_216_);}
.st227{fill:url(#SVGID_217_);}
.st228{fill:url(#SVGID_218_);}
.st229{fill:url(#SVGID_219_);}
.st230{fill:url(#SVGID_220_);}
.st231{fill:url(#SVGID_221_);}
.st232{fill:url(#SVGID_222_);}
.st233{fill:url(#SVGID_223_);}
.st234{fill:url(#SVGID_224_);}
.st235{fill:url(#SVGID_225_);}
.st236{fill:url(#SVGID_226_);}
.st237{fill:url(#SVGID_227_);}
.st238{fill:url(#SVGID_228_);}
.st239{fill:url(#SVGID_229_);}
.st240{fill:url(#SVGID_230_);}
.st241{fill:url(#SVGID_231_);}
.st242{fill:url(#SVGID_232_);}
.st243{fill:url(#SVGID_233_);}
.st244{fill:url(#SVGID_234_);}
.st245{fill:url(#SVGID_235_);}
.st246{fill:url(#SVGID_236_);}
.st247{fill:url(#SVGID_237_);}
.st248{fill:url(#SVGID_238_);}
.st249{fill:url(#SVGID_239_);}
.st250{fill:url(#SVGID_240_);}
.st251{fill:url(#SVGID_241_);}
.st252{fill:url(#SVGID_242_);}
.st253{fill:url(#SVGID_243_);}
.st254{fill:url(#SVGID_244_);}
.st255{fill:url(#SVGID_245_);}
.st256{fill:url(#SVGID_246_);}
.st257{fill:url(#SVGID_247_);}
.st258{fill:url(#SVGID_248_);}
.st259{fill:url(#SVGID_249_);}
.st260{fill:url(#SVGID_250_);}
.st261{fill:url(#SVGID_251_);}
.st262{fill:url(#SVGID_252_);}
.st263{fill:url(#SVGID_253_);}
.st264{fill:url(#SVGID_254_);}
.st265{fill:url(#SVGID_255_);}
.st266{fill:url(#SVGID_256_);}
.st267{fill:url(#SVGID_257_);}
.st268{fill:url(#SVGID_258_);}
.st269{fill:url(#SVGID_259_);}
.st270{fill:url(#SVGID_260_);}
.st271{fill:url(#SVGID_261_);}
.st272{fill:url(#SVGID_262_);}
.st273{fill:url(#SVGID_263_);}
.st274{fill:url(#SVGID_264_);}
.st275{fill:url(#SVGID_265_);}
.st276{fill:url(#SVGID_266_);}
.st277{fill:url(#SVGID_267_);}
.st278{fill:url(#SVGID_268_);}
.st279{fill:url(#SVGID_269_);}
.st280{fill:url(#SVGID_270_);}
.st281{fill:url(#SVGID_271_);}
.st282{fill:url(#SVGID_272_);}
.st283{fill:url(#SVGID_273_);}
.st284{fill:url(#SVGID_274_);}
.st285{fill:url(#SVGID_275_);}
.st286{fill:url(#SVGID_276_);}
.st287{fill:url(#SVGID_277_);}
.st288{fill:url(#SVGID_278_);}
.st289{fill:url(#SVGID_279_);}
.st290{fill:url(#SVGID_280_);}
.st291{fill:#B6C9FF;}
.st292{fill:url(#SVGID_281_);}
.st293{fill:url(#SVGID_282_);}
.st294{fill:url(#SVGID_283_);}
.st295{fill:url(#SVGID_284_);}
.st296{fill:url(#SVGID_285_);}
.st297{fill:url(#SVGID_286_);}
.st298{fill:url(#SVGID_287_);}
.st299{fill:url(#SVGID_288_);}
.st300{fill:url(#SVGID_289_);}
.st301{fill:#B1C5FF;}
.st302{fill:url(#SVGID_290_);}
.st303{fill:url(#SVGID_291_);}
.st304{fill:url(#SVGID_292_);}
.st305{fill:url(#SVGID_293_);}
.st306{fill:url(#SVGID_294_);}
.st307{fill:url(#SVGID_295_);}
.st308{fill:url(#SVGID_296_);}
.st309{fill:url(#SVGID_297_);}
.st310{fill:url(#SVGID_298_);}
.st311{fill:url(#SVGID_299_);}
.st312{fill:url(#SVGID_300_);}
.st313{fill:url(#SVGID_301_);}
.st314{fill:url(#SVGID_302_);}
.st315{fill:url(#SVGID_303_);}
.st316{fill:url(#SVGID_304_);}
.st317{fill:url(#SVGID_305_);}
.st318{fill:url(#SVGID_306_);}
.st319{fill:url(#SVGID_307_);}
.st320{fill:url(#SVGID_308_);}
.st321{fill:#87A4FF;}
.st322{fill:url(#SVGID_309_);}
.st323{fill:url(#SVGID_310_);}
.st324{fill:url(#SVGID_311_);}
.st325{fill:url(#SVGID_312_);}
.st326{fill:url(#SVGID_313_);}
.st327{fill:url(#SVGID_314_);}
.st328{fill:url(#SVGID_315_);}
.st329{fill:url(#SVGID_316_);}
.st330{fill:url(#SVGID_317_);}
.st331{fill:url(#SVGID_318_);}
.st332{fill:url(#SVGID_319_);}
.st333{fill:url(#SVGID_320_);}
.st334{fill:url(#SVGID_321_);}
.st335{fill:url(#SVGID_322_);}
.st336{fill:url(#SVGID_323_);}
.st337{fill:url(#SVGID_324_);}
.st338{fill:url(#SVGID_325_);}
.st339{fill:url(#SVGID_326_);}
.st340{fill:url(#SVGID_327_);}
.st341{fill:url(#SVGID_328_);}
.st342{fill:url(#SVGID_329_);}
.st343{fill:url(#SVGID_330_);}
.st344{fill:url(#SVGID_331_);}
</style>
<g id="暂无内容">
</g>
<g id="图层_2">
</g>
<g id="图层_3">
</g>
<g id="图层_4">
</g>
<g id="图层_5">
<g>
<g>
<circle class="st0" cx="665.41" cy="171.55" r="19.78"/>
<circle class="st1" cx="97.29" cy="623.35" r="19.78"/>
<path class="st1" d="M218.42,685.35c-7.38,0-13.38-6-13.38-13.38s6-13.38,13.38-13.38c7.38,0,13.38,6,13.38,13.38
S225.79,685.35,218.42,685.35z M218.42,665.02c-3.83,0-6.95,3.12-6.95,6.95c0,3.83,3.12,6.95,6.95,6.95
c3.83,0,6.95-3.12,6.95-6.95C225.37,668.14,222.25,665.02,218.42,665.02z"/>
<path class="st0" d="M596.85,112.21h-19.98V92.23c0-3.66-2.96-6.62-6.62-6.62c-3.66,0-6.62,2.96-6.62,6.62v19.98h-19.98
c-3.66,0-6.62,2.96-6.62,6.62c0,3.66,2.96,6.62,6.62,6.62h19.98v19.98c0,3.66,2.96,6.62,6.62,6.62c3.66,0,6.62-2.96,6.62-6.62
v-19.98h19.98c3.66,0,6.62-2.96,6.62-6.62C603.47,115.17,600.5,112.21,596.85,112.21z"/>
<path class="st1" d="M654.43,689.24h-14.84V674.4c0-3.04-2.47-5.51-5.51-5.51c-3.04,0-5.51,2.47-5.51,5.51v14.84h-14.84
c-3.04,0-5.51,2.47-5.51,5.51s2.47,5.51,5.51,5.51h14.84v14.84c0,3.04,2.47,5.51,5.51,5.51c3.04,0,5.51-2.47,5.51-5.51v-14.84
h14.84c3.04,0,5.51-2.47,5.51-5.51S657.47,689.24,654.43,689.24z"/>
<path class="st0" d="M119.36,127.65c8.7,8.7,22.82,8.7,31.52,0l0,0c8.7-8.7,8.7-22.82,0-31.52l-6.66-6.66
c-8.7-8.7-22.82-8.7-31.52,0h0c-8.7,8.7-8.7,22.82,0,31.52L119.36,127.65z"/>
<path class="st0" d="M234.08,119.6c3.97,3.97,10.41,3.97,14.38,0c3.97-3.97,3.97-10.41,0-14.38l-10.01-10.01
c-3.97-3.97-10.41-3.97-14.38,0c-3.97,3.97-3.97,10.41,0,14.38L234.08,119.6z"/>
<path class="st0" d="M83.01,320.32c3.97,3.97,10.41,3.97,14.38,0h0c3.97-3.97,3.97-10.41,0-14.38l-10.01-10.01
c-3.97-3.97-10.41-3.97-14.38,0c-3.97,3.97-3.97,10.41,0,14.38L83.01,320.32z"/>
<path class="st1" d="M610.97,627.78c-3.97-3.97-10.41-3.97-14.38,0c-3.97,3.97-3.97,10.41,0,14.38l10.01,10.01
c3.97,3.97,10.41,3.97,14.38,0c3.97-3.97,3.97-10.41,0-14.38L610.97,627.78z"/>
<circle class="st1" cx="698.59" cy="636.27" r="32.77"/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="387.3627" y1="91.5774" x2="387.3627" y2="710.569">
<stop offset="1.918004e-07" style="stop-color:#F6F8FF"/>
<stop offset="1" style="stop-color:#D9E3FF"/>
</linearGradient>
<path class="st2" d="M699.24,484.31l-31.51-31.51l-19.97-21.27c-5.17-5.43-5.24-13.94-0.16-19.46l0,0
c5.55-6.03,15.04-6.14,20.72-0.23l4.04,4.41l9.32,9.32c5.41,5.41,14.29,5.82,19.8,0.51c5.61-5.41,5.68-14.35,0.19-19.84
l-18.63-18.63c-6.88-6.88-11.36-15.82-12.66-25.46c-18.22-135.39-134.2-239.79-274.58-239.79c-4.79,0-9.55,0.13-14.28,0.37
c-5.11,0.26-10.1-1.61-13.71-5.23l-19.14-19.14c-8.55-8.55-22.47-9.18-31.32-0.94c-9.29,8.65-9.48,23.19-0.59,32.09l3.34,3.34
l10.41,10.94c6.54,6.92,5.57,18-2.08,23.68c-6.51,4.83-14.68,3.26-20.19-2.69l-20.69-20.46l-18.04-18.04
c-3.83-3.83-10.06-4.28-14.13-0.71c-4.46,3.92-4.62,10.71-0.49,14.84l13.14,13.14l8.56,8.24c8.7,8.52,8.89,21.8,0.24,30.36l0,0
c-8.59,8.49-22.45,8.38-30.89-0.26l-14.74-15.33l-35.31-35.31c-8.7-8.7-22.82-8.7-31.52,0h0c-8.7,8.7-8.7,22.82,0,31.52
l25.61,25.69l8.98,8.38c8.49,8.43,8.69,22.09,0.45,30.76l0,0c-8.82,9.29-23.69,9.11-32.28-0.4l-4.96-5.44l-15.36-15.44
c-12.62-12.62-33.2-13.4-46.16-1.12c-13.45,12.73-13.67,33.97-0.66,46.98l37.34,37.33l17.8,17.59c6.71,6.64,6.5,17.55-0.46,23.93
l0,0c-6.58,6.03-16.76,5.75-22.99-0.63l-5.35-5.55l-8.11-8.11c-3.97-3.97-10.41-3.97-14.38,0h0c-5.06,4.2-3.69,10.69,0.06,14.44
l17.79,17.79l36.99,36.09c6.38,6.22,6.41,16.47,0.08,22.73l0,0c-6.18,6.11-16.12,6.15-22.34,0.08l-17.67-17.24l-25.96-25.96
c-5.41-5.41-14.29-5.82-19.8-0.51c-5.61,5.41-5.68,14.35-0.19,19.84l43.89,43.89c3.55,3.55,5.91,8.11,6.84,13.05
c23.49,124.93,130.8,220.27,261.3,225.36c7.2,0.28,14.06,3.16,19.28,8.14c8.46,8.08,20.27,19.34,20.27,19.34
c8.55,8.55,22.47,9.18,31.32,0.94c9.29-8.65,9.48-23.19,0.59-32.09l-2.26-2.26l-9.81-10.24c-5.59-6.06-5.03-15.55,1.23-20.92l0,0
c5.93-5.08,14.81-4.58,20.13,1.13l20.22,21.22l22.02,22.02c8.9,8.89,23.44,8.7,32.09-0.58c8.24-8.84,7.6-22.77-0.94-31.32
l-9.5-9.5c0,0-3.02-3.11-6.89-7.09c-9.03-9.28-8.71-24.16,0.7-33.05l0.48-0.45c9.02-8.52,23.15-8.43,32.07,0.2l13.82,13.38
l5.91,5.91c3.83,3.83,10.07,4.28,14.13,0.71c4.46-3.92,4.62-10.72,0.49-14.84l-5.15-5.15c-0.94,0.44-20.83-20.04-32.63-32.32
c-4.75-4.94-4.56-12.79,0.41-17.5h0c4.95-4.69,12.77-4.47,17.45,0.48l31.03,32.82l14.15,14.15c12.62,12.62,33.19,13.4,46.16,1.13
c13.45-12.73,13.67-33.97,0.66-46.98l-22.57-22.57l-11.4-12.43c-5.57-6.02-5.32-15.38,0.56-21.1h0
c6.11-5.94,15.92-5.64,21.67,0.65l2.92,3.4l14.64,14.65c8.9,8.9,23.44,8.7,32.09-0.58C708.42,506.79,707.79,492.86,699.24,484.31
z"/>
<path class="st3" d="M427.04,205.2H412.2v-14.84c0-3.04-2.47-5.51-5.51-5.51c-3.04,0-5.51,2.47-5.51,5.51v14.84h-14.84
c-3.04,0-5.51,2.47-5.51,5.51s2.47,5.51,5.51,5.51h14.84v14.84c0,3.04,2.47,5.51,5.51,5.51c3.04,0,5.51-2.47,5.51-5.51v-14.84
h14.84c3.04,0,5.51-2.47,5.51-5.51S430.08,205.2,427.04,205.2z"/>
<path class="st3" d="M582.8,423.74c-7.38,0-13.38-6-13.38-13.38c0-7.38,6-13.38,13.38-13.38s13.38,6,13.38,13.38
C596.17,417.74,590.17,423.74,582.8,423.74z M582.8,403.4c-3.83,0-6.95,3.12-6.95,6.95c0,3.83,3.12,6.95,6.95,6.95
c3.83,0,6.95-3.12,6.95-6.95C589.75,406.52,586.63,403.4,582.8,403.4z"/>
<path class="st3" d="M220.15,525.77c-7.38,0-13.38-6-13.38-13.38c0-7.38,6-13.38,13.38-13.38c7.38,0,13.38,6,13.38,13.38
C233.52,519.77,227.52,525.77,220.15,525.77z M220.15,505.44c-3.83,0-6.95,3.12-6.95,6.95c0,3.83,3.12,6.95,6.95,6.95
c3.83,0,6.95-3.12,6.95-6.95C227.1,508.56,223.98,505.44,220.15,505.44z"/>
</g>
<g>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="285.1193" y1="174.8921" x2="519.4578" y2="580.7783">
<stop offset="0" style="stop-color:#D4E1FF"/>
<stop offset="1" style="stop-color:#7798FF"/>
</linearGradient>
<path class="st4" d="M579.77,259.16c-39.46,0-71.45-31.99-71.45-71.45c0-0.02,0-0.04,0-0.06c0-3.61-2.38-6.79-5.89-7.64
c-30.42-7.37-67.51-12.96-110.05-12.96c-45.16,0-81.57,4.22-110.46,10.16c-3.72,0.77-6.34,4.1-6.19,7.9
c0.03,0.87,0.05,1.74,0.05,2.61c0,39.46-31.99,71.45-71.45,71.45c-2.93,0-5.81-0.18-8.64-0.52c-4.57-0.56-8.57,3.09-8.57,7.69
v85.62c0,225.36,156.32,281.4,201.39,292.23c1.39,0.33,2.84,0.27,4.2-0.18c52.25-17.16,202.33-69.45,202.33-292.05v-85.41
c0-4.61-3.99-8.12-8.58-7.69C584.26,259.05,582.03,259.16,579.77,259.16z"/>
<path class="st3" d="M428.48,459.57h-18.22v-37.41c32.12-7.9,56.02-36.93,56.02-71.46c0-40.58-33.01-73.6-73.59-73.6
s-73.59,33.01-73.59,73.6c0,34.22,23.47,63.06,55.17,71.26v87.63c0,9.94,8.06,18,18,18s18-8.06,18-18v-14.03h18.22
c9.94,0,18-8.06,18-18S438.42,459.57,428.48,459.57z M355.1,350.7c0-20.73,16.87-37.6,37.59-37.6s37.59,16.87,37.59,37.6
c0,20.73-16.87,37.59-37.59,37.59S355.1,371.43,355.1,350.7z"/>
</g>
</g>
</g>
<g id="图层_6">
</g>
<g id="图层_7">
</g>
</svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 800 800" style="enable-background:new 0 0 800 800;" xml:space="preserve">
<style type="text/css">
.st0{fill:#F3F6FF;}
.st1{fill:#DFE7FF;}
.st2{fill:url(#SVGID_1_);}
.st3{fill:#FFFFFF;}
.st4{fill:url(#SVGID_2_);}
.st5{fill:#F7F8F8;}
.st6{fill:url(#SVGID_3_);}
.st7{fill:url(#SVGID_4_);}
.st8{fill:url(#SVGID_5_);}
.st9{fill:url(#SVGID_6_);}
.st10{fill:url(#SVGID_7_);}
.st11{fill:url(#SVGID_8_);}
.st12{fill:url(#SVGID_9_);}
.st13{fill:#B2C6FF;}
.st14{fill:url(#SVGID_10_);}
.st15{fill:url(#SVGID_11_);}
.st16{fill:url(#SVGID_12_);}
.st17{fill:url(#SVGID_13_);}
.st18{fill:url(#SVGID_14_);}
.st19{fill:url(#SVGID_15_);}
.st20{fill:url(#SVGID_16_);}
.st21{fill:#D0DEFF;}
.st22{fill:url(#SVGID_17_);}
.st23{fill:url(#SVGID_18_);}
.st24{fill:#7899FF;}
.st25{fill:#ECF1FF;}
.st26{fill:url(#SVGID_19_);}
.st27{fill:url(#SVGID_20_);}
.st28{fill:url(#SVGID_21_);}
.st29{fill:url(#SVGID_22_);}
.st30{fill:url(#SVGID_23_);}
.st31{fill:url(#SVGID_24_);}
.st32{fill:url(#SVGID_25_);}
.st33{fill:url(#SVGID_26_);}
.st34{fill:url(#SVGID_27_);}
.st35{fill:url(#SVGID_28_);}
.st36{fill:url(#SVGID_29_);}
.st37{fill:url(#SVGID_30_);}
.st38{fill:url(#SVGID_31_);}
.st39{fill:url(#SVGID_32_);}
.st40{fill:url(#SVGID_33_);}
.st41{fill:url(#SVGID_34_);}
.st42{fill:url(#SVGID_35_);}
.st43{fill:url(#SVGID_36_);}
.st44{fill:url(#SVGID_37_);}
.st45{fill:url(#SVGID_38_);}
.st46{fill:url(#SVGID_39_);}
.st47{fill:url(#SVGID_40_);}
.st48{fill:url(#SVGID_41_);}
.st49{fill:url(#SVGID_42_);}
.st50{fill:url(#SVGID_43_);}
.st51{fill:url(#SVGID_44_);}
.st52{fill:url(#SVGID_45_);}
.st53{fill:url(#SVGID_46_);}
.st54{fill:url(#SVGID_47_);}
.st55{fill:url(#SVGID_48_);}
.st56{fill:url(#SVGID_49_);}
.st57{fill:url(#SVGID_50_);}
.st58{fill:url(#SVGID_51_);}
.st59{fill:url(#SVGID_52_);}
.st60{fill:url(#SVGID_53_);}
.st61{fill:url(#SVGID_54_);}
.st62{fill:url(#SVGID_55_);}
.st63{fill:url(#SVGID_56_);}
.st64{fill:url(#SVGID_57_);}
.st65{fill:url(#SVGID_58_);}
.st66{fill:url(#SVGID_59_);}
.st67{fill:url(#SVGID_60_);}
.st68{fill:url(#SVGID_61_);}
.st69{fill:url(#SVGID_62_);}
.st70{fill:url(#SVGID_63_);}
.st71{fill:url(#SVGID_64_);}
.st72{fill:url(#SVGID_65_);}
.st73{fill:url(#SVGID_66_);}
.st74{fill:url(#SVGID_67_);}
.st75{fill:url(#SVGID_68_);}
.st76{fill:url(#SVGID_69_);}
.st77{fill:url(#SVGID_70_);}
.st78{fill:url(#SVGID_71_);}
.st79{fill:#EBF0FF;}
.st80{fill:url(#SVGID_72_);}
.st81{fill:url(#SVGID_73_);}
.st82{fill:url(#SVGID_74_);}
.st83{fill:url(#SVGID_75_);}
.st84{fill:url(#SVGID_76_);}
.st85{fill:url(#SVGID_77_);}
.st86{fill:url(#SVGID_78_);}
.st87{fill:url(#SVGID_79_);}
.st88{fill:url(#SVGID_80_);}
.st89{fill:url(#SVGID_81_);}
.st90{fill:url(#SVGID_82_);}
.st91{fill:url(#SVGID_83_);}
.st92{fill:url(#SVGID_84_);}
.st93{fill:url(#SVGID_85_);}
.st94{fill:url(#SVGID_86_);}
.st95{fill:url(#SVGID_87_);}
.st96{fill:url(#SVGID_88_);}
.st97{fill:url(#SVGID_89_);}
.st98{fill:url(#SVGID_90_);}
.st99{fill:url(#SVGID_91_);}
.st100{fill:url(#SVGID_92_);}
.st101{fill:url(#SVGID_93_);}
.st102{fill:url(#SVGID_94_);}
.st103{fill:url(#SVGID_95_);}
.st104{fill:url(#SVGID_96_);}
.st105{fill:url(#SVGID_97_);}
.st106{fill:url(#SVGID_98_);}
.st107{fill:url(#SVGID_99_);}
.st108{fill:url(#SVGID_100_);}
.st109{fill:url(#SVGID_101_);}
.st110{fill:url(#SVGID_102_);}
.st111{fill:url(#SVGID_103_);}
.st112{fill:url(#SVGID_104_);}
.st113{fill:url(#SVGID_105_);}
.st114{fill:url(#SVGID_106_);}
.st115{fill:url(#SVGID_107_);}
.st116{fill:url(#SVGID_108_);}
.st117{fill:url(#SVGID_109_);}
.st118{fill:url(#SVGID_110_);}
.st119{fill:url(#SVGID_111_);}
.st120{fill:url(#SVGID_112_);}
.st121{fill:url(#SVGID_113_);}
.st122{fill:url(#SVGID_114_);}
.st123{fill:url(#SVGID_115_);}
.st124{fill:url(#SVGID_116_);}
.st125{fill:url(#SVGID_117_);}
.st126{fill:url(#SVGID_118_);}
.st127{fill:url(#SVGID_119_);}
.st128{fill:url(#SVGID_120_);}
.st129{fill:url(#SVGID_121_);}
.st130{fill:url(#SVGID_122_);}
.st131{fill:url(#SVGID_123_);}
.st132{fill:url(#SVGID_124_);}
.st133{fill:url(#SVGID_125_);}
.st134{fill:url(#SVGID_126_);}
.st135{fill:url(#SVGID_127_);}
.st136{fill:url(#SVGID_128_);}
.st137{fill:url(#SVGID_129_);}
.st138{fill:url(#SVGID_130_);}
.st139{fill:url(#SVGID_131_);}
.st140{fill:url(#SVGID_132_);}
.st141{fill:url(#SVGID_133_);}
.st142{fill:url(#SVGID_134_);}
.st143{fill:url(#SVGID_135_);}
.st144{fill:url(#SVGID_136_);}
.st145{fill:url(#SVGID_137_);}
.st146{fill:url(#SVGID_138_);}
.st147{fill:url(#SVGID_139_);}
.st148{fill:url(#SVGID_140_);}
.st149{fill:url(#SVGID_141_);}
.st150{fill:url(#SVGID_142_);}
.st151{fill:url(#SVGID_143_);}
.st152{fill:url(#SVGID_144_);}
.st153{fill:url(#SVGID_145_);}
.st154{fill:url(#SVGID_146_);}
.st155{fill:url(#SVGID_147_);}
.st156{fill:url(#SVGID_148_);}
.st157{fill:url(#SVGID_149_);}
.st158{fill:url(#SVGID_150_);}
.st159{fill:url(#SVGID_151_);}
.st160{fill:url(#SVGID_152_);}
.st161{fill:url(#SVGID_153_);}
.st162{fill:url(#SVGID_154_);}
.st163{fill:url(#SVGID_155_);}
.st164{fill:url(#SVGID_156_);}
.st165{fill:url(#SVGID_157_);}
.st166{fill:url(#SVGID_158_);}
.st167{fill:url(#SVGID_159_);}
.st168{fill:url(#SVGID_160_);}
.st169{fill:url(#SVGID_161_);}
.st170{fill:url(#SVGID_162_);}
.st171{fill:url(#SVGID_163_);}
.st172{fill:url(#SVGID_164_);}
.st173{fill:url(#SVGID_165_);}
.st174{fill:url(#SVGID_166_);}
.st175{fill:url(#SVGID_167_);}
.st176{fill:url(#SVGID_168_);}
.st177{fill:url(#SVGID_169_);}
.st178{fill:url(#SVGID_170_);}
.st179{fill:url(#SVGID_171_);}
.st180{fill:url(#SVGID_172_);}
.st181{fill:url(#SVGID_173_);}
.st182{fill:url(#SVGID_174_);}
.st183{fill:url(#SVGID_175_);}
.st184{fill:url(#SVGID_176_);}
.st185{fill:url(#SVGID_177_);}
.st186{fill:url(#SVGID_178_);}
.st187{fill:url(#SVGID_179_);}
.st188{fill:url(#SVGID_180_);}
.st189{fill:url(#SVGID_181_);}
.st190{fill:url(#SVGID_182_);}
.st191{fill:url(#SVGID_183_);}
.st192{fill:#9DB6FF;}
.st193{fill:url(#SVGID_184_);}
.st194{fill:url(#SVGID_185_);}
.st195{fill:url(#SVGID_186_);}
.st196{fill:url(#SVGID_187_);}
.st197{fill:url(#SVGID_188_);}
.st198{fill:url(#SVGID_189_);}
.st199{fill:url(#SVGID_190_);}
.st200{fill:url(#SVGID_191_);}
.st201{fill:url(#SVGID_192_);}
.st202{fill:url(#SVGID_193_);}
.st203{fill:#9AB3FF;}
.st204{fill:url(#SVGID_194_);}
.st205{fill:url(#SVGID_195_);}
.st206{fill:url(#SVGID_196_);}
.st207{fill:url(#SVGID_197_);}
.st208{fill:url(#SVGID_198_);}
.st209{fill:url(#SVGID_199_);}
.st210{fill:url(#SVGID_200_);}
.st211{fill:url(#SVGID_201_);}
.st212{fill:url(#SVGID_202_);}
.st213{fill:url(#SVGID_203_);}
.st214{fill:url(#SVGID_204_);}
.st215{fill:url(#SVGID_205_);}
.st216{fill:url(#SVGID_206_);}
.st217{fill:url(#SVGID_207_);}
.st218{fill:url(#SVGID_208_);}
.st219{fill:url(#SVGID_209_);}
.st220{fill:url(#SVGID_210_);}
.st221{fill:url(#SVGID_211_);}
.st222{fill:url(#SVGID_212_);}
.st223{fill:url(#SVGID_213_);}
.st224{fill:url(#SVGID_214_);}
.st225{fill:url(#SVGID_215_);}
.st226{fill:url(#SVGID_216_);}
.st227{fill:url(#SVGID_217_);}
.st228{fill:url(#SVGID_218_);}
.st229{fill:url(#SVGID_219_);}
.st230{fill:url(#SVGID_220_);}
.st231{fill:url(#SVGID_221_);}
.st232{fill:url(#SVGID_222_);}
.st233{fill:url(#SVGID_223_);}
.st234{fill:url(#SVGID_224_);}
.st235{fill:url(#SVGID_225_);}
.st236{fill:url(#SVGID_226_);}
.st237{fill:url(#SVGID_227_);}
.st238{fill:url(#SVGID_228_);}
.st239{fill:url(#SVGID_229_);}
.st240{fill:url(#SVGID_230_);}
.st241{fill:url(#SVGID_231_);}
.st242{fill:url(#SVGID_232_);}
.st243{fill:url(#SVGID_233_);}
.st244{fill:url(#SVGID_234_);}
.st245{fill:url(#SVGID_235_);}
.st246{fill:url(#SVGID_236_);}
.st247{fill:url(#SVGID_237_);}
.st248{fill:url(#SVGID_238_);}
.st249{fill:url(#SVGID_239_);}
.st250{fill:url(#SVGID_240_);}
.st251{fill:url(#SVGID_241_);}
.st252{fill:url(#SVGID_242_);}
.st253{fill:url(#SVGID_243_);}
.st254{fill:url(#SVGID_244_);}
.st255{fill:url(#SVGID_245_);}
.st256{fill:url(#SVGID_246_);}
.st257{fill:url(#SVGID_247_);}
.st258{fill:url(#SVGID_248_);}
.st259{fill:url(#SVGID_249_);}
.st260{fill:url(#SVGID_250_);}
.st261{fill:url(#SVGID_251_);}
.st262{fill:url(#SVGID_252_);}
.st263{fill:url(#SVGID_253_);}
.st264{fill:url(#SVGID_254_);}
.st265{fill:url(#SVGID_255_);}
.st266{fill:url(#SVGID_256_);}
.st267{fill:url(#SVGID_257_);}
.st268{fill:url(#SVGID_258_);}
.st269{fill:url(#SVGID_259_);}
.st270{fill:url(#SVGID_260_);}
.st271{fill:url(#SVGID_261_);}
.st272{fill:url(#SVGID_262_);}
.st273{fill:url(#SVGID_263_);}
.st274{fill:url(#SVGID_264_);}
.st275{fill:url(#SVGID_265_);}
.st276{fill:url(#SVGID_266_);}
.st277{fill:url(#SVGID_267_);}
.st278{fill:url(#SVGID_268_);}
.st279{fill:url(#SVGID_269_);}
.st280{fill:url(#SVGID_270_);}
.st281{fill:url(#SVGID_271_);}
.st282{fill:url(#SVGID_272_);}
.st283{fill:url(#SVGID_273_);}
.st284{fill:url(#SVGID_274_);}
.st285{fill:url(#SVGID_275_);}
.st286{fill:url(#SVGID_276_);}
.st287{fill:url(#SVGID_277_);}
.st288{fill:url(#SVGID_278_);}
.st289{fill:url(#SVGID_279_);}
.st290{fill:url(#SVGID_280_);}
.st291{fill:#B6C9FF;}
.st292{fill:url(#SVGID_281_);}
.st293{fill:url(#SVGID_282_);}
.st294{fill:url(#SVGID_283_);}
.st295{fill:url(#SVGID_284_);}
.st296{fill:url(#SVGID_285_);}
.st297{fill:url(#SVGID_286_);}
.st298{fill:url(#SVGID_287_);}
.st299{fill:url(#SVGID_288_);}
.st300{fill:url(#SVGID_289_);}
.st301{fill:#B1C5FF;}
.st302{fill:url(#SVGID_290_);}
.st303{fill:url(#SVGID_291_);}
.st304{fill:url(#SVGID_292_);}
.st305{fill:url(#SVGID_293_);}
.st306{fill:url(#SVGID_294_);}
.st307{fill:url(#SVGID_295_);}
.st308{fill:url(#SVGID_296_);}
.st309{fill:url(#SVGID_297_);}
.st310{fill:url(#SVGID_298_);}
.st311{fill:url(#SVGID_299_);}
.st312{fill:url(#SVGID_300_);}
.st313{fill:url(#SVGID_301_);}
.st314{fill:url(#SVGID_302_);}
.st315{fill:url(#SVGID_303_);}
.st316{fill:url(#SVGID_304_);}
.st317{fill:url(#SVGID_305_);}
.st318{fill:url(#SVGID_306_);}
.st319{fill:url(#SVGID_307_);}
.st320{fill:url(#SVGID_308_);}
.st321{fill:#87A4FF;}
.st322{fill:url(#SVGID_309_);}
.st323{fill:url(#SVGID_310_);}
.st324{fill:url(#SVGID_311_);}
.st325{fill:url(#SVGID_312_);}
.st326{fill:url(#SVGID_313_);}
.st327{fill:url(#SVGID_314_);}
.st328{fill:url(#SVGID_315_);}
.st329{fill:url(#SVGID_316_);}
.st330{fill:url(#SVGID_317_);}
.st331{fill:url(#SVGID_318_);}
.st332{fill:url(#SVGID_319_);}
.st333{fill:url(#SVGID_320_);}
.st334{fill:url(#SVGID_321_);}
.st335{fill:url(#SVGID_322_);}
.st336{fill:url(#SVGID_323_);}
.st337{fill:url(#SVGID_324_);}
.st338{fill:url(#SVGID_325_);}
.st339{fill:url(#SVGID_326_);}
.st340{fill:url(#SVGID_327_);}
.st341{fill:url(#SVGID_328_);}
.st342{fill:url(#SVGID_329_);}
.st343{fill:url(#SVGID_330_);}
.st344{fill:url(#SVGID_331_);}
</style>
<g id="暂无内容">
<g>
<g>
<circle class="st0" cx="667.95" cy="167.6" r="19.78"/>
<circle class="st1" cx="99.83" cy="619.4" r="19.78"/>
<path class="st1" d="M220.96,681.4c-7.38,0-13.38-6-13.38-13.38c0-7.38,6-13.38,13.38-13.38s13.38,6,13.38,13.38
C234.33,675.4,228.33,681.4,220.96,681.4z M220.96,661.07c-3.83,0-6.95,3.12-6.95,6.95c0,3.83,3.12,6.95,6.95,6.95
c3.83,0,6.95-3.12,6.95-6.95C227.91,664.19,224.79,661.07,220.96,661.07z"/>
<path class="st0" d="M599.39,108.26h-19.98V88.28c0-3.66-2.96-6.62-6.62-6.62c-3.66,0-6.62,2.96-6.62,6.62v19.98h-19.98
c-3.66,0-6.62,2.96-6.62,6.62c0,3.66,2.96,6.62,6.62,6.62h19.98v19.98c0,3.66,2.96,6.62,6.62,6.62c3.66,0,6.62-2.96,6.62-6.62
V121.5h19.98c3.66,0,6.62-2.96,6.62-6.62C606,111.22,603.04,108.26,599.39,108.26z"/>
<path class="st1" d="M656.96,685.29h-14.84v-14.84c0-3.04-2.47-5.51-5.51-5.51c-3.04,0-5.51,2.47-5.51,5.51v14.84h-14.84
c-3.04,0-5.51,2.47-5.51,5.51s2.47,5.51,5.51,5.51h14.84v14.84c0,3.04,2.47,5.51,5.51,5.51c3.04,0,5.51-2.47,5.51-5.51V696.3
h14.84c3.04,0,5.51-2.47,5.51-5.51S660,685.29,656.96,685.29z"/>
<path class="st0" d="M121.9,123.7c8.7,8.7,22.82,8.7,31.52,0l0,0c8.7-8.7,8.7-22.82,0-31.52l-6.66-6.66
c-8.7-8.7-22.82-8.7-31.52,0h0c-8.7,8.7-8.7,22.82,0,31.52L121.9,123.7z"/>
<path class="st0" d="M236.62,115.65c3.97,3.97,10.41,3.97,14.38,0c3.97-3.97,3.97-10.41,0-14.38l-10.01-10.01
c-3.97-3.97-10.41-3.97-14.38,0c-3.97,3.97-3.97,10.41,0,14.38L236.62,115.65z"/>
<path class="st0" d="M85.55,316.37c3.97,3.97,10.41,3.97,14.38,0h0c3.97-3.97,3.97-10.41,0-14.38l-10.01-10.01
c-3.97-3.97-10.41-3.97-14.38,0c-3.97,3.97-3.97,10.41,0,14.38L85.55,316.37z"/>
<path class="st1" d="M613.51,623.83c-3.97-3.97-10.41-3.97-14.38,0c-3.97,3.97-3.97,10.41,0,14.38l10.01,10.01
c3.97,3.97,10.41,3.97,14.38,0c3.97-3.97,3.97-10.41,0-14.38L613.51,623.83z"/>
<circle class="st1" cx="701.12" cy="632.32" r="32.77"/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="389.8995" y1="87.6274" x2="389.8995" y2="706.619">
<stop offset="1.918004e-07" style="stop-color:#F6F8FF"/>
<stop offset="1" style="stop-color:#D9E3FF"/>
</linearGradient>
<path class="st2" d="M701.78,480.36l-31.51-31.51l-19.97-21.27c-5.17-5.43-5.24-13.94-0.16-19.46l0,0
c5.55-6.03,15.04-6.14,20.72-0.23l4.04,4.41l9.32,9.32c5.41,5.41,14.29,5.82,19.8,0.51c5.61-5.41,5.68-14.35,0.19-19.84
l-18.63-18.63c-6.88-6.88-11.36-15.82-12.66-25.46c-18.22-135.39-134.2-239.79-274.58-239.79c-4.79,0-9.55,0.13-14.28,0.37
c-5.11,0.26-10.1-1.61-13.71-5.23L351.2,94.41c-8.55-8.55-22.47-9.18-31.32-0.94c-9.29,8.65-9.48,23.19-0.59,32.09l3.34,3.34
l10.41,10.94c6.54,6.92,5.57,18-2.08,23.68c-6.51,4.83-14.68,3.26-20.19-2.69l-20.69-20.46l-18.04-18.04
c-3.83-3.83-10.06-4.28-14.13-0.71c-4.46,3.92-4.62,10.71-0.49,14.84l13.14,13.14l8.56,8.24c8.7,8.52,8.89,21.8,0.24,30.36l0,0
c-8.59,8.49-22.45,8.38-30.89-0.26l-14.74-15.33l-35.31-35.31c-8.7-8.7-22.82-8.7-31.52,0h0c-8.7,8.7-8.7,22.82,0,31.52
l25.61,25.69l8.98,8.38c8.49,8.43,8.69,22.09,0.45,30.76l0,0c-8.82,9.29-23.69,9.11-32.28-0.4l-4.96-5.44l-15.36-15.44
c-12.62-12.62-33.2-13.4-46.16-1.12c-13.45,12.73-13.67,33.97-0.66,46.98l37.34,37.33l17.8,17.59c6.71,6.64,6.5,17.55-0.46,23.93
l0,0c-6.58,6.03-16.76,5.75-22.99-0.63l-5.35-5.55l-8.11-8.11c-3.97-3.97-10.41-3.97-14.38,0h0c-5.06,4.2-3.69,10.69,0.06,14.44
l17.79,17.79l36.99,36.09c6.38,6.22,6.41,16.47,0.08,22.73l0,0c-6.18,6.11-16.12,6.15-22.34,0.08l-17.67-17.24L95.3,370.73
c-5.41-5.41-14.29-5.82-19.8-0.51c-5.61,5.41-5.68,14.35-0.19,19.84l43.89,43.89c3.55,3.55,5.91,8.11,6.84,13.05
c23.49,124.93,130.8,220.27,261.3,225.36c7.2,0.28,14.06,3.16,19.28,8.14c8.46,8.08,20.27,19.34,20.27,19.34
c8.55,8.55,22.47,9.18,31.32,0.94c9.29-8.65,9.48-23.19,0.59-32.09l-2.26-2.26l-9.81-10.24c-5.59-6.06-5.03-15.55,1.23-20.92l0,0
c5.93-5.08,14.81-4.58,20.13,1.13l20.22,21.22l22.02,22.02c8.9,8.89,23.44,8.7,32.09-0.58c8.24-8.84,7.6-22.77-0.94-31.32
l-9.5-9.5c0,0-3.02-3.11-6.89-7.09c-9.03-9.28-8.71-24.16,0.7-33.05l0.48-0.45c9.02-8.52,23.15-8.43,32.07,0.2l13.82,13.38
l5.91,5.91c3.83,3.83,10.07,4.28,14.13,0.71c4.46-3.92,4.62-10.71,0.49-14.84l-5.15-5.15c-0.94,0.44-20.83-20.04-32.63-32.32
c-4.75-4.94-4.56-12.79,0.41-17.5l0,0c4.95-4.69,12.77-4.47,17.45,0.48l31.03,32.82l14.15,14.15
c12.62,12.62,33.19,13.4,46.16,1.13c13.45-12.73,13.67-33.97,0.66-46.98l-22.57-22.57l-11.4-12.43
c-5.57-6.02-5.32-15.38,0.56-21.1l0,0c6.11-5.94,15.92-5.64,21.67,0.65l2.92,3.4l14.64,14.65c8.9,8.9,23.44,8.7,32.09-0.58
C710.96,502.84,710.32,488.91,701.78,480.36z"/>
<path class="st3" d="M429.58,201.25h-14.84v-14.84c0-3.04-2.47-5.51-5.51-5.51c-3.04,0-5.51,2.47-5.51,5.51v14.84h-14.84
c-3.04,0-5.51,2.47-5.51,5.51s2.47,5.51,5.51,5.51h14.84v14.84c0,3.04,2.47,5.51,5.51,5.51c3.04,0,5.51-2.47,5.51-5.51v-14.84
h14.84c3.04,0,5.51-2.47,5.51-5.51S432.62,201.25,429.58,201.25z"/>
<path class="st3" d="M585.33,419.79c-7.38,0-13.38-6-13.38-13.38c0-7.38,6-13.38,13.38-13.38s13.38,6,13.38,13.38
C598.71,413.79,592.71,419.79,585.33,419.79z M585.33,399.45c-3.83,0-6.95,3.12-6.95,6.95c0,3.83,3.12,6.95,6.95,6.95
c3.83,0,6.95-3.12,6.95-6.95C592.29,402.57,589.17,399.45,585.33,399.45z"/>
<path class="st3" d="M222.68,521.82c-7.38,0-13.38-6-13.38-13.38c0-7.38,6-13.38,13.38-13.38s13.38,6,13.38,13.38
C236.06,515.82,230.06,521.82,222.68,521.82z M222.68,501.49c-3.83,0-6.95,3.12-6.95,6.95c0,3.83,3.12,6.95,6.95,6.95
c3.83,0,6.95-3.12,6.95-6.95C229.64,504.61,226.52,501.49,222.68,501.49z"/>
</g>
<g>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="82.0704" y1="313.8314" x2="256.6039" y2="488.3649">
<stop offset="0" style="stop-color:#D4E1FF"/>
<stop offset="1" style="stop-color:#7798FF"/>
</linearGradient>
<polygon class="st4" points="256.31,414.64 231.19,414.64 231.19,362.91 176.01,362.91 176.01,414.64 127.82,414.64
202.18,255.58 142.01,253.89 68.42,412.48 68.42,414.64 68.38,414.64 68.38,478.51 176.01,478.51 176.01,513.78 231.19,513.78
231.19,478.51 256.31,478.51 "/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="559.1218" y1="313.8314" x2="733.6553" y2="488.3649">
<stop offset="0" style="stop-color:#D4E1FF"/>
<stop offset="1" style="stop-color:#7798FF"/>
</linearGradient>
<polygon class="st6" points="733.37,414.64 708.24,414.64 708.24,362.91 653.06,362.91 653.06,414.64 604.87,414.64
679.23,255.58 619.06,253.89 545.47,412.48 545.47,414.64 545.44,414.64 545.44,478.51 653.06,478.51 653.06,513.78
708.24,513.78 708.24,478.51 733.37,478.51 "/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="290.9846" y1="274.9543" x2="512.0224" y2="495.9921">
<stop offset="0" style="stop-color:#D4E1FF"/>
<stop offset="1" style="stop-color:#7798FF"/>
</linearGradient>
<path class="st7" d="M477.93,249.1l-13.55,35.3l-48.56,29.31l-5.46,46.83l-21.84,20.4l16.66,24.13l-3.16,27.58l-15.23,21.55
l8.91-28.73l-1.15-18.1l-32.18-26.72l20.4-29.88l-20.4-53.15l15.8-21.55c0,0-10.68-24.92-17.58-41.49
c-66.48,17.98-115.4,78.71-115.4,150.88c0,86.32,69.98,156.3,156.3,156.3s156.3-69.98,156.3-156.3
C557.81,326.91,525.59,275.88,477.93,249.1z"/>
<g>
<path class="st3" d="M452.37,492.5c-8.96,0-16.25-7.29-16.25-16.25S443.4,460,452.37,460s16.25,7.29,16.25,16.25
S461.33,492.5,452.37,492.5z M452.37,466c-5.65,0-10.25,4.6-10.25,10.25s4.6,10.25,10.25,10.25s10.25-4.6,10.25-10.25
S458.02,466,452.37,466z"/>
</g>
<g>
<path class="st3" d="M309.69,426.33c-7.31,0-13.25-5.94-13.25-13.25s5.94-13.25,13.25-13.25c7.31,0,13.25,5.94,13.25,13.25
S317,426.33,309.69,426.33z M309.69,404.72c-4.61,0-8.36,3.75-8.36,8.36s3.75,8.36,8.36,8.36c4.61,0,8.36-3.75,8.36-8.36
S314.3,404.72,309.69,404.72z"/>
</g>
<path class="st3" d="M480.78,355.07l-6.44-5.18l5.18-6.44c1.06-1.32,0.85-3.25-0.47-4.31c-1.32-1.06-3.25-0.85-4.31,0.47
l-5.18,6.44l-6.44-5.18c-1.32-1.06-3.25-0.85-4.31,0.47c-1.06,1.32-0.85,3.25,0.47,4.31l6.44,5.18l-5.18,6.44
c-1.06,1.32-0.85,3.25,0.47,4.31c1.32,1.06,3.25,0.85,4.31-0.47l5.18-6.44l6.44,5.18c1.32,1.06,3.25,0.85,4.31-0.47
C482.31,358.06,482.1,356.13,480.78,355.07z"/>
</g>
</g>
</g>
<g id="图层_2">
</g>
<g id="图层_3">
</g>
<g id="图层_4">
</g>
<g id="图层_5">
</g>
<g id="图层_6">
</g>
<g id="图层_7">
</g>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="128px" height="128px" viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch -->
<title>Vue</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="69.644116%" y1="0%" x2="69.644116%" y2="100%" id="linearGradient-1">
<stop stop-color="#29CDFF" offset="0%"></stop>
<stop stop-color="#148EFF" offset="37.8600687%"></stop>
<stop stop-color="#0A60FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-19.8191553%" y1="-36.7931464%" x2="138.57919%" y2="157.637507%" id="linearGradient-2">
<stop stop-color="#29CDFF" offset="0%"></stop>
<stop stop-color="#0F78FF" offset="100%"></stop>
</linearGradient>
<linearGradient x1="68.1279872%" y1="-35.6905737%" x2="30.4400914%" y2="114.942679%" id="linearGradient-3">
<stop stop-color="#FA8E7D" offset="0%"></stop>
<stop stop-color="#F74A5C" offset="51.2635191%"></stop>
<stop stop-color="#F51D2C" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Vue" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group" transform="translate(19.000000, 9.000000)">
<path d="M89.96,90.48 C78.58,93.48 68.33,83.36 67.62,82.48 L46.6604487,62.2292258 C45.5023849,61.1103236 44.8426845,59.5728835 44.8296987,57.9626396 L44.5035564,17.5209948 C44.4948861,16.4458744 44.0537714,15.4195095 43.2796864,14.6733517 L29.6459999,1.53153737 C28.055475,-0.00160504005 25.5232423,0.0449126588 23.9900999,1.63543756 C23.2715121,2.38092066 22.87,3.37600834 22.87,4.41143746 L22.87,64.3864751 C22.87,67.0807891 23.9572233,69.6611067 25.885409,71.5429748 L63.6004615,108.352061 C65.9466323,110.641873 69.6963584,110.624605 72.0213403,108.313281" id="Path-Copy" fill="url(#linearGradient-1)" fill-rule="nonzero" transform="translate(56.415000, 54.831157) scale(-1, 1) translate(-56.415000, -54.831157) "></path>
<path d="M68,90.1163122 C56.62,93.1163122 45.46,83.36 44.75,82.48 L23.7904487,62.2292258 C22.6323849,61.1103236 21.9726845,59.5728835 21.9596987,57.9626396 L21.6335564,17.5209948 C21.6248861,16.4458744 21.1837714,15.4195095 20.4096864,14.6733517 L6.7759999,1.53153737 C5.185475,-0.00160504005 2.65324232,0.0449126588 1.12009991,1.63543756 C0.401512125,2.38092066 3.90211878e-13,3.37600834 3.90798505e-13,4.41143746 L3.94351218e-13,64.3864751 C3.94681177e-13,67.0807891 1.08722326,69.6611067 3.01540903,71.5429748 L40.7807092,108.401101 C43.1069304,110.671444 46.8180151,110.676525 49.1504445,108.412561" id="Path" fill="url(#linearGradient-2)" fill-rule="nonzero"></path>
<path d="M43.2983488,19.0991931 L27.5566079,3.88246244 C26.7624281,3.11476967 26.7409561,1.84862177 27.5086488,1.05444194 C27.8854826,0.664606611 28.4044438,0.444472651 28.9466386,0.444472651 L60.3925021,0.444472651 C61.4970716,0.444472651 62.3925021,1.33990315 62.3925021,2.44447265 C62.3925021,2.9858375 62.1730396,3.50407742 61.7842512,3.88079942 L46.0801285,19.0975301 C45.3051579,19.8484488 44.0742167,19.8491847 43.2983488,19.0991931 Z" id="Path" fill="url(#linearGradient-3)"></path>
</g>
</g>
</svg>
\ No newline at end of file
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 44"><defs><style>.cls-1{fill:#409eff;fill-rule:evenodd;}</style></defs><title>element plus-logo-small 副本</title><path id="element_plus-logo-small" data-name="element plus-logo-small" class="cls-1" d="M37.41,32.37c0,1.57-.83,1.93-.83,1.93L21.51,43A1.69,1.69,0,0,1,20,43S5.2,34.4,4.66,34a1.29,1.29,0,0,1-.55-1V15.24c0-.78,1-1.33,1-1.33L19.86,5.36a2,2,0,0,1,1.79,0l14.46,8.41a2.06,2.06,0,0,1,1.25,2.06V32.37Zm-5.9-17L21.35,9.5a1.59,1.59,0,0,0-1.41,0L8.33,16.15s-.77.46-.76,1.08,0,13.92,0,13.92A1,1,0,0,0,8,31.9c.43.3,12,7,12,7a1.31,1.31,0,0,0,1.19,0C21.91,38.5,33,32.11,33,32.11s.65-.28.65-1.51V27.13l-13,7.9V32a3.05,3.05,0,0,1,1-2.07L33.2,23a2.44,2.44,0,0,0,.55-1.46V18.43L20.64,26.35v-3.2a2.22,2.22,0,0,1,.83-1.79ZM41.07,4.22a.39.39,0,0,0-.37-.42H38V1.06c0-.16-.26-.22-.53-.22L36,1.08c-.18,0-.31.12-.31.23V3.8H33a.4.4,0,0,0-.36.37v2h3V9c0,.16.26.27.54.23l1.51-.25c.18,0,.29-.13.29-.23V6.14h3Z"/></svg>
\ No newline at end of file
<svg width="450" height="80" viewBox="0 0 450 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M131.49 8.57001L120.84 13.54L120.79 70.65H131.49V8.57001Z" fill="#292C35"/>
<path d="M152.07 58.1C150.849 56.9794 149.881 55.6113 149.231 54.0869C148.581 52.5624 148.263 50.9169 148.3 49.26C148.251 47.6269 148.565 46.0034 149.219 44.506C149.872 43.0087 150.849 41.6747 152.08 40.6C154.509 38.4221 157.668 37.237 160.93 37.28C162.661 37.2019 164.386 37.5313 165.967 38.2417C167.547 38.9521 168.939 40.0236 170.03 41.37L178.29 35.15C176.315 32.7051 173.803 30.7479 170.95 29.43C166.658 27.4855 161.895 26.8284 157.237 27.5384C152.579 28.2485 148.227 30.2951 144.71 33.43C142.509 35.4299 140.768 37.8837 139.608 40.6219C138.448 43.3602 137.896 46.3176 137.99 49.29C137.905 52.2818 138.461 55.2571 139.62 58.0166C140.779 60.7761 142.514 63.256 144.71 65.29C149.088 69.4239 154.909 71.6814 160.93 71.58C164.407 71.5978 167.845 70.8501 171 69.39C173.886 68.0662 176.418 66.0789 178.39 63.59L170.04 57.45C168.962 58.8204 167.574 59.9149 165.99 60.6434C164.406 61.3719 162.672 61.7135 160.93 61.64C157.627 61.667 154.445 60.3958 152.07 58.1V58.1Z" fill="#292C35"/>
<path d="M202.26 27.25C196.42 27.1188 190.767 29.3089 186.54 33.34C182.29 37.34 180.14 42.74 180.14 49.26C180.14 55.78 182.3 61.18 186.55 65.26C190.81 69.2389 196.421 71.452 202.25 71.452C208.079 71.452 213.69 69.2389 217.95 65.26C222.2 61.16 224.35 55.77 224.35 49.26C224.35 42.75 222.2 37.38 217.95 33.34C213.73 29.3163 208.089 27.1268 202.26 27.25V27.25ZM210.66 58C208.387 60.1441 205.38 61.3383 202.255 61.3383C199.13 61.3383 196.123 60.1441 193.85 58V58C192.717 56.8528 191.831 55.4851 191.248 53.9817C190.665 52.4784 190.397 50.8713 190.46 49.26C190.405 47.6856 190.678 46.1169 191.261 44.6536C191.845 43.1902 192.726 41.8643 193.85 40.76C196.138 38.6446 199.139 37.4696 202.255 37.4696C205.371 37.4696 208.372 38.6446 210.66 40.76C211.784 41.8643 212.665 43.1902 213.249 44.6536C213.832 46.1169 214.105 47.6856 214.05 49.26C214.119 50.8779 213.853 52.4926 213.27 54.0033C212.687 55.5139 211.798 56.8881 210.66 58.04V58Z" fill="#292C35"/>
<path d="M255.31 27.25C249.25 26.95 243.85 29.62 241.66 32.42L240.83 33.48V28.33L230.42 33.17V70.63H240.83V47.77C240.83 41.06 244.36 37.37 250.77 37.37C258.87 37.37 259.5 45.12 259.5 47.5V70.63H269.9V42.52C269.9 35.49 266.07 27.78 255.31 27.25Z" fill="#292C35"/>
<path d="M316.85 14.41C313.57 10.54 308.66 8.58002 302.25 8.58002H277.12V70.65H287.9V48.25H302.25C308.66 48.25 313.58 46.32 316.85 42.52C320.18 38.6262 321.96 33.6425 321.85 28.52C321.955 23.3652 320.177 18.3489 316.85 14.41V14.41ZM308.6 35.6C307.782 36.635 306.73 37.4604 305.53 38.0082C304.331 38.5559 303.018 38.8102 301.7 38.75H287.9V18.09H302.07C303.366 18.0318 304.656 18.2986 305.823 18.8662C306.99 19.4339 307.996 20.2843 308.75 21.34C310.251 23.4276 311.05 25.9387 311.03 28.51C311.058 31.0823 310.2 33.5859 308.6 35.6Z" fill="#292C35"/>
<path d="M350.96 28.75C348.644 27.7901 346.167 27.281 343.66 27.25C340.84 27.2168 338.042 27.7422 335.426 28.796C332.81 29.8499 330.429 31.4112 328.42 33.39C324.17 37.46 322.02 42.83 322.02 49.39C322.02 55.95 324.19 61.28 328.47 65.39C330.474 67.3615 332.847 68.9185 335.454 69.972C338.06 71.0256 340.849 71.555 343.66 71.53C346.207 71.485 348.72 70.9308 351.05 69.9C353.368 68.9973 355.354 67.4058 356.74 65.34L357.59 64V70.7H368V28.31L357.59 33.13V34.65L356.74 33.36C355.343 31.2566 353.321 29.6442 350.96 28.75V28.75ZM353.91 58.2C351.521 60.3549 348.413 61.5393 345.196 61.5208C341.978 61.5023 338.884 60.2822 336.52 58.1C335.341 56.9689 334.403 55.6111 333.763 54.1083C333.122 52.6055 332.792 50.9887 332.792 49.355C332.792 47.7213 333.122 46.1045 333.763 44.6017C334.403 43.0989 335.341 41.7411 336.52 40.61C338.873 38.382 342 37.1557 345.24 37.19C348.422 37.1598 351.499 38.327 353.86 40.46C355.037 41.6165 355.973 42.9951 356.613 44.5159C357.253 46.0367 357.585 47.6695 357.59 49.3196C357.595 50.9697 357.272 52.6043 356.64 54.1286C356.008 55.653 355.08 57.0369 353.91 58.2V58.2Z" fill="#292C35"/>
<path d="M385.27 32.04L384.48 32.85V28.24L374.08 33.09V70.63H384.48V51.21C384.48 42.67 388.38 37.72 395.76 36.91C398.433 36.7251 401.117 37.0648 403.66 37.91L403.88 28.65C401.764 27.7373 399.484 27.2644 397.18 27.26C394.975 27.2111 392.783 27.6095 390.736 28.431C388.689 29.2525 386.829 30.4802 385.27 32.04V32.04Z" fill="#292C35"/>
<path d="M428.5 47.15L445.97 28.88H432.8L420.35 42.38V8.57001L410.21 13.3V70.65H420.35V51.62L437.1 70.65H449.85L428.5 47.15Z" fill="#292C35"/>
<path d="M0 79.26L25.45 67.39V0L0 11.86V79.26Z" fill="#067C5A"/>
<path d="M33.57 79.26L59.02 67.39V0L33.57 11.86V79.26Z" fill="#08DFAD"/>
<path d="M67.14 49.56L92.59 37.69V0L67.14 11.86V49.56Z" fill="#08DFAD"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 207.74 252.58"><defs><style>.cls-1{fill:#93ceaa;}.cls-2{fill:#4c9717;}.cls-3{fill:#5fbc21;}.cls-4{fill:#e8ceaa;opacity:0.6;}</style></defs><title>Naive UI - LOGO</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M60.37,205.2c0,2.5,0,5,.05,7.5,0,.54,0,1.07-.05,1.58Z"/><path class="cls-1" d="M147.29,37.68v85.2c-.06-27.24-.19-54.49-.22-81.73A29,29,0,0,1,147.29,37.68Z"/><path class="cls-2" d="M147.06,125.43a16.9,16.9,0,0,0,.23-2.55v79.66l-41.74-38.17-21-19.15L71.91,133.6l-5.62-5.13-2.07-1.89-.15-.13c-.51-.41-1-.84-1.47-1.23s-.88-.73-1.44-.56a1.25,1.25,0,0,0-.79.9v-75l.43.39,8.57,7.75c4.51,4,9.06,8,13.56,12Q92.53,79.3,102.07,88q7.55,6.86,15.08,13.74c6,5.47,11.92,11,17.92,16.43,2.7,2.44,5.42,4.83,8.15,7.23.58.51,1.15,1,1.74,1.52a1.47,1.47,0,0,0,1.4.18,1.16,1.16,0,0,0,.56-.72C147,126,147,125.74,147.06,125.43Z"/><path class="cls-3" d="M60.28,126a15.67,15.67,0,0,0-.1,2.74c0,25.5,0,51,.19,76.49v9.08a14.85,14.85,0,0,1-5.87,11.09c-6.81,5.69-13.16,11.91-19.73,17.89-2.75,2.5-5.48,5-8.31,7.44s-10.11,2.74-14.14-.63a78.48,78.48,0,0,1-8.54-8.66,12.76,12.76,0,0,1-3.73-8.69c0-.72-.06-1.51,0-2.38,0-1.5,0-3,.05-4.51.05-4.3,0-8.61,0-12.91V72.71a12.64,12.64,0,0,1,.38-3.18,18.42,18.42,0,0,1,1-2.64A22.16,22.16,0,0,1,3.5,63.31l16-16L29.69,37.13a7.84,7.84,0,0,1,1.84-1.38,8.05,8.05,0,0,1,3.21-1h0a13.85,13.85,0,0,1,9.71,1.79h0a13.59,13.59,0,0,1,2.09,1.57L60.37,50.53v75A3.38,3.38,0,0,0,60.28,126Z"/><path class="cls-4" d="M60.28,126a3.38,3.38,0,0,1,.09-.41V205.2c-.15-25.5-.14-51-.19-76.49A15.67,15.67,0,0,1,60.28,126Z"/><path class="cls-3" d="M205.59,187.21l-29.39,27a3.75,3.75,0,0,1-.36.24l-.11.08-.2.12A14,14,0,0,1,158.77,213L157,211.45l-9.75-8.91V37.68c.16-1.39.4-2.78.67-4.14.7-3.55,3.82-5.31,6.2-7.53,5.17-4.8,10.5-9.42,15.77-14.12,3.63-3.24,7.25-6.49,10.91-9.71,3.19-2.81,9.37-2.67,12.37-.93a10.11,10.11,0,0,1,2.08,1.61l3.38,3.26,2.73,2.61,2.49,2.41a11.45,11.45,0,0,1,1.78,2.09,11.73,11.73,0,0,1,1.7,6.46c-.05,14.66.06,29.32.09,44q.09,37.35.16,74.7,0,19.14.07,38.27c0,.59,0,1.17,0,1.75C207.8,182.21,207.72,184.72,205.59,187.21Z"/></g></g></svg>
\ No newline at end of file
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><defs><style>.a{fill:#35495e;}.b{fill:#41b883;}</style></defs><path class="a" d="M735.07,67.05V531.58c0,129.83-105.24,235.07-235.07,235.07S264.93,661.41,264.93,531.58V67.05h166.3V531.58a68.77,68.77,0,1,0,137.54,0V67.05Z"/><path class="b" d="M901.36,67.05V531.58C901.36,753.25,721.67,933,500,933S98.64,753.25,98.64,531.58V67.05H264.93V531.58c0,129.83,105.25,235.07,235.07,235.07S735.07,661.41,735.07,531.58V67.05Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="6395" height="1080"><defs><linearGradient id="b" x1=".631" y1=".5" x2=".958" y2=".488" gradientUnits="objectBoundingBox"><stop offset="0" stop-color="#2e364a"/><stop offset="1" stop-color="#2c344a"/></linearGradient><clipPath id="a"><path data-name="Rectangle 73" transform="translate(-5391)" fill="#fff" d="M0 0h6395v1079H0z"/></clipPath></defs><g data-name="Web 1920 – 1" clip-path="url(#clip-Web_1920_1)"><g data-name="Mask Group 1" transform="translate(5391)" clip-path="url(#a)"><g data-name="Group 118"><path data-name="Path 142" d="M976.018-7.302S753.54 180.507 920.669 575.952c44.957 106.375 81.514 205.964 84.521 277 8.164 192.764-156.046 268.564-156.046 268.564l-653.53-26.8L179.349-22.751z" fill="#2d3750"/><path data-name="Union 6" d="M-2631.1 1081.8v-1.6h-5599.8V.022h5599.8V0h759.7s-187.845 197.448-91.626 488.844c49.167 148.9 96.309 256.289 104.683 362.118 7.979 100.852-57.98 201.711-168.644 254.286-65.858 31.29-144.552 42.382-223.028 42.383-191.185.001-381.085-65.831-381.085-65.831z" transform="translate(2840.191 -.323)" fill="url(#b)"/></g></g></g></svg>
\ No newline at end of file
<template>
<div class="cell-container">
<div class="cell-item" v-for="(item, index) in list" :key="index">
<div class="cell-item-meta-avatar">
<div class="icon-box" v-if="item.icon">
<component class="icon" theme="outline" size="16" :strokeWidth="3" :is="item.icon" />
</div>
</div>
<div class="cell-item-meta-content">
<div class="cell-item-meta-title">{{ item[name] }}</div>
<div class="cell-item-meta-desc">
<div class="time">{{ item.time }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
defineProps({
// 数据
list: {
type: Array,
default: () => {
return [];
},
},
// key
name: {
type: String,
default: 'title',
},
});
</script>
<style lang="scss" scoped>
.cell-container {
position: relative;
box-sizing: border-box;
transition: all $base-transition-time;
.cell-item {
display: flex;
justify-content: space-between;
padding: $base-cell-item-padding;
cursor: pointer;
border-bottom: $base-border-width-mini solid $base-border-color;
&:last-child {
border-bottom: $base-border-none;
}
.icon-box {
display: flex;
align-items: center;
justify-content: center;
width: $base-icon-width-super-max;
height: $base-icon-width-super-max;
line-height: $base-icon-width-super-max;
color: $base-color-white;
text-align: center;
background-color: $base-color-primary;
border-radius: $base-border-radius-circle;
.icon {
display: inline-flex;
}
}
&-meta {
display: flex;
flex: 1;
align-items: flex-start;
max-width: 100%;
&-avatar {
padding-right: 10px;
}
&-content {
flex: 1 0;
width: 0;
}
&-title {
margin-bottom: 4px;
overflow: hidden;
font-size: 14px;
line-height: 1.5715;
color: $base-color-black;
text-overflow: ellipsis;
white-space: nowrap;
}
&-desc {
font-size: 14px;
line-height: 1.5715;
.time {
font-size: 13px;
color: $base-font-color;
}
}
}
}
}
</style>
<!-- author:hu-snail 1217437592@qq.com 标题/描述组件 -->
<template>
<div class="desc-wrapper">
<h2 class="title">{{ title }}</h2>
<div class="desc" v-if="showDesc">
<slot name="descrition"></slot>
</div>
</div>
</template>
<script setup>
defineProps({
title: {
type: String,
default: '标题',
},
descrition: {
type: String,
default: '描述',
},
showDesc: {
type: Boolean,
default: true,
},
});
</script>
<style lang="scss" scoped>
.desc-wrapper {
.desc {
padding: $base-content-padding;
color: $base-color-primary;
background-color: $base-color-primary-light9;
border-left: $base-border-width-default solid $base-color-primary;
}
}
</style>
<template>
<el-card class="echart-card" shadow="hover">
<template #header>
<div class="card-header">
<div class="card-header-title">
<component
class="icon"
:is="headerIcon"
theme="filled"
size="16"
:strokeWidth="3"
fill="#333"
/>
<span class="title">{{ title }}</span>
</div>
<div class="card-header-right">
<slot name="header-right"></slot>
</div>
</div>
</template>
<div class="echarts" :id="`echarts${index}`" :style="style"> </div>
</el-card>
</template>
<script setup>
import debounce from 'lodash/debounce';
import { onMounted, ref, reactive, computed, watch, onBeforeUnmount } from 'vue';
import { useStore } from 'vuex';
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from 'echarts/core';
// 引入柱状图图表,图表后缀都为 Chart
import theme from './theme.json';
import {
BarChart,
CandlestickChart,
FunnelChart,
GaugeChart,
LineChart,
PieChart,
RadarChart,
ScatterChart,
} from 'echarts/charts';
// 引入提示框,标题,直角坐标系组件,组件后缀都为 Component
import {
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
ToolboxComponent,
MarkPointComponent,
MarkLineComponent,
} from 'echarts/components';
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from 'echarts/renderers';
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
BarChart,
LineChart,
PieChart,
RadarChart,
GaugeChart,
CandlestickChart,
ScatterChart,
CanvasRenderer,
LegendComponent,
ToolboxComponent,
MarkPointComponent,
MarkLineComponent,
FunnelChart,
]);
const props = defineProps({
// 图表下标 同个页面有多个图表时,必填
index: {
type: Number,
default: 0,
},
title: {
type: String,
default: '标题',
},
headerIcon: {
type: String,
default: 'icon-full-screen',
},
style: {
type: Object,
default: () => {
return {
width: '100%',
height: '380px',
};
},
},
options: {
type: Object,
default: () => {
return {};
},
},
grid: {
type: Object,
default: () => {
return {
top: '10px',
left: 0,
right: '1px',
bottom: 0,
containLabel: true,
};
},
},
});
const store = useStore();
const isCollapse = computed(() => {
return store.getters.collapse;
});
let chart = reactive({});
let timer = ref(null);
watch(
() => isCollapse,
() => {
timer = setTimeout(() => {
chart.resize(); //页面大小变化后Echarts也更改大小
clearTimeout(timer);
timer = null;
}, 300);
},
{
deep: true,
}
);
onMounted(() => {
initChart();
window.addEventListener(
'resize',
debounce(() => {
chart.resize(); //页面大小变化后Echarts也更改大小
}, 200)
);
});
onBeforeUnmount(() => {
clearTimeout(timer);
timer = null;
});
const initChart = () => {
echarts.registerTheme('wonderland', theme);
chart = echarts.init(document.getElementById('echarts' + props.index), theme);
// 绘制图表
chart.setOption({
grid: props.grid,
...props.options,
});
};
</script>
<style lang="scss" scoped>
.echart-card {
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
&-title {
display: flex;
align-content: center;
align-items: center;
color: $base-font-color;
.icon {
display: flex;
padding-right: 5px;
}
}
}
.echarts {
margin: 0 auto;
background-color: $base-color-white;
}
}
</style>
{
"color": [
"#84a2fe",
"#22c3aa",
"#7bd9a5",
"#d0648a",
"#f58db2",
"#f2b3c9"
],
"backgroundColor": "rgba(255,255,255,0)",
"textStyle": {},
"title": {
"textStyle": {
"color": "#666666"
},
"subtextStyle": {
"color": "#999999"
}
},
"line": {
"itemStyle": {
"borderWidth": "2"
},
"lineStyle": {
"width": "3"
},
"symbolSize": "8",
"symbol": "emptyCircle",
"smooth": false
},
"radar": {
"itemStyle": {
"borderWidth": "2"
},
"lineStyle": {
"width": "3"
},
"symbolSize": "8",
"symbol": "emptyCircle",
"smooth": false
},
"bar": {
"itemStyle": {
"barBorderWidth": 0,
"barBorderColor": "#cccccc"
}
},
"pie": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#cccccc"
}
},
"scatter": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#cccccc"
}
},
"boxplot": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#cccccc"
}
},
"parallel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#cccccc"
}
},
"sankey": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#cccccc"
}
},
"funnel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#cccccc"
}
},
"gauge": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#cccccc"
}
},
"candlestick": {
"itemStyle": {
"color": "#84a2fe",
"color0": "transparent",
"borderColor": "#84a2fe",
"borderColor0": "#22c3aa",
"borderWidth": "1"
}
},
"graph": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#cccccc"
},
"lineStyle": {
"width": "1",
"color": "#cccccc"
},
"symbolSize": "8",
"symbol": "emptyCircle",
"smooth": false,
"color": [
"#84a2fe",
"#22c3aa",
"#7bd9a5",
"#d0648a",
"#08a17e",
"#f2b3c9"
],
"label": {
"color": "#ffffff"
}
},
"map": {
"itemStyle": {
"areaColor": "#eeeeee",
"borderColor": "#999999",
"borderWidth": 0.5
},
"label": {
"color": "#28544e"
},
"emphasis": {
"itemStyle": {
"areaColor": "rgba(34,195,170,0.25)",
"borderColor": "#22c3aa",
"borderWidth": 1
},
"label": {
"color": "#349e8e"
}
}
},
"geo": {
"itemStyle": {
"areaColor": "#eeeeee",
"borderColor": "#999999",
"borderWidth": 0.5
},
"label": {
"color": "#28544e"
},
"emphasis": {
"itemStyle": {
"areaColor": "rgba(34,195,170,0.25)",
"borderColor": "#22c3aa",
"borderWidth": 1
},
"label": {
"color": "#349e8e"
}
}
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#999999"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#999999"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"logAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#999999"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"timeAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#999999"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"toolbox": {
"iconStyle": {
"borderColor": "#999999"
},
"emphasis": {
"iconStyle": {
"borderColor": "#666666"
}
}
},
"legend": {
"textStyle": {
"color": "#999999"
}
},
"tooltip": {
"axisPointer": {
"lineStyle": {
"color": "#cccccc",
"width": 1
},
"crossStyle": {
"color": "#cccccc",
"width": 1
}
}
},
"timeline": {
"lineStyle": {
"color": "#84a2fe",
"width": 1
},
"itemStyle": {
"color": "#84a2fe",
"borderWidth": 1
},
"controlStyle": {
"color": "#84a2fe",
"borderColor": "#84a2fe",
"borderWidth": 0.5
},
"checkpointStyle": {
"color": "#84a2fe",
"borderColor": "#3cebd2"
},
"label": {
"color": "#84a2fe"
},
"emphasis": {
"itemStyle": {
"color": "#84a2fe"
},
"controlStyle": {
"color": "#84a2fe",
"borderColor": "#84a2fe",
"borderWidth": 0.5
},
"label": {
"color": "#84a2fe"
}
}
},
"visualMap": {
"color": [
"#d0648a",
"#22c3aa",
"#adfff1"
]
},
"dataZoom": {
"backgroundColor": "rgba(255,255,255,0)",
"dataBackgroundColor": "rgba(222,222,222,1)",
"fillerColor": "rgba(114,230,212,0.25)",
"handleColor": "#cccccc",
"handleSize": "100%",
"textStyle": {
"color": "#999999"
}
},
"markPoint": {
"label": {
"color": "#ffffff"
},
"emphasis": {
"label": {
"color": "#ffffff"
}
}
}
}
<template>
<div class="errorPage-container">
<el-row :gutter="10" class="error-row">
<el-col class="error-col" :xs="24" :sm="24" :md="10" :lg="10" :xl="10">
<div class="error-img">
<img v-if="type === '401'" src="@/assets/error/401.svg" class="img" :alt="type" />
<img v-if="type === '404'" src="@/assets/error/404.svg" class="img" :alt="type" />
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="10" :lg="10" :xl="10">
<div class="error-content">
<h2 class="error-title">{{ t('errorPages.title') }}</h2>
<h3>{{ title }}</h3>
<p class="desc"> {{ msg }} </p>
<div class="btn">
<el-button type="primary" @click="handleBack">{{ t('errorPages.btn') }}</el-button>
</div>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
export default {
props: {
src: {
type: String,
default: '',
},
type: {
type: String,
default: '',
},
title: {
type: String,
default: '',
},
msg: {
type: String,
default: '',
},
},
setup() {
const { t } = useI18n();
const router = useRouter();
const handleBack = () => {
router.replace('/');
};
return {
t,
handleBack,
};
},
};
</script>
<style lang="scss" scoped>
.errorPage-container {
display: flex;
align-items: center;
padding: $base-main-padding;
background-color: $base-color-white;
.error-row {
flex: 1;
justify-content: center;
.error-col {
text-align: center;
}
.error-img {
width: 100%;
.img {
width: 80%;
}
}
.error-content {
padding: 40px 20px;
.error-title {
font-size: 36px;
color: $base-color-primary;
}
.desc {
width: 290px;
line-height: 1.5;
}
.btn {
margin: 20px 0;
}
}
}
}
</style>
<template>
<span
class="icon-hover full-screen-wrapper"
:title="isFullScreen ? t('navbar.noFull') : t('navbar.full')"
>
<component
theme="filled"
size="16"
:fill="color"
:strokeWidth="4"
:is="(isFullScreen ? 'icon-off' : 'icon-full') + '-screen'"
@click="handleClick"
/>
</span>
</template>
<script setup>
import { computed } from 'vue';
import screenfull from 'screenfull';
import { useStore } from 'vuex';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
const store = useStore();
defineProps({
color: {
type: String,
default: '#666',
},
});
const { t } = useI18n();
const isFullScreen = computed(() => {
return store.getters['setting/isFullScreen'];
});
const emit = defineEmits(['refresh']);
const handleClick = () => {
if (!screenfull.isEnabled) {
ElMessage.warning('进入全屏失败');
return false;
}
store.dispatch('setting/changeFullScreen', !isFullScreen.value);
screenfull.toggle();
emit('refresh', screenfull.isFullscreen);
};
</script>
<style lang="scss" scoped>
.full-screen-wrapper {
padding: 20px 10px;
}
</style>
<template>
<span class="icon-comp">
<i
v-if="name.indexOf('el-icon') !== -1"
:class="name"
:style="{ color, 'font-size': size + 'px' }"
></i>
<component
v-if="type === 'icon-park'"
:theme="theme"
:size="size"
:strokeWidth="strokeWidth"
:is="name"
:fill="color"
class="icon"
/>
<el-icon v-if="type === 'el-icon'" :style="{ color, 'font-size': size + 'px' }">
<component :is="name" />
</el-icon>
</span>
</template>
<script setup>
defineProps({
type: {
type: String,
default: 'icon-park',
},
size: {
type() {
return Number | String;
},
default: 14,
},
color: {
type: String,
default: '#333',
},
theme: {
type: String,
default: 'outline',
},
strokeWidth: {
type: Number,
default: 3,
},
name: {
type: String,
default: '',
},
className: {
type: String,
default: 'icon',
},
});
</script>
<style lang="scss" scoped></style>
<template>
<div class="icon-hover icon-lang">
<el-dropdown>
<icon-translate theme="filled" size="16" :fill="color" :strokeWidth="4" />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="item in languages"
:key="item.value"
:disabled="language == item.value"
>
<span @click="handleSetLanguage(item.value)">{{ item.name }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<script setup>
import { reactive, computed } from 'vue';
import { useStore } from 'vuex';
defineProps({
color: {
type: String,
default: '#666',
},
});
const languages = reactive([
{
name: '简体中文',
value: 'zh-cn',
},
{
name: 'English',
value: 'en',
},
]);
const language = computed(() => {
return store.getters['setting/lang'];
});
const store = useStore();
const handleSetLanguage = (lang) => {
store.dispatch('setting/changeLanguage', lang);
location.reload();
};
</script>
<style lang="scss" scoped>
.icon-lang {
padding: 20px 10px;
}
</style>
<template>
<svg :class="svgClass" v-bind="$attrs" :style="{ 'font-size': size, color: color }">
<use :xlink:href="iconName" />
</svg>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
name: {
type: String,
required: true,
},
color: {
type: String,
default: '',
},
size: {
type: String,
default: '14px',
},
});
const iconName = computed(() => `#icon-${props.name}`);
const svgClass = computed(() => {
if (props.name) {
return `svg-icon icon-${props.name}`;
}
return 'svg-icon';
});
</script>
<style lang="scss">
.svg-icon {
width: 1em;
height: 1em;
vertical-align: middle;
fill: currentColor;
}
</style>
/**
* @description 配置axios请求基础信息
* @author hu-snail 1217437592@qq.com
*/
export const netConfig = {
// axios 基础url地址
baseURL: process.env.NODE_ENV === 'development' ? 'http://localhost:8089/api' : '/api',
// 为开发服务器配置 CORS。默认启用并允许任何源,传递一个 选项对象 来调整行为或设为 false 表示禁用
cors: true,
// 根据后端定义配置
contentType: 'application/json;charset=UTF-8',
//消息框消失时间
messageDuration: 3000,
//最长请求时间
requestTimeout: 10000,
//操作正常code,支持String、Array、int多种类型
successCode: [200, 0],
//登录失效code
invalidCode: -1,
//无权限code
noPermissionCode: -1,
};
/**
* @author hujiangjun 1217437592@qq.com
* @description 路由控制
*/
import router from '@/router';
import store from '@/store';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import { getPageTitle } from '@/utils/index';
import { setting } from '@/config/setting';
const { authentication, loginInterception, progressBar, routesWhiteList, recordRoute } = setting;
NProgress.configure({
easing: 'ease',
speed: 500,
trickleSpeed: 200,
showSpinner: false,
});
router.beforeEach(async (to, from, next) => {
if (progressBar) NProgress.start();
let hasToken = store.getters['user/accessToken'];
if (!loginInterception) hasToken = true;
if (hasToken) {
if (to.path === '/login') {
next({ path: '/' });
if (progressBar) NProgress.done();
} else {
const hasPermissions =
store.getters['user/permissions'] && store.getters['user/permissions'].length > 0;
if (hasPermissions) {
next();
} else {
try {
let permissions;
if (!loginInterception) {
//settings.js loginInterception为false时,创建虚拟权限
await store.dispatch('user/setPermissions', ['admin']);
permissions = ['admin'];
} else {
permissions = await store.dispatch('user/getUserInfo');
}
let accessRoutes = [];
if (authentication === 'intelligence') {
accessRoutes = await store.dispatch('routes/setRoutes', permissions);
} else if (authentication === 'all') {
accessRoutes = await store.dispatch('routes/setAllRoutes');
}
accessRoutes.forEach((item) => {
router.addRoute(item);
});
next({ ...to, replace: true });
} catch {
await store.dispatch('user/resetAccessToken');
if (progressBar) NProgress.done();
}
}
}
} else {
// 免登录路由
if (routesWhiteList.indexOf(to.path) !== -1) {
next();
} else {
if (recordRoute) {
next(`/login?redirect=${to.path}`);
} else {
next('/login');
}
if (progressBar) NProgress.done();
}
}
document.title = getPageTitle(to.meta.title);
});
router.afterEach(() => {
if (progressBar) NProgress.done();
});
/**
* @description 公共配置文件
* @author hu-snail 1217437592@qq.com
* vite相关的配置文件参考 https://cn.vitejs.dev/config/#define
*/
export const setting = {
//项目部署的基础路径
base: './',
// 静态资源服务的文件夹 类型 string | false
publicDir: 'public',
// 存储缓存文件的目录
cacheDir: 'node_modules/.vite',
// 输出路径
outDir: 'dist',
// 生成静态资源的存放路径
assetsDir: 'static/',
// 构建后是否生成 source map 文件
sourcemap: false,
// chunk 大小警告的限制
chunkSizeWarningLimit: 2000,
// 启用/禁用 CSS 代码拆分
// 压缩大型输出文件可能会很慢,因此禁用该功能可能会提高大型项目的构建性能。
cssCodeSplit: true,
// 启用/禁用 brotli 压缩大小报告
brotliSize: false,
// 指定服务器应该监听哪个 IP 地址
host: '0.0.0.0',
// 指定开发服务器端口
port: '8089',
// 设为 true 时若端口已被占用则会直接退出,而不是尝试下一个可用端口
strictPort: false,
// 服务器启动时自动在浏览器中打开应用程序 此值为字符串时,会被用作 URL 的路径名
open: true,
//是否显示顶部进度条
progressBar: true,
// 菜单栏默认打开路由
defaultOpeneds: ['/comp', '/errorPage', '/chart'],
// vertical布局时是否只保持一个子菜单的展开
uniqueOpened: false,
//token名称
tokenName: 'accessToken',
//是否开启登录拦截
loginInterception: true,
//token在localStorage、sessionStorage存储的key的名称
tokenTableName: 'vue3-admin-template',
// lang storage
langKey: 'i18nLang',
// theme storage
themeKey: 'theme',
// default language
lang: 'zh-cn',
//token存储位置localStorage sessionStorage
storage: 'localStorage',
// 标题
title: 'vue3-admin-template',
// 版权信息
copyright: '© hu-snail-2021 vue3-admin-element-template',
// 是否显示页面底部自定义版权信息
footerCopyright: true,
// 缓存路由的最大数量
keepAliveMaxNum: 99,
// intelligence 前端控制路由 all 后端控制
authentication: 'intelligence',
//token失效回退到登录页时是否记录本次的路由
recordRoute: true,
// 路由白名单不经过token校验的路由
routesWhiteList: ['/login', '/register', '/404', '/401'],
// 需要加loading层的请求,防止重复提交
debounce: [],
// 导入时想要省略的扩展名列表
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
// 调整控制台输出的级别 'info' | 'warn' | 'error' | 'silent'
logLevel: 'info',
// 设为 false 可以避免 Vite 清屏而错过在终端中打印某些关键信息
clearScreen: false,
// 是否删除生产环境console
drop_console: true,
// 是否删除生产环境debugger
drop_debugger: true,
};
/**
* @description 主题配置
* @author hu-snail
*/
/**
* @description 主题配置 menuBgColor: 菜单背景色 primary: 主题色
*/
const themeOptions = {
theme1: { menuBgColor: '#ffffff', primary: '#7e9cff' },
theme2: { menuBgColor: '#293246', primary: '#7e9cff' },
theme3: { menuBgColor: '#ffffff', primary: '#08a17e' },
theme4: { menuBgColor: '#293246', primary: '#08a17e' },
theme5: { menuBgColor: '#ffffff', primary: '#f45555' },
theme6: { menuBgColor: '#293246', primary: '#f45555' },
};
export const themeConfig = {
// 模式 horizontal / vertical
mode: 'vertical',
// 主题 默认配置theme1
// 注意⚠️ :修改默认主题时,记得同步修改 element-variables.scss
// 文件中的 $base-color-primary 默认值,否则不生效!!!
theme: 'theme2',
// 主题配置
themeOptions,
// 是否固定头部
fixedHead: true,
// 是否显示全屏
fullScreen: true,
// 是否显示刷新
refresh: true,
// 是否显示通知
notice: true,
// 是否显示面包导航
isBreadcrumb: true,
// 是否显示logo
isLogo: true,
// 是否显示标签
tag: true,
// 是否展开菜单
collapse: false,
};
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629102216892" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2056" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M615.6 123.6h165.5L512 589.7 242.9 123.6H63.5L512 900.4l448.5-776.9z" fill="#41B883" p-id="2057" data-spm-anchor-id="a313x.7781069.0.i0" class="selected"></path><path d="M781.1 123.6H615.6L512 303 408.4 123.6H242.9L512 589.7z" fill="#34495E" p-id="2058"></path></svg>
\ No newline at end of file
<template>
<router-view />
</template>
<template>
<div v-if="store.getters['setting/routerView']" class="app-main-container">
<router-view class="app-main-height" />
<!-- <footer class="footer-copyright">{{ copyrightStr }} </footer> -->
</div>
</template>
<script>
export default {
name: 'AppMain',
};
</script>
<script setup>
import { ref } from 'vue';
import { useStore } from 'vuex';
import { setting } from '@/config/setting';
const { copyright } = setting;
const copyrightStr = ref(copyright);
const store = useStore();
</script>
<style lang="scss" scoped>
.app-main-container {
position: relative;
box-sizing: border-box;
width: $base-width;
overflow: hidden;
text-align: left;
.app-main-height {
min-height: $app-main-min-height;
}
.footer-copyright {
min-height: $footer-copyright-height;
line-height: $footer-copyright-height;
color: $base-color-3;
text-align: center;
border-top: 1px dashed $base-border-color;
}
}
</style>
<template>
<el-dropdown @command="handleCommand">
<span class="avatar-dropdown" :style="{ color }">
<div class="user-name">
{{ userName }}
<i class="el-icon-arrow-down el-icon--right"></i>
</div>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="logout" divided>{{ t('navbar.logOut') }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script>
export default {
name: 'Avatar',
};
</script>
<script setup>
import { ref } from 'vue';
import { useStore } from 'vuex';
import { ElMessageBox } from 'element-plus';
import { setting } from '@/config/setting';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
const { title, recordRoute } = setting;
const { t } = useI18n();
const userName = ref('管理员');
const store = useStore();
const router = useRouter();
defineProps({
color: {
type: String,
default: '#666',
},
});
const handleCommand = (command) => {
switch (command) {
case 'logout':
handleLogout();
break;
default:
break;
}
};
const handleLogout = () => {
ElMessageBox.confirm(`${t('confirm.msg')}${title}?`, t('confirm.title'), {
confirmButtonText: t('btn.confirm'),
cancelButtonText: t('btn.cancel'),
dangerouslyUseHTMLString: true,
type: 'warning',
})
.then(async () => {
await store.dispatch('user/logout');
if (recordRoute) {
const { fullPath } = router.currentRoute._value;
location.replace('/login');
} else {
router.push('/login');
}
})
.catch(() => {});
};
</script>
<style lang="scss" scoped>
.avatar-dropdown {
display: flex;
align-content: center;
align-items: center;
justify-content: center;
justify-items: center;
height: $base-avatar-dropdown-height;
padding: $base-padding-10;
.user-avatar {
width: $base-avatar-widht;
height: $base-avatar-height;
cursor: pointer;
border-radius: $base-border-radius-circle;
}
.user-name {
position: relative;
margin-left: $base-margin-5;
margin-left: $base-margin-5;
cursor: pointer;
}
}
</style>
<template>
<el-breadcrumb class="breadcrumb-container" separator=">">
<el-breadcrumb-item v-for="item in list" :key="item.path">
<component
class="menu-icon"
v-if="item.meta.icon"
theme="outline"
size="14"
strokeWidth="3"
:is="item.meta.icon"
/>
{{ item.meta.title }}
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script>
export default {
name: 'Breadcrumb',
};
</script>
<script setup>
import { computed } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
let list = computed(() => {
const { matched } = router.currentRoute.value;
return matched.filter((item) => item.name && item.meta.title);
});
</script>
<style lang="scss" scoped>
.breadcrumb-container {
display: flex;
align-items: center;
:deep(.el-breadcrumb__inner, .el-breadcrumb__item) {
display: inline-flex;
align-items: center;
margin: 1px;
}
.menu-icon {
padding-right: 5px;
}
}
</style>
<template>
<el-container class="horizontal-container">
<el-header
:style="{ 'background-color': menuBgColor }"
class="head"
:class="{ fixed: settings.fixedHead }"
>
<div class="head-nav">
<Logo v-if="settings.isLogo" />
<el-menu
class="menu"
:class="{ 'is-black': isBlack }"
:default-active="defaultActive"
:background-color="menuBgColor"
:text-color="textColor"
:active-text-color="activeTextColor"
:unique-opened="uniqueOpenedFlag"
router
mode="horizontal"
>
<template v-for="item in routes">
<template v-if="!item.hidden">
<MenuItem :item="item" :key="item.path" />
</template>
</template>
</el-menu>
<RightPanel :class="{ 'is-black': isBlack }" :color="isBlack ? '#fff' : '#666'" />
</div>
<TabBar class="tag" v-if="tag" />
</el-header>
<el-main class="main" :class="{ fixed: settings.fixedHead, istag: tag }">
<AppMain />
</el-main>
</el-container>
</template>
<script>
export default {
name: 'Horizontal',
};
</script>
<script setup>
import { computed, ref } from 'vue';
import { setting } from '@/config/setting';
const { uniqueOpened } = setting;
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { themeConfig } from '@/config/theme';
const { themeOptions } = themeConfig;
const whiteColors = ['#fff', '#ffffff', '#FFF', '#FFF', 'rgb(255, 255, 255)'];
const uniqueOpenedFlag = ref(uniqueOpened);
const store = useStore();
const router = useRouter();
const routes = computed(() => {
return store.getters['routes/routes'];
});
const settings = computed(() => {
return store.getters['setting/settings'];
});
const tag = computed(() => {
return store.getters['setting/tag'];
});
const theme = computed(() => {
return store.getters['setting/theme'];
});
const menuBgColor = computed(() => {
return themeOptions[theme.value].menuBgColor;
});
const isBlack = computed(() => {
return whiteColors.indexOf(menuBgColor.value) === -1;
});
const textColor = computed(() => {
return whiteColors.indexOf(menuBgColor.value) !== -1 ? '#333' : '#fff';
});
const activeTextColor = computed(() => {
const mcolor = whiteColors.indexOf(menuBgColor.value) !== -1;
return mcolor ? theme : '#fff';
});
const defaultActive = computed(() => {
const { fullPath } = router.currentRoute.value;
return fullPath || '/index';
});
</script>
<style lang="scss" scoped>
.horizontal-container {
position: relative;
align-items: center;
.head {
display: flex;
flex-direction: column;
align-items: center;
width: $base-width;
transition: background-color $base-transition-time;
&-nav {
display: flex;
align-items: center;
justify-content: space-between;
width: 90%;
}
.menu {
flex: 1;
}
&.fixed {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 99;
}
.tag {
width: $base-width;
padding: 5px 5%;
margin-top: -1px;
}
}
.main {
width: calc(90% + 40px);
margin: 50px 20px 0 20px;
&[class='el-main main fixed istag'] {
margin-top: $base-main-fixed-top;
}
&[class='el-main main fixed'] {
margin-top: $base-main-vertical-fixed-notag-top;
}
&[class='el-main main'] {
margin-top: $base-main-notag-top;
}
}
.is-black {
:deep(.icon-hover:hover) {
background-color: transparent;
}
}
}
</style>
<template>
<div
@click="handleClick"
class="logo-wrapper"
:class="{ unfold: collapse, horizontal: mode === 'horizontal' }"
>
<!-- <svg-icon name="vue" size="35px" /> -->
<img src="@/assets/logo.png" alt="">
<span class="logo-title" :style="{ color: textColor }" v-if="!collapse"> 系统名称 </span>
</div>
</template>
<script>
export default {
name: 'Logo',
};
</script>
<script setup>
import { computed } from 'vue';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { themeConfig } from '@/config/theme';
const { themeOptions } = themeConfig;
const store = useStore();
const router = useRouter();
const collapse = computed(() => {
return store.getters.collapse;
});
const mode = computed(() => {
return store.getters['setting/mode'];
});
const textColor = computed(() => {
const whiteColors = ['#fff', '#ffffff', '#FFF', '#FFF', 'rgb(255, 255, 255)'];
const color = themeOptions[store.getters['setting/theme']].menuBgColor;
return whiteColors.indexOf(color) !== -1 ? '#333' : '#fff';
});
const handleClick = () => {
router.replace('/');
};
</script>
<style lang="scss" scoped>
.logo-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: $base-logo-width;
cursor: pointer;
&.unfold {
width: $base-unfold-width;
padding: $base-padding-10 0;
}
&.horizontal {
justify-content: flex-start;
}
.logo-title {
display: inline-block;
max-width: calc(246px - 60px);
// padding-left: $base-padding-10;
overflow: hidden;
font-size: $base-font-size-max;
line-height: $base-logo-height;
color: #333;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
}
</style>
<template>
<el-menu-item
:key="item.path"
:index="item.children ? item.children[0].path : item.path"
v-if="!item.meta || !item.children"
>
<component
class="menu-icon"
v-if="item.children ? item.children[0].meta.icon : item.meta.icon"
theme="outline"
size="14"
strokeWidth="3"
:is="item.children ? item.children[0].meta.icon : item.meta.icon"
/>
<template #title>
<span class="title">
{{ item.children ? item.children[0].meta.title : item.meta.title }}
</span>
</template>
</el-menu-item>
<el-sub-menu :class="{ 'is-black': isBlack }" :index="item.path" v-else>
<template #title>
<component
class="menu-icon"
v-if="item.meta.icon"
theme="outline"
size="14"
strokeWidth="3"
:is="item.meta.icon"
/>
<span class="title">{{ item.meta.title }}</span>
</template>
<template v-for="(option, index) in item.children">
<menu-item v-if="option.children" :key="option.path" :item="option" />
<el-menu-item v-else :index="option.path" :key="index">
<component
class="menu-icon"
v-if="option.meta.icon"
theme="outline"
size="14"
strokeWidth="3"
:is="option.meta.icon"
/>
<span class="title">
{{ option.meta.title }}
</span>
</el-menu-item>
</template>
</el-sub-menu>
</template>
<script>
export default {
name: 'MenuItem',
};
</script>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
import { themeConfig } from '@/config/theme';
const { themeOptions } = themeConfig;
const whiteColors = ['#fff', '#ffffff', '#FFF', '#FFF', 'rgb(255, 255, 255)'];
defineProps({
item: {
type: Object,
default: () => {
return {};
},
},
});
const store = useStore();
const theme = computed(() => {
return store.getters['setting/theme'];
});
const menuBgColor = computed(() => {
return themeOptions[theme.value].menuBgColor;
});
const isBlack = computed(() => {
return whiteColors.indexOf(menuBgColor.value) === -1;
});
</script>
<style lang="scss" scoped>
.menu-icon,
.icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: $base-icon-width-big !important;
height: $base-icon-height-super-max !important;
margin-right: $base-margin-5;
visibility: initial !important;
}
</style>
<template>
<el-scrollbar height="100vh">
<el-menu
:default-active="defaultActive"
:background-color="menuBgColor"
:default-openeds="defaultOpened"
:unique-opened="uniqueOpenedFlag"
class="el-menu-vertical"
:class="{ 'is-black': isBlack }"
:collapse="isCollapse"
:text-color="textColor"
:active-text-color="activeTextColor"
router
:mode="mode"
@open="handleOpen"
@close="handleClose"
>
<Logo v-if="isLogo" />
<template v-for="item in routes">
<template v-if="!item.hidden">
<MenuItem :item="{ ...item, isBlack }" :key="item.path" />
</template>
</template>
</el-menu>
</el-scrollbar>
</template>
<script>
export default {
name: 'Menu',
};
</script>
<script setup>
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { setting } from '@/config/setting';
const { defaultOpeneds, uniqueOpened } = setting;
import { themeConfig } from '@/config/theme';
const { themeOptions } = themeConfig;
const whiteColors = ['#fff', '#ffffff', '#FFF', '#FFF', 'rgb(255, 255, 255)'];
defineProps({
isCollapse: {
type: Boolean,
default: false,
},
mode: {
type: String,
default: 'vertical',
},
});
const uniqueOpenedFlag = ref(uniqueOpened);
const store = useStore();
const router = useRouter();
const theme = computed(() => {
return store.getters['setting/theme'];
});
const menuBgColor = computed(() => {
return themeOptions[theme.value].menuBgColor;
});
const isBlack = computed(() => {
return whiteColors.indexOf(menuBgColor.value) === -1;
});
const textColor = computed(() => {
return whiteColors.indexOf(menuBgColor.value) !== -1 ? '#333' : '#fff';
});
const activeTextColor = computed(() => {
const mcolor = whiteColors.indexOf(menuBgColor.value) !== -1;
return mcolor ? theme : '#fff';
});
const routes = computed(() => {
return store.getters['routes/routes'];
});
const isLogo = computed(() => {
return store.getters['setting/isLogo'];
});
const defaultOpened = computed(() => {
return defaultOpeneds;
});
const defaultActive = computed(() => {
const { fullPath } = router.currentRoute.value;
return fullPath || '/index';
});
const handleOpen = (key, keyPath) => {
console.log('open:', key, keyPath);
};
const handleClose = (key, keyPath) => {
console.log('close:', key, keyPath);
};
</script>
<style lang="scss" scoped>
.el-menu-vertical {
position: fixed;
top: 0;
bottom: 0;
left: 0;
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
@include base-scrollbar;
&:not(.el-menu--collapse) {
width: $base-menu-width;
}
}
</style>
<template>
<div class="admin-container">
<el-container>
<el-container class="container">
<el-header class="header" height="60px">
<NavBar @handleCollapse="handleCollapse" />
<TabBar v-if="tag" />
</el-header>
<el-main class="main" :class="{ fixed: fixedHead, 'no-tag': !tag }">
<AppMain />
</el-main>
</el-container>
</el-container>
<el-drawer v-model="isDrawer" direction="ltr" :with-header="false" @close="closeDrawer">
<Menu />
</el-drawer>
</div>
</template>
<script>
export default {
name: 'Mobile',
};
</script>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const isDrawer = computed(() => {
return store.getters['setting/isDrawer'];
});
const tag = computed(() => {
return store.getters['setting/tag'];
});
const fixedHead = computed(() => {
return store.getters['setting/fixedHead'];
});
const handleCollapse = () => {
store.dispatch('setting/changeDrawer', true);
};
const closeDrawer = () => {
store.dispatch('setting/changeDrawer', false);
};
</script>
<style lang="scss" scoped>
.admin-container {
position: relative;
background-color: $base-content-bg-color;
.container {
position: absolute;
right: 0;
left: 0;
transition: all $base-transition-time-4;
}
.header {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: $base-z-index-999;
padding: 0;
transition: all $base-transition-time-4;
}
.main {
position: relative;
top: $base-main-mobile-top;
&.no-tag {
top: $base-main-mobile-no-tag-top;
}
background-color: $base-content-bg-color;
}
:deep(.el-menu) {
border-right: $base-border-none !important;
}
}
</style>
<template>
<div class="right-panel">
<icon-theme
class="icon-hover theme"
:title="t('navbar.theme')"
theme="outline"
:strokeWidth="4"
size="16"
:fill="color"
@click="handleChangeTheme"
/>
<!-- <el-popover v-if="settings.notice" placement="bottom" :width="320" trigger="hover">
<template #reference>
<icon-remind
class="icon-hover refresh"
theme="outline"
size="16"
:fill="color"
:strokeWidth="3"
/>
</template>
<div class="message-box">
<el-tabs v-model="activeName" stretch>
<el-tab-pane :label="`${t('tabs.notice')} (5)`" name="first">
<Cell :list="noticeList" />
</el-tab-pane>
<el-tab-pane :label="`${t('tabs.message')} (0)`" name="second">暂无消息</el-tab-pane>
<el-tab-pane :label="`${t('tabs.email')} (0)`" name="third">暂无邮件</el-tab-pane>
</el-tabs>
</div>
</el-popover> -->
<FullScreen :color="color" v-if="settings.fullScreen" @refresh="onRefresh" />
<LangChange :color="color" />
<icon-refresh
v-if="settings.refresh"
:title="t('navbar.refresh')"
@click="handleRefresh"
class="icon-hover refresh"
theme="filled"
size="16"
:fill="color"
:strokeWidth="4"
/>
<Avatar :color="color" />
<ThemeSetting />
</div>
</template>
<script>
export default {
name: 'RightPanel',
};
</script>
<script setup>
import { noticeList } from './data';
import FullScreen from '@/components/FullScreen/index.vue';
import Cell from '@/components/Cell/index.vue';
import LangChange from '@/components/LangChange/index.vue';
import { useI18n } from 'vue-i18n';
import { useStore } from 'vuex';
import { computed, nextTick, ref } from 'vue';
defineProps({
color: {
type: String,
default: '#666',
},
});
const { t } = useI18n();
const store = useStore();
let activeName = ref('first');
const settings = computed(() => {
return store.getters['setting/settings'];
});
const onRefresh = () => {};
const handleRefresh = () => {
store.dispatch('setting/setRouterView', false);
nextTick(() => {
store.dispatch('setting/setRouterView', true);
});
};
const handleChangeTheme = () => {
store.dispatch('setting/setSettingDrawer', true);
};
</script>
<style lang="scss" scoped>
.right-panel {
display: flex;
align-content: center;
align-items: center;
justify-content: flex-end;
height: $base-nav-bar-height;
.msg-badge {
:deep(.el-badge__content.is-fixed) {
right: calc(10px + var(--el-badge-size) / 2);
}
}
.refresh,
.theme {
padding: $base-padding-20-10;
}
}
.message-box {
padding: $base-padding-5-15;
:deep(.el-tabs__active-bar) {
width: $base-tab-width_active !important;
}
}
</style>
export const noticeList = [
{
id: 1,
title: '您收到了新的周报消息',
time: '2021-8-27 12:30:23',
icon: 'mail',
},
{
id: 2,
title: 'vue3-admin已更新到最新版本',
time: '2021-8-28 15:20:53',
icon: 'mail',
},
{
id: 3,
title: '最新功能通知',
time: '2021-8-29 15:20:53',
icon: 'mail',
},
{
id: 4,
title: '收到了一条gitstart新增消息',
time: '2021-8-23 15:20:53',
icon: 'github',
},
{
id: 5,
title: '系统数据更新1000+条',
time: '2021-8-21 15:20:53',
icon: 'mail',
},
];
<template>
<div class="nav-bar-container">
<el-row :gutter="15">
<el-col :xs="4" :sm="12" :md="12" :lg="12" :xl="12" v-if="settings.mode !== ''">
<div class="left-panel">
<component
:title="collapse ? t('navbar.unfold') : t('navbar.fold')"
class="icon-hover fold"
:is="collapse ? 'icon-menu-fold-one' : 'icon-menu-unfold-one'"
theme="filled"
size="16"
:strokeWidth="4"
fill="#666"
@click="handleCollapse"
/>
<template v-if="isBreadcrumb">
<Breadcrumb class="hidden-xs-only" />
</template>
</div>
</el-col>
<el-col :xs="20" :sm="12" :md="12" :lg="12" :xl="12">
<RightPanel />
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: 'NavBar',
};
</script>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const store = useStore();
const collapse = computed(() => {
return store.getters.collapse;
});
const isBreadcrumb = computed(() => {
return store.getters['setting/isBreadcrumb'];
});
const settings = computed(() => {
return store.getters['setting/settings'];
});
const emit = defineEmits(['handleCollapse']);
const handleCollapse = () => {
emit('handleCollapse');
};
</script>
<style lang="scss" scoped>
.nav-bar-container {
position: relative;
height: $base-nav-bar-height;
padding-right: $base-padding;
overflow: hidden;
user-select: none;
background: $base-color-white;
box-shadow: $base-box-shadow;
.left-panel {
display: flex;
align-items: center;
justify-items: center;
height: $base-nav-bar-height;
.fold-unfold {
color: $base-color-gray;
cursor: pointer;
}
.fold {
padding: $base-padding-20-10;
}
:deep(.breadcrumb-container) {
margin-left: $base-margin-10;
}
}
}
</style>
<template>
<div
id="tabs-bar-container"
class="tabs-bar-container"
:class="{ horizontal: mode === 'horizontal' }"
>
<el-tabs
v-model="tabActive"
type="card"
class="tabs-content"
@tab-click="handleTabClick"
@tab-remove="handleTabRemove"
>
<el-tab-pane
v-for="item in visitedRouteList"
:key="item.path"
:name="item.path"
:closable="!isAffix(item)"
>
<template #label>
<div class="item">
<component
class="menu-icon"
v-if="item.meta.icon"
theme="outline"
strokeWidth="3"
:is="item.meta.icon"
/>
<span>
{{ item.meta.title }}
</span>
</div>
</template>
</el-tab-pane>
</el-tabs>
<el-popover
placement="bottom"
width="auto"
trigger="hover"
@show="handleShow"
@hide="handleHide"
>
<template #reference>
<span class="more" :class="{ active: visible }" style="cursor: pointer">
<icon-all-application theme="filled" size="18" :strokeWidth="3" />
</span>
</template>
<div
class="command-item"
v-for="(item, index) in commandList"
:key="index"
@click="handleCommand(item.command)"
>
<component class="icon" theme="filled" size="14" :strokeWidth="3" :is="item.icon" />
<span class="command-label">{{ item.text }}</span>
</div>
</el-popover>
</div>
</template>
<script>
import { reactive, watch, toRefs, computed, nextTick } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
export default {
name: 'TabBar',
setup() {
const store = useStore();
const router = useRouter();
const { t } = useI18n();
const state = reactive({
affixtabs: [],
tabActive: '',
visible: false,
commandList: [
{
command: 'refreshRoute',
text: t('tagsView.refresh'),
icon: 'icon-refresh',
},
{
command: 'closeOtherstabs',
text: t('tagsView.closeOthers'),
icon: 'icon-close',
},
{
command: 'closeLefttabs',
text: t('tagsView.closeLeft'),
icon: 'icon-to-left',
},
{
command: 'closeRighttabs',
text: t('tagsView.closeRight'),
icon: 'icon-to-right',
},
{
command: 'closeAlltabs',
text: t('tagsView.closeAll'),
icon: 'icon-minus',
},
],
});
const visitedRouteList = computed(() => {
return store.getters['tabsBar/visitedRoutes'];
});
const routes = computed(() => {
return store.getters['routes/routes'];
});
const mode = computed(() => {
return store.getters['setting/mode'];
});
const filterAffixtabs = (routes) => {
let tabs = [];
routes.forEach((route) => {
if (route.meta && route.meta.affix) {
tabs.push({
fullPath: route.path,
path: route.path,
name: route.name,
meta: { ...route.meta },
});
}
if (route.children) {
const temptabs = filterAffixtabs(route.children, route.path);
if (temptabs.length >= 1) {
tabs = [...tabs, ...temptabs];
}
}
});
return tabs;
};
const inittabs = () => {
let affixtabs = (state.affixtabs = filterAffixtabs(routes.value));
for (const tag of affixtabs) {
if (tag.name) {
store.dispatch('tabsBar/addVisitedRoute', tag);
}
}
};
const addtabs = () => {
const { name } = router.currentRoute.value;
if (name) {
store.dispatch('tabsBar/addVisitedRoute', router.currentRoute.value);
}
return false;
};
watch(
() => router.currentRoute.value,
() => {
inittabs();
addtabs();
let tabActiveR = '';
visitedRouteList.value.forEach((item) => {
if (item.path === router.currentRoute.value.path) {
tabActiveR = item.path;
}
});
state.tabActive = tabActiveR;
},
{ immediate: true }
);
const isActive = (route) => {
return route.path === router.currentRoute.value.path;
};
const isAffix = (tag) => {
return tag.meta && tag.meta.affix;
};
const toLastTag = (visitedRoutes) => {
const latestView = visitedRoutes.slice(-1)[0];
if (latestView) {
router.push(latestView);
} else {
router.push('/');
}
};
const handleTabRemove = async (tabActive) => {
let view;
visitedRouteList.value.forEach((item) => {
if (tabActive == item.path) {
view = item;
}
});
const { visitedRoutes } = await store.dispatch('tabsBar/delRoute', view);
if (isActive(view)) {
toLastTag(visitedRoutes, view);
}
};
const handleTabClick = (tab) => {
const route = visitedRouteList.value.filter((item, index) => {
if (tab.index == index) return item;
})[0];
if (router.currentRoute.value.path !== route.path) {
router.push({
path: route.path,
query: route.query,
fullPath: route.fullPath,
});
} else {
return false;
}
};
const refreshRoute = async () => {
store.dispatch('setting/setRouterView', false);
nextTick(() => {
store.dispatch('setting/setRouterView', true);
});
};
const closeOtherstabs = async () => {
const view = await toThisTag();
await store.dispatch('tabsBar/delOthersRoutes', view);
};
const closeLefttabs = async () => {
const view = await toThisTag();
await store.dispatch('tabsBar/delLeftRoutes', view);
};
const closeRighttabs = async () => {
const view = await toThisTag();
await store.dispatch('tabsBar/delRightRoutes', view);
};
const closeAlltabs = async () => {
const view = await toThisTag();
const { visitedRoutes } = await store.dispatch('tabsBar/delAllRoutes');
if (state.affixtabs.some((tag) => tag.path === view.path)) {
return;
}
toLastTag(visitedRoutes, view);
};
const toThisTag = async () => {
const { fullPath, path } = router.currentRoute.value;
const view = visitedRouteList.value.filter((item) => {
if (item.path === fullPath) {
return item;
}
})[0];
if (path !== view.path) router.push(view);
return view;
};
const handleCommand = (command) => {
switch (command) {
case 'refreshRoute':
refreshRoute();
break;
case 'closeOtherstabs':
closeOtherstabs();
break;
case 'closeLefttabs':
closeLefttabs();
break;
case 'closeRighttabs':
closeRighttabs();
break;
case 'closeAlltabs':
closeAlltabs();
break;
default:
return '错误的事件类型';
}
};
const handleShow = () => {
state.visible = true;
};
const handleHide = () => {
state.visible = false;
};
return {
...toRefs(state),
visitedRouteList,
routes,
mode,
isAffix,
refreshRoute,
closeAlltabs,
closeRighttabs,
closeLefttabs,
closeOtherstabs,
handleTabClick,
handleTabRemove,
handleCommand,
handleShow,
handleHide,
};
},
};
</script>
<style lang="scss" scoped>
.tabs-bar-container {
position: relative;
box-sizing: border-box;
display: flex;
align-content: center;
align-items: center;
justify-content: space-between;
height: $base-tabs-bar-height;
padding-right: $base-padding;
padding-left: $base-padding;
user-select: none;
background: $base-color-white;
border-top: 1px solid #f6f6f6;
&.horizontal {
padding: 0 40px;
}
:deep(.fold-unfold) {
margin-right: $base-padding;
}
:deep(.el-tabs__item) {
display: inline-flex;
align-items: center;
}
.item {
display: inline-flex;
align-items: center;
.menu-icon {
display: flex;
padding-right: $base-margin-5;
}
}
.tabs-content {
width: calc(100% - 90px);
height: $base-tag-item-height;
:deep(.el-tabs__nav-next, .el-tabs__nav-prev) {
height: $base-tag-item-height;
line-height: $base-tag-item-height;
}
:deep(.el-tabs__header) {
border-bottom: 0;
.el-tabs__nav {
border: 0;
.el-tabs__item {
box-sizing: border-box;
height: $base-tag-item-height;
margin-right: $base-margin-5;
line-height: $base-tag-item-height;
border: none;
border-radius: $base-border-radius;
transition: padding 0.5s cubic-bezier(0.645, 0.045, 0.355, 1) !important;
&.is-active {
color: $base-color-primary;
background: $base-color-primary-light9;
border: none;
border-bottom: 2px solid;
}
&:hover {
color: $base-color-primary;
background: $base-color-primary-light9;
border: none;
border-bottom: 2px solid;
}
}
}
}
}
}
.command-item {
display: flex;
align-content: center;
align-items: center;
padding: 5px 10px;
cursor: pointer;
.command-label {
padding-left: 5px;
}
&:hover {
color: $base-color-primary;
background-color: $base-color-primary-light9;
}
.icon {
display: flex;
}
}
.more {
display: flex;
align-content: center;
align-items: center;
color: $base-font-color;
cursor: pointer;
transition: all 0.5s;
&.active {
color: $base-color-primary !important;
transform: rotate(180deg);
}
}
</style>
<template>
<el-drawer
:title="t('settings.title')"
v-model="settings.isDrawerSetting"
:direction="direction"
:before-close="handleClose"
destroy-on-close
:size="320"
>
<div class="theme-wrapper">
<el-scrollbar height="85vh">
<div class="form">
<el-form label-width="100px" label-position="left">
<el-form-item :label="t('settings.layout')">
<el-select
class="theme-select-width"
v-model="settings.mode"
size="small"
placeholder="请选择"
@change="handleChangeMode"
>
<el-option
v-for="item in setting.modeOption"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
<!-- <el-form-item :label="t('settings.theme')">
<el-select
class="theme-select-width"
v-model="settings.theme"
size="small"
placeholder="请选择"
@change="handleChangeTheme"
>
<el-option
v-for="item in setting.colorOptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item> -->
<el-form-item label="Logo">
<el-switch v-model="settings.isLogo" />
</el-form-item>
<el-form-item :label="t('settings.tag')">
<el-switch @change="handleChangeTag" v-model="setting.tag" />
</el-form-item>
<el-form-item :label="t('settings.breadcurmb')">
<el-switch
:disabled="settings.mode === 'horizontal'"
@change="handleChangeBread"
v-model="setting.isBreadcrumb"
/>
</el-form-item>
<el-form-item :label="t('settings.fixed')">
<el-switch :disabled="isMobile" v-model="settings.fixedHead" />
</el-form-item>
<el-form-item :label="t('settings.fullscreen')">
<el-switch v-model="settings.fullScreen" />
</el-form-item>
<el-form-item :label="t('settings.refresh')">
<el-switch v-model="settings.refresh" />
</el-form-item>
<el-form-item :label="t('settings.notice')">
<el-switch v-model="settings.notice" />
</el-form-item>
</el-form>
</div>
</el-scrollbar>
</div>
<template #footer>
<div class="drawer-footer">
<el-button size="small">{{ t('settings.defaultBtn') }}</el-button>
<el-button type="primary" size="small" @click="handleToSave">{{
t('settings.saveBtn')
}}</el-button>
</div>
</template>
</el-drawer>
</template>
<script>
export default {
name: 'ThemeSetting',
};
</script>
<script setup>
import { ref, reactive, computed, watch } from 'vue';
import { useStore } from 'vuex';
import { useI18n } from 'vue-i18n';
import { themeConfig } from '@/config/theme';
const { t } = useI18n();
const { themeOptions } = themeConfig;
const store = useStore();
const setting = reactive({
tag: true,
chalk: '',
isLogo: true,
mode: 'vertical',
isBreadcrumb: true,
fixedHead: true,
fullscreen: true,
refresh: true,
notice: true,
modeOption: [
{
value: 'vertical',
label: t('layout.vertical'),
},
{
value: 'horizontal',
label: t('layout.horizontal'),
},
],
colorOptions: [
{
value: 'theme1',
label: t('theme.options.theme1'),
},
{
value: 'theme2',
label: t('theme.options.theme2'),
},
{
value: 'theme3',
label: t('theme.options.theme3'),
},
{
value: 'theme4',
label: t('theme.options.theme4'),
},
{
value: 'theme5',
label: t('theme.options.theme5'),
},
{
value: 'theme6',
label: t('theme.options.theme6'),
},
],
});
const direction = ref('rtl');
const settings = computed(() => {
return store.getters['setting/settings'];
});
const isMobile = computed(() => {
return store.getters['setting/isMobile'];
});
const handleToSave = () => {
store.dispatch('setting/setSettingOptions', settings);
store.dispatch('setting/setSettingDrawer', false);
};
const handleChangeTag = (val) => {
store.dispatch('setting/setTag', val);
};
const handleChangeBread = (val) => {
store.dispatch('setting/setBreadcrumb', val);
};
const handleChangeMode = (val) => {
store.dispatch('setting/setSettingOptions', settings);
store.dispatch('setting/setMode', val);
};
const handleChangeTheme = (val) => {
store.dispatch('setting/setTheme', val);
};
const handleClose = () => {
store.dispatch('setting/setSettingDrawer', false);
};
</script>
<style lang="scss" scoped>
.theme-wrapper {
display: flex;
flex-direction: column;
height: $base-height;
.form {
flex: 1;
}
:deep(.el-form-item__content) {
text-align: right;
}
.theme-select-width {
width: $base-select-width-small;
}
}
.drawer-footer {
box-sizing: border-box;
align-content: center;
justify-content: space-between;
padding: 10px 0;
background-color: $base-color-white;
border-top: 1px solid $base-border-color;
}
:deep(.el-form-item__content) {
justify-content: flex-end;
}
</style>
const modulesFiles = import.meta.globEager('./*/*.vue');
// 注册
export default (app) => {
for (const path in modulesFiles) {
const componentName = modulesFiles[path].default.name;
app.component(componentName, modulesFiles[path].default);
}
};
<template>
<div class="admin-container">
<Mobile v-if="isMobile" />
<template v-else>
<el-container v-if="mode === 'vertical'">
<Menu :isCollapse="isCollapse" class="hidden-xs-only" />
<el-container class="container" :style="{ left: isCollapse ? '65px' : '240px' }">
<el-header
class="header"
:class="{ fixed: fixedHead, notag: !tag }"
height="60px"
:style="{ left: isCollapse ? '65px' : '240px' }"
>
<NavBar @handleCollapse="handleCollapse" />
<template v-if="tag">
<TabBar />
</template>
</el-header>
<el-main class="main" :class="{ fixed: fixedHead, notag: !tag }">
<AppMain />
</el-main>
</el-container>
</el-container>
<Horizontal v-if="mode === 'horizontal'" />
<el-backtop />
</template>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const isMobile = computed(() => {
return store.getters['setting/isMobile'];
});
const fixedHead = computed(() => {
return store.getters['setting/fixedHead'];
});
const tag = computed(() => {
return store.getters['setting/tag'];
});
const isCollapse = computed(() => {
return store.getters.collapse;
});
const mode = computed(() => {
return store.getters['setting/mode'];
});
const handleCollapse = () => {
store.dispatch('setting/changeCollapse');
};
</script>
<style lang="scss" scoped>
.admin-container {
position: relative;
background-color: $base-content-bg-color;
.container {
position: absolute;
right: 0;
transition: all $base-transition-time-4;
}
.header {
padding: 0;
transition: all $base-transition-time-4;
&.fixed {
position: fixed;
top: 0;
right: 0;
z-index: $base-z-index-999;
}
}
.main {
position: relative;
top: $base-main-vertical-top;
overflow-y: auto;
&.fixed {
top: $base-main-fixed-top;
}
&[class='el-main main fixed notag'] {
top: $base-main-vertical-fixed-notag-top;
}
&[class='el-main main notag'] {
top: $base-main-notag-top;
}
background-color: $base-content-bg-color;
}
}
</style>
import { createI18n } from 'vue-i18n';
import { getLanguage } from '@/utils/cookies';
import elementEnLocale from 'element-plus/lib/locale/lang/en';
import elementZhLocale from 'element-plus/lib/locale/lang/zh-cn';
// User defined lang
import enLocale from './lang/en';
import zhLocale from './lang/zh-cn';
const messages = {
en: {
...enLocale,
...elementEnLocale,
},
'zh-cn': {
...zhLocale,
...elementZhLocale,
},
};
export const getLocale = () => {
const cookieLanguage = getLanguage();
if (cookieLanguage) {
return cookieLanguage;
}
const language = navigator.language.toLowerCase();
const locales = Object.keys(messages);
for (const locale of locales) {
if (language.indexOf(locale) > -1) {
return locale;
}
}
return 'zh';
};
const i18n = createI18n({
locale: getLocale(),
messages: messages,
});
export default i18n;
/**
* @description 项目英语配置
* @author hu-snail 1217437592@qq.com
*/
export default {
route: {
home: 'Home',
icons: 'Icons',
components: 'Components',
eleComponents: 'Element Components',
charts: 'Charts',
barChart: 'Bar',
lineChart: 'Line',
mixedChart: 'Other',
errorPages: 'Error Pages',
page401: '401',
page404: '404',
},
navbar: {
logOut: 'Log Out',
dashboard: 'Dashboard',
github: 'Github',
theme: 'Change theme',
full: 'Full screen',
noFull: 'Exit full screen',
refresh: 'Refresh',
fold: 'Fold',
unfold: 'Unfold',
size: 'Global Size',
profile: 'Profile',
},
login: {
title: 'Login',
rules: {
username: 'Please enter a username',
password: 'Please enter your password',
},
loginBtn: 'Login',
desc: 'Out of the box in the background management system',
tip: 'Click login for a quick experience',
username: 'Username',
password: 'Password',
thirdparty: 'Third-party Login',
rememberPwd: 'Remember the password',
forgotPwd: 'Forgot password',
},
register: {
title: 'Register',
registerBtn: 'Register',
username: 'UserName',
smsCode: 'SMS verification code',
smsbtn: 'Get Code',
password: 'Password',
confirmPwd: 'Confirm Password',
checkText: 'I agree with XXX Privacy Policy',
},
theme: {
change: 'Change Theme',
documentation: 'Theme documentation',
tips: 'Tips: It is different from the theme-pick on the navbar is two different skinning methods, each with different application scenarios. Refer to the documentation for details.',
loading: 'Theme change loading...',
options: {
theme1: 'Blue white',
theme2: 'Blue black',
theme3: 'Green white',
theme4: 'Green black',
theme5: 'Red white',
theme6: 'Red black',
},
},
tagsView: {
refresh: 'Refresh',
closeLeft: 'Close Left',
closeRight: 'Close Right',
closeOthers: 'Close Others',
closeAll: 'Close All',
},
settings: {
title: 'Theme Settings',
layout: 'Layout',
theme: 'Theme',
menuBg: 'Menu Theme',
logo: 'Logo',
tag: 'Tag',
breadcurmb: 'Breadcurmb',
fixed: 'fixed Header',
fullscreen: 'Fuscreen',
refresh: 'Refresh',
notice: 'Notice',
defaultBtn: 'Restore the default',
saveBtn: 'Save',
},
layout: {
vertical: 'Vertical',
horizontal: 'Horizontal',
},
sayHi: {
early: 'Good early morning',
morning: 'Good morning',
noon: 'Good noon',
afternoon: 'Good afternoon',
evening: 'Good evening',
},
notice: {
msg: 'Welcome to ',
},
tabs: {
notice: 'Notice',
message: 'Message',
email: 'EMail',
},
indexPage: {
descTitle: 'Start your day!',
resourceTitle: 'Vue3 related resources are recommended',
orderTitle: 'The order list',
order: {
planned: 'Planned',
finished: 'Completed',
unfinished: 'Outstanding',
},
skillTitle: 'The skills list',
envTitle: 'Production environments depend on information',
chartTitle: 'Smoothed Line Chart',
},
errorPages: {
title: 'Sorry!',
btn: 'Back Home',
404: {
desc: 'Current page does not exist...',
remark:
'Please check whether the url you entered is correct, or click the button below to return to the home page',
},
401: {
desc: "You don't have permission to go to this page...",
remark:
'Please contact the administrator or click the button below to return to the home page',
},
},
echarts: {
demo: 'Demo',
line: {
title: 'Line',
demo1Title: 'Stacked area chart',
demo2Title: 'Smoothed Line Chart',
demo3Title: 'Stacked area chart',
},
bar: {
title: 'Bar',
demo1Title: 'Basic Bar',
demo2Title: 'The amount of evaporation and precipitation in an area',
},
other: {
title: 'Other Charts',
demo1Title: 'Basic Candlestick',
demo2Title: 'Basic Scatter Chart',
demo3Title: 'Doughnut Chart with Rounded Corner',
demo4Title: 'Basic Radar Chart',
demo5Title: 'Simple Gauge',
demo6Title: 'Funnel Chart',
},
},
iconPage: {
title: 'Icons',
demo: 'Demo',
props: 'Props',
table: {
label1: 'Params',
label2: 'Type',
label3: 'Options',
label4: 'Default',
label5: 'Descrition',
},
},
element: {
title: 'Element-Plus Demo',
btn: 'Button',
radio: 'Radio',
checkBox: 'CheckBox',
datePicker: 'DatePicker',
dateTimePicker: 'DataTimePicker',
},
confirm: {
title: 'Operating hints',
msg: 'Are you sure you want to exit',
},
btn: {
confirm: 'Confirm',
cancel: 'Cancel',
},
};
/**
* @description 项目简体中文配置
* @author hu-snail 1217437592@qq.com
*/
export default {
route: {
home: '首页',
icons: '图标',
components: '组件',
eleComponents: 'Element 组件',
charts: '图表',
barChart: '柱状图表',
lineChart: '折线图',
mixedChart: '其他图表',
errorPages: '错误页面',
page401: '401',
page404: '404',
},
navbar: {
logOut: '退出登录',
github: '项目地址',
theme: '切换换肤',
full: '全屏',
noFull: '退出全屏',
refresh: '刷新',
fold: '收起',
unfold: '展开',
size: '布局大小',
profile: '个人中心',
},
login: {
title: '登录',
rules: {
username: '请输入用户名',
password: '请输入密码',
},
loginBtn: '登录',
desc: '开箱即用的中后台管理系统',
tip: '点击登录快速体验',
username: '账号',
password: '密码',
thirdparty: '第三方登录',
rememberPwd: '记住密码',
forgotPwd: '忘记密码',
},
register: {
title: '注册',
registerBtn: '注册',
username: '手机号',
smsCode: '短信验证码',
smsbtn: '获取验证码',
password: '密码',
confirmPwd: '确认密码',
checkText: '我同意xxx隐私政策',
},
theme: {
change: '换肤',
documentation: '换肤文档',
tips: 'Tips: 它区别于 navbar 上的 theme-pick, 是两种不同的换肤方法,各自有不同的应用场景,具体请参考文档。',
loading: '主题正在努力重置...',
options: {
theme1: '蓝白',
theme2: '蓝黑',
theme3: '绿白',
theme4: '绿黑',
theme5: '红白',
theme6: '红黑',
},
},
tagsView: {
refresh: '重新加载',
closeLeft: '关闭左侧',
closeRight: '关闭右侧',
closeOthers: '关闭其它',
closeAll: '关闭所有',
},
settings: {
title: '主题设置',
layout: '布局',
theme: '主题',
menuBg: '菜单主题',
logo: 'Logo',
tag: '标签',
breadcurmb: '面包导航',
fixed: '固定头部',
fullscreen: '全屏',
refresh: '刷新',
notice: '通知',
defaultBtn: '恢复默认',
saveBtn: '保存',
},
layout: {
vertical: '纵向',
horizontal: '横向',
},
sayHi: {
early: '早上好',
morning: '上午好',
noon: '中午好',
afternoon: '下午好',
evening: '晚上好',
},
notice: {
msg: '欢迎登录',
},
tabs: {
notice: '通知',
message: '消息',
email: '邮件',
},
indexPage: {
descTitle: '开始您一天的工作吧!',
resourceTitle: 'Vue3相关资源推荐',
orderTitle: '订单清单',
order: {
planned: '计划订单',
finished: '已完成订单',
unfinished: '未完成订单',
},
skillTitle: '技能列表',
envTitle: '生产环境依赖信息',
chartTitle: '基础平滑折线图',
},
errorPages: {
title: '抱歉!',
btn: '返回首页',
404: {
desc: '当前页面不存在...',
remark: '请检查您输入的网址是否正确,或点击下面的按钮返回首页',
},
401: {
desc: '你没有权限去该页面...',
remark: '请联系管理员,或点击下面的按钮返回首页',
},
},
echarts: {
demo: '演示',
line: {
title: '折线图',
demo1Title: '基础折线图',
demo2Title: '基础平滑折线图',
demo3Title: '堆叠面积图',
},
bar: {
title: '柱状图',
demo1Title: '基础柱状图',
demo2Title: '某地区蒸发量和降水量',
},
other: {
title: '其他图表',
demo1Title: '基础 K 线图',
demo2Title: '基础散点图',
demo3Title: '圆角环形图',
demo4Title: '基础雷达图',
demo5Title: '数字动画仪表盘',
demo6Title: '漏斗图',
},
},
iconPage: {
title: '图标库',
demo: '演示',
props: '属性',
table: {
label1: '参数',
label2: '类型',
label3: '可选值',
label4: '默认值',
label5: '说明',
},
},
element: {
title: 'Element-Plus 组件演示',
btn: '按钮',
radio: '单选',
checkBox: '多选',
datePicker: '日期选择器',
dateTimePicker: '日期时间选择器',
},
confirm: {
title: '操作提示',
msg: '您确定要退出',
},
btn: {
confirm: '确定',
cancel: '取消',
},
};
import { createApp } from 'vue';
// permission 权限文件
import './config/permission';
// element
import 'element-plus/theme-chalk/display.css';
import App from './App.vue';
const app = createApp(App);
import { VueClipboard } from '@soerenmartius/vue3-clipboard';
app.use(VueClipboard);
// layout components
import layoutComp from './layouts/components/export';
layoutComp(app);
// router
import router from './router/index';
app.use(router);
// vuex
import store from '@/store';
app.use(store);
// 按需注册方式
// import elementPlus from './plugin/el-comp';
// 注册 elementPlus组件/插件
// elementPlus(app);
// // 完整引入
// 注册字节跳动图标
import iconPark from './plugin/icon-park';
iconPark(app);
import loadI18n from './plugin/i18n';
loadI18n(app);
app.mount('#app');
// mockProdServer.ts
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
// 逐一导入您的mock.ts文件
// 如果使用vite.mock.config.ts,只需直接导入文件
// 可以使用 import.meta.glob功能来进行全部导入
import userMock from '../mock/user';
import routerMock from '../mock/router';
import iconMock from '../mock/icon';
import indexMock from '../mock/index';
export const setupProdMockServer = () => {
createProdMockServer([...userMock, ...routerMock, ...iconMock, ...indexMock]);
};
import {
ElAlert,
ElAside,
ElButton,
ElSelect,
ElRow,
ElCol,
ElForm,
ElFormItem,
ElInput,
ElTabs,
ElTabPane,
ElCheckbox,
ElIcon,
ElDivider,
ElBacktop,
ElDropdown,
ElDropdownMenu,
ElDropdownItem,
ElContainer,
ElHeader,
ElSlider,
ElMain,
ElFooter,
ElMenu,
ElMenuItem,
ElMenuItemGroup,
ElSubmenu,
ElRadio,
ElRadioButton,
ElRadioGroup,
ElTooltip,
ElScrollbar,
ElTableColumn,
ElTag,
ElTable,
} from 'element-plus';
// 所需的组件
export const components = [
ElAlert,
ElAside,
ElButton,
ElSelect,
ElRow,
ElCol,
ElForm,
ElFormItem,
ElInput,
ElTabs,
ElTabPane,
ElCheckbox,
ElIcon,
ElDivider,
ElBacktop,
ElDropdown,
ElDropdownMenu,
ElDropdownItem,
ElContainer,
ElHeader,
ElSlider,
ElMain,
ElFooter,
ElMenu,
ElMenuItem,
ElMenuItemGroup,
ElSubmenu,
ElRadio,
ElRadioButton,
ElRadioGroup,
ElTooltip,
ElScrollbar,
ElTableColumn,
ElTag,
ElTable,
];
// 注册
export default (app) => {
components.forEach((component) => {
console.log(component);
app.component(component.name, component);
});
};
import i18n from '@/locales';
export default function loadComponent(app) {
app.use(i18n);
}
/**
* 字节跳动图标库:https://iconpark.oceanengine.com/official
* and vue3 https://github.com/bytedance/IconPark/tree/master/packages/vue-next
* element-plus icon: https://element-plus.gitee.io/#/zh-CN/component/icon
* @description 图标库按需注册
* @author hu-snail
* @example <icon-user theme="outline" size="16" fill="#999" />
* @example <el-icon :size="20"> <edit /> </el-icon>
*/
import { install } from '@icon-park/vue-next/es/all';
// iconpark
import {
User,
Lock,
Alipay,
Wechat,
Github,
Twitter,
Google,
MenuUnfoldOne,
MenuFoldOne,
FullScreen,
OffScreen,
Refresh,
Remind,
AllApplication,
Close,
ToLeft,
ToRight,
Minus,
Mail,
Home,
Code,
ChartLine,
Like,
Xigua,
Performance,
Pic,
MoveOne,
Search,
Tailoring,
TailoringTwo,
AddText,
ScanCode,
Play,
PauseOne,
VolumeNotice,
VolumeMute,
PlayCycle,
PlayOnce,
GoStart,
GoEnd,
MusicList,
LinkCloudFaild,
LinkInterrupt,
Copy,
ChartHistogram,
MultiPictureCarousel,
Theme,
Translate,
} from '@icon-park/vue-next';
import '@icon-park/vue-next/styles/index.css';
// el-icon
// 所需的组件
export const components = [
User,
Lock,
Alipay,
Wechat,
Github,
Twitter,
Google,
MenuUnfoldOne,
MenuFoldOne,
FullScreen,
OffScreen,
Refresh,
Remind,
AllApplication,
Close,
ToLeft,
ToRight,
Minus,
Mail,
Home,
Code,
ChartLine,
Like,
Xigua,
Performance,
Pic,
MoveOne,
Search,
Tailoring,
TailoringTwo,
AddText,
ScanCode,
Play,
PauseOne,
VolumeNotice,
VolumeMute,
PlayCycle,
PlayOnce,
GoStart,
GoEnd,
MusicList,
LinkCloudFaild,
LinkInterrupt,
Copy,
ChartHistogram,
MultiPictureCarousel,
Theme,
Translate,
];
import SvgIcon from '@/components/SvgIcon/index.vue';
// 注册
export default (app) => {
app.component('svg-icon', SvgIcon);
install(app);
};
import { readFileSync, readdirSync } from 'fs';
let idPerfix = '';
const svgTitle = /<svg([^>+].*?)>/;
const clearHeightWidth = /(width|height)="([^>+].*?)"/g;
const hasViewBox = /(viewBox="[^>+].*?")/g;
const clearReturn = /(\r)|(\n)/g;
function findSvgFile(dir) {
const svgRes = [];
const dirents = readdirSync(dir, {
withFileTypes: true,
});
for (const dirent of dirents) {
if (dirent.isDirectory()) {
svgRes.push(...findSvgFile(dir + dirent.name + '/'));
} else {
const svg = readFileSync(dir + dirent.name)
.toString()
.replace(clearReturn, '')
.replace(svgTitle, ($1, $2) => {
// console.log(++i)
// console.log(dirent.name)
let width = 0;
let height = 0;
let content = $2.replace(clearHeightWidth, (s1, s2, s3) => {
if (s2 === 'width') {
width = s3;
} else if (s2 === 'height') {
height = s3;
}
return '';
});
if (!hasViewBox.test($2)) {
content += `viewBox="0 0 ${width} ${height}"`;
}
return `<symbol id="${idPerfix}-${dirent.name.replace('.svg', '')}" ${content}>`;
})
.replace('</svg>', '</symbol>');
svgRes.push(svg);
}
}
return svgRes;
}
export const svgBuilder = (path, perfix = 'icon') => {
if (path === '') return;
idPerfix = perfix;
const res = findSvgFile(path);
// console.log(res.length)
// const res = []
return {
name: 'svg-transform',
transformIndexHtml(html) {
return html.replace(
'<body>',
`
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0">
${res.join('')}
</svg>
`
);
},
};
};
import { createRouter, createWebHashHistory } from 'vue-router';
import Layout from '@/layouts/index.vue';
import i18n from '@/locales';
const { global } = i18n;
export const constantRoutes = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/login/index.vue'),
meta: {
title: '登录',
},
hidden: true,
},
{
path: '/401',
name: '401',
component: () => import('@/views/errorPage/401.vue'),
hidden: true,
},
{
path: '/404',
name: '404',
component: () => import('@/views/errorPage/404.vue'),
hidden: true,
},
];
export const asyncRoutes = [
{
path: '/',
component: Layout,
redirect: '/index',
name: 'Root',
children: [
{
path: '/index',
name: 'Index',
component: () => import('../views/index/index.vue'),
meta: {
title: global.t('route.home'),
icon: 'icon-home',
affix: true,
noKeepAlive: true,
},
},
],
},
{
path: '/comp',
component: Layout,
name: 'Comp',
meta: { title: global.t('route.components'), icon: 'icon-code' },
children: [
{
path: '/element',
name: 'ElementComp',
component: () => import('@/views/element/index.vue'),
meta: {
title: global.t('route.eleComponents'),
icon: 'icon-code',
},
},
{
path: '/iconPark',
name: 'IconPark',
component: () => import('@/views/icon/index.vue'),
meta: {
title: global.t('route.icons'),
icon: 'icon-like',
},
},
{
path: '/chart',
name: 'Chart',
component: () => import('@/views/echarts/index.vue'),
meta: {
title: global.t('route.charts'),
icon: 'icon-chart-line',
},
children: [
{
path: '/line',
name: 'Line',
component: () => import('@/views/echarts/line.vue'),
meta: {
title: global.t('route.lineChart'),
},
},
{
path: '/bar',
name: 'Bar',
component: () => import('@/views/echarts/bar.vue'),
meta: {
title: global.t('route.barChart'),
},
},
{
path: '/otherChart',
name: 'OtherChart',
component: () => import('@/views/echarts/other.vue'),
meta: {
title: global.t('route.mixedChart'),
},
},
],
},
],
},
{
path: '/errorPage',
name: 'ErrorPage',
component: Layout,
meta: {
title: global.t('route.errorPages'),
icon: 'icon-link-cloud-faild',
},
children: [
{
path: '/404Page',
name: '404Page',
component: () => import('@/views/errorPage/404.vue'),
meta: {
title: global.t('route.page404'),
icon: 'icon-link-cloud-faild',
},
},
{
path: '/401Page',
name: '401Page',
component: () => import('@/views/errorPage/401.vue'),
meta: {
title: global.t('route.page401'),
icon: 'icon-link-interrupt',
},
},
],
},
// {
// path: '*',
// redirect: '/404',
// hidden: true,
// },
];
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes,
});
// reset router
export function resetRouter() {
router.getRoutes().forEach((route) => {
const { name } = route;
if (name) {
router.hasRoute(name) && router.removeRoute(name);
}
});
}
export default router;
const getters = {
userInfo: (state) => state.user.userInfo,
token: (state) => state.user.token,
collapse: (state) => state.setting.collapse,
fullScreen: (state) => state.setting.fullScreen,
};
export default getters;
import { createStore } from 'vuex';
import getters from './getters.js';
// https://vitejs.dev/guide/features.html#glob-import
const modulesFiles = import.meta.globEager('./modules/*.js');
let modules = {};
for (const path in modulesFiles) {
const moduleName = path.replace(/(.*\/)*([^.]+).*/gi, '$2');
modules[moduleName] = modulesFiles[path].default;
}
Object.keys(modules).forEach((key) => {
modules[key]['namespaced'] = true;
});
const store = new createStore({
modules,
getters,
});
export default store;
import { asyncRoutes, constantRoutes } from '@/router';
import { getRouterList } from '@/api/router';
import { convertRouter, filterAsyncRoutes } from '@/utils/handleRoutes';
const state = () => ({
routes: [],
partialRoutes: [],
});
const getters = {
routes: (state) => state.routes,
partialRoutes: (state) => state.partialRoutes,
};
const mutations = {
setRoutes(state, routes) {
state.routes = constantRoutes.concat(routes);
},
setAllRoutes(state, routes) {
state.routes = constantRoutes.concat(routes);
},
setPartialRoutes(state, routes) {
state.partialRoutes = constantRoutes.concat(routes);
},
};
const actions = {
async setRoutes({ commit }, permissions) {
//开源版只过滤动态路由permissions,admin不再默认拥有全部权限
const finallyAsyncRoutes = await filterAsyncRoutes([...asyncRoutes], permissions);
commit('setRoutes', finallyAsyncRoutes);
return finallyAsyncRoutes;
},
async setAllRoutes({ commit }) {
let { data } = await getRouterList();
// data.push({ path: '*', redirect: '/404', hidden: true });
let accessRoutes = convertRouter(data);
commit('setAllRoutes', accessRoutes);
return accessRoutes;
},
setPartialRoutes({ commit }, accessRoutes) {
commit('setPartialRoutes', accessRoutes);
return accessRoutes;
},
};
export default { state, getters, mutations, actions };
/**
* @description 主题全局配置状态
* @author hu-snail 1217437592@qq.com
*/
import { themeConfig } from '@/config/theme';
import { setting } from '@/config/setting';
import { getLanguage, setLanguage, setSettings, getSettings } from '@/utils/cookies';
const { mode, theme, fixedHead, fullScreen, refresh, collapse, notice, isBreadcrumb, isLogo, tag } =
themeConfig;
const { lang } = setting;
const state = {
routerView: true, // 是否显示路由
isDrawerSetting: false, // 是否打开主题设置
isMobile: false, // 是否为移动端
isDrawer: false, // 是否展开移动端菜单
isFullScreen: false, // 是否显示全屏
collapse,
fullScreen,
refresh,
mode: getSettings() ? getSettings().mode : mode,
theme,
fixedHead,
notice,
isBreadcrumb,
isLogo,
tag,
lang: getLanguage() || lang,
};
const getters = {
routerView: (state) => state.routerView,
isMobile: (state) => state.isMobile,
isDrawer: (state) => state.isDrawer,
isFullScreen: (state) => state.isFullScreen,
theme: (state) => state.theme,
isDrawerSetting: (state) => state.isDrawerSetting,
fullScreen: (state) => state.fullScreen,
refresh: (state) => state.refresh,
fixedHead: (state) => state.fixedHead,
notice: (state) => state.notice,
isBreadcrumb: (state) => state.isBreadcrumb,
isLogo: (state) => state.isLogo,
tag: (state) => state.tag,
mode: (state) => state.mode,
settings: (state) => state,
lang: (state) => state.lang,
};
const mutations = {
CHANGE_COLLAPSE: (state) => {
state.collapse = !state.collapse;
},
CHANGE_FULL_SCREEN: (state, flag) => {
state.isFullScreen = flag;
},
SET_ROUTER_VIEW: (state) => {
state.routerView = !state.routerView;
},
CHANGE_IS_MOBILE: (state, flag) => {
state.isMobile = flag;
},
CHANGE_IS_DRAWER: (state, flag) => {
state.isDrawer = flag;
},
SET_THEME: (state, theme) => {
state.theme = theme;
},
CHANGE_SETTING_DRAWER: (state, flag) => {
state.isDrawerSetting = flag;
},
CHANGE_BREADCRUMB: (state, flag) => {
state.isBreadcrumb = flag;
},
CHANGE_TAG: (state, flag) => {
state.tag = flag;
},
CHANE_MODE: (state, mode) => {
state.mode = mode;
},
SET_SETTING_OPTIONS: (state, options) => {
setSettings(options.value);
Object.assign(state, { ...options.value });
},
CHANGE_LANGUAGE: (state, lang) => {
setLanguage(lang);
state.lang = lang;
},
};
const actions = {
/**
* @description 切换展开收起
*/
changeCollapse: ({ commit }) => {
commit('CHANGE_COLLAPSE');
},
/**
* @description 切换是否全屏
* @param {boolean} flag true|false
*/
changeFullScreen: ({ commit }, flag) => {
commit('CHANGE_FULL_SCREEN', flag);
},
/**
* @description 是否刷新路由
* @param {boolean} flag true|false
*/
setRouterView: ({ commit }, flag) => {
commit('SET_ROUTER_VIEW', flag);
},
/**
* @description 是否为移动端
* @param {boolean} flag true|false
*/
changeMobile: ({ commit }, flag) => {
commit('CHANGE_IS_MOBILE', flag);
},
/**
* @description 是否展开移动端菜单
* @param {boolean} flag true|false
*/
changeDrawer: ({ commit }, flag) => {
commit('CHANGE_IS_DRAWER', flag);
},
/**
* @description 设置主题
* @param {strinng} theme 系统默认:blue|green|red|default
*/
setTheme: ({ commit }, theme) => {
commit('SET_THEME', theme);
},
/**
* @description 是否打开主题设置
* @param {boolean} flag true|false
*/
setSettingDrawer: ({ commit }, flag) => {
commit('CHANGE_SETTING_DRAWER', flag);
},
/**
* @description 是否显示面包导航
* @param {boolean} flag true|false
*/
setBreadcrumb: ({ commit }, flag) => {
commit('CHANGE_BREADCRUMB', flag);
},
/**
* @description 是否显示标签
* @param {boolean} flag true|false
*/
setTag: ({ commit }, flag) => {
commit('CHANGE_TAG', flag);
},
/**
* @description 切换布局
* @param {string} mode 可选值:vertical|horizontal
*/
setMode: ({ commit }, mode) => {
commit('CHANE_MODE', mode);
},
/**
* @description 切换语言
* @param {string} lang 语言 可选值: zh-cn|en
*/
changeLanguage: ({ commit }, lang) => {
commit('CHANGE_LANGUAGE', lang);
},
/**
* @description 设置主题配置信息
* @param {object} options 配置项
*/
setSettingOptions: ({ commit }, options) => {
commit('SET_SETTING_OPTIONS', options);
},
};
export default {
getters,
state,
mutations,
actions,
};
/**
* @description 路由tabbar
*/
const state = {
visitedRoutes: [],
};
const getters = {
visitedRoutes: (state) => state.visitedRoutes,
};
const mutations = {
addVisitedRoute(state, route) {
let target = state.visitedRoutes.find((item) => item.path === route.path);
if (target) {
if (route.fullPath !== target.fullPath) Object.assign(target, route);
return;
}
state.visitedRoutes.push(Object.assign({}, route));
},
delVisitedRoute(state, route) {
state.visitedRoutes.forEach((item, index) => {
if (item.path === route.path) state.visitedRoutes.splice(index, 1);
});
},
delOthersVisitedRoute(state, route) {
state.visitedRoutes = state.visitedRoutes.filter(
(item) => item.meta.affix || item.path === route.path
);
},
delLeftVisitedRoute(state, route) {
let index = state.visitedRoutes.length;
state.visitedRoutes = state.visitedRoutes.filter((item) => {
if (item.name === route.name) index = state.visitedRoutes.indexOf(item);
return item.meta.affix || index <= state.visitedRoutes.indexOf(item);
});
},
delRightVisitedRoute(state, route) {
let index = state.visitedRoutes.length;
state.visitedRoutes = state.visitedRoutes.filter((item) => {
if (item.name === route.name) index = state.visitedRoutes.indexOf(item);
return item.meta.affix || index >= state.visitedRoutes.indexOf(item);
});
},
delAllVisitedRoutes(state) {
state.visitedRoutes = state.visitedRoutes.filter((item) => item.meta.affix);
},
updateVisitedRoute(state, route) {
state.visitedRoutes.forEach((item) => {
if (item.path === route.path) item = Object.assign(item, route);
});
},
};
const actions = {
addVisitedRoute({ commit }, route) {
commit('addVisitedRoute', route);
},
async delRoute({ dispatch, state }, route) {
await dispatch('delVisitedRoute', route);
return {
visitedRoutes: [...state.visitedRoutes],
};
},
delVisitedRoute({ commit, state }, route) {
commit('delVisitedRoute', route);
return [...state.visitedRoutes];
},
async delOthersRoutes({ dispatch, state }, route) {
await dispatch('delOthersVisitedRoute', route);
return {
visitedRoutes: [...state.visitedRoutes],
};
},
async delLeftRoutes({ dispatch, state }, route) {
await dispatch('delLeftVisitedRoute', route);
return {
visitedRoutes: [...state.visitedRoutes],
};
},
async delRightRoutes({ dispatch, state }, route) {
await dispatch('delRightVisitedRoute', route);
return {
visitedRoutes: [...state.visitedRoutes],
};
},
delOthersVisitedRoute({ commit, state }, route) {
commit('delOthersVisitedRoute', route);
return [...state.visitedRoutes];
},
delLeftVisitedRoute({ commit, state }, route) {
commit('delLeftVisitedRoute', route);
return [...state.visitedRoutes];
},
delRightVisitedRoute({ commit, state }, route) {
commit('delRightVisitedRoute', route);
return [...state.visitedRoutes];
},
async delAllRoutes({ dispatch, state }, route) {
await dispatch('delAllVisitedRoutes', route);
return {
visitedRoutes: [...state.visitedRoutes],
};
},
delAllVisitedRoutes({ commit, state }) {
commit('delAllVisitedRoutes');
return [...state.visitedRoutes];
},
updateVisitedRoute({ commit }, route) {
commit('updateVisitedRoute', route);
},
};
export default { state, getters, mutations, actions };
import { getUserInfo, login } from '@/api/user';
import { getAccessToken, removeAccessToken, setAccessToken } from '@/utils/accessToken';
import { setting } from '@/config/setting';
const { title, tokenName } = setting;
import { resetRouter } from '@/router';
import i18n from '@/locales';
const { global } = i18n;
import { ElMessage, ElNotification } from 'element-plus';
const state = {
accessToken: getAccessToken(),
username: '',
avatar: '',
permissions: [],
};
const getters = {
accessToken: (state) => state.accessToken,
username: (state) => state.username,
avatar: (state) => state.avatar,
permissions: (state) => state.permissions,
};
const mutations = {
setAccessToken(state, accessToken) {
state.accessToken = accessToken;
setAccessToken(accessToken);
},
setUsername(state, username) {
state.username = username;
},
setAvatar(state, avatar) {
state.avatar = avatar;
},
setPermissions(state, permissions) {
state.permissions = permissions;
},
};
const actions = {
setPermissions({ commit }, permissions) {
commit('setPermissions', permissions);
},
async login({ commit }, userInfo) {
const { data } = await login(userInfo);
const accessToken = data[tokenName];
if (accessToken) {
commit('setAccessToken', accessToken);
const hour = new Date().getHours();
const thisTime =
hour < 8
? global.t('sayHi.early')
: hour <= 11
? global.t('sayHi.morning')
: hour <= 13
? global.t('sayHi.noon')
: hour < 18
? global.t('sayHi.afternoon')
: global.t('sayHi.evening');
ElNotification({
title: `${thisTime}!`,
message: `${global.t('notice.msg')}${title}!`,
type: 'success',
});
} else {
ElMessage.error(`登录接口异常,未正确返回${tokenName}...`);
}
},
async getUserInfo({ commit, state }) {
const { data } = await getUserInfo(state.accessToken);
if (!data) {
ElMessage.error('验证失败,请重新登录...');
return false;
}
let { permissions, username, avatar } = data;
if (permissions && username && Array.isArray(permissions)) {
commit('setPermissions', permissions);
commit('setUsername', username);
commit('setAvatar', avatar);
return permissions;
} else {
ElMessage.error('用户信息接口异常');
return false;
}
},
async logout({ dispatch }) {
// await logout(state.accessToken);
await dispatch('resetAccessToken');
await resetRouter();
},
resetAccessToken({ commit }) {
commit('setPermissions', []);
commit('setAccessToken', '');
removeAccessToken();
},
};
export default { state, getters, mutations, actions };
@import './variable.scss';
@mixin scrollbar {
max-height: 88vh;
margin-bottom: 0.5vh;
overflow-y: auto;
&::-webkit-scrollbar {
width: 0;
height: 0;
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(144, 147, 153, 0.3);
border-radius: 10px;
}
&::-webkit-scrollbar-thumb:hover {
// background-color: rgba(144, 147, 153, 0.3);
}
}
@mixin base-scrollbar {
&::-webkit-scrollbar {
width: 10px;
height: 10px;
}
&::-webkit-scrollbar-thumb {
background-color: #ddd;
background-clip: padding-box;
border: 3px solid transparent;
border-radius: 7px;
}
&::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.5);
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-track:hover {
// background-color: #f8fafc;
}
}
.icon-hover{
cursor: pointer;
&:hover {
background-color: $base-hover-color;
}
}
.i-icon:focus-visible{
border: none !important;
outline: none !important;
}
html {
body {
@include base-scrollbar;
position: relative;
box-sizing: border-box;
height: 100vh;
padding: 0;
overflow: hidden;
}
div {
@include base-scrollbar;
}
}
\ No newline at end of file
.el-popover.el-popper {
min-width: 100px !important;
padding: 5px 0 !important;
}
.el-menu-item {
&.is-active {
// background-color: $base-color-primary-light9 !important;
border-right: $base-border-width-small solid $base-color-primary;
span {
color: $base-color-primary;
}
}
&:hover {
color: $base-color-primary !important;
// background-color: $base-color-primary-light9 !important;
}
}
.is-black {
.el-menu-item {
&.is-active {
background-color: $base-color-primary !important;
border-right: 0;
span {
color: $base-color-white;
}
}
&:hover {
color: $base-color-white !important;
background-color: $base-color-primary !important;
}
}
}
.el-sub-menu__title:hover {
background-color: transparent !important;
}
.el-menu--horizontal > .el-menu-item {
&.is-active {
border-right: none;
}
}
.el-menu--horizontal {
.el-menu-item {
&.is-active {
background-color: $base-color-primary !important;
border-right: 0;
span {
color: $base-color-white;
}
}
&:hover {
color: $base-color-white !important;
background-color: $base-color-primary !important;
}
}
.el-sub-menu__title{
color: $base-color-3 !important;
}
}
.el-menu--horizontal.is-black{
.el-sub-menu__title{
color: $base-color-white !important;
}
}
.el-sub-menu.is-black{
.el-sub-menu__title{
color: $base-color-white !important;
}
}
.el-menu--collapse .el-menu-item{
text-align: center;
}
.el-menu--horizontal .el-menu .el-menu-item,
.el-menu--horizontal .el-menu .el-sub-menu__title {
height: $sub-menu__title-height !important;
line-height: $sub-menu__title-height !important;
}
.el-header {
--el-header-padding: 0;
}
.el-button--primary:active {
background-color: $base-color-primary !important;
border-color: $base-color-primary !important;
}
.el-menu--collapse .menu-icon{
display: flex !important;
justify-content: center;
}
\ No newline at end of file
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': #00a69c,
)
)
);
@use "element-plus/theme-chalk/src/message.scss" as *;
@use "element-plus/theme-chalk/src/message-box.scss" as *;
@import './common.scss';
@import './reset.scss';
@import './variable.scss';
@import './element-reset.scss'
\ No newline at end of file
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
// :root{
// --color-primary: #08a17e;
// --color-text-primar: #08a17e;
// }
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
margin: 0.67em 0;
font-size: 2em;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
border-bottom: none; /* 1 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
margin: 0; /* 2 */
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input {
/* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select {
/* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
color: inherit; /* 2 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}
/**
* 全局主题变量配置
*/
// 框架默认主题色
$base-color-primary: var(--el-color-primary);
// 主题色透明
$base-color-primary-light1: var(--el-color-primary-light-1);
$base-color-primary-light2: var(--el-color-primary-light-2);
$base-color-primary-light3: var(--el-color-primary-light-3);
$base-color-primary-light4: var(--el-color-primary-light-4);
$base-color-primary-light5: var(--el-color-primary-light-5);
$base-color-primary-light6: var(--el-color-primary-light-6);
$base-color-primary-light7: var(--el-color-primary-light-7);
$base-color-primary-light8: var(--el-color-primary-light-8);
$base-color-primary-light9: var(--el-color-primary-light-9);
$base-z-index-999: 999;
$base-z-index-default: 99;
// hover基础样式
$base-hover-color: #f5f5f5;
// 中间内容背景
$base-content-bg-color: #f1f2f5;
// 标题颜色
$base-title-color: #fff;
// 深色背景
$dark-bg-color: #293246;
// width
$base-width: 100%;
$base-tab-width_active: 70px;
$base-select-width-small: 120px;
$base-drawer-width: 320px;
$base-logo-width: 240px;
// 收起宽度
$base-unfold-width: 60px;
// 菜单栏宽度
$base-menu-width: 240px;
// 头像宽度
$base-avatar-widht: 40px;
// height
$base-height: 100%;
// 主题配置底部高度
$base-drawer-footer-height: 60px;
// 二级菜单标题高度
$sub-menu__title-height: 50px;
// logo 高度
$base-logo-height: 55px;
// 头像下拉框高度
$base-avatar-dropdown-height: 50px;
// 头像高度
$base-avatar-height: 40px;
// 底部copyright高度
$footer-copyright-height: 55px;
// 内容最低高度
$app-main-min-height: calc(100vh - 140px);
// top
// 移动端内容距顶部高度
$base-main-mobile-top: 110px;
// 移动端内容无标签距离顶部高度
$base-main-mobile-no-tag-top: 60px;
// 纵向布局内容呢距顶部距离
$base-main-vertical-top: 50px;
// 内容呢距顶部距离(固定头部,有标签)
$base-main-fixed-top: 110px;
// 内容呢距顶部距离(固定头部, 无标签)
$base-main-vertical-fixed-notag-top: 60px;
// 内容呢距顶部距离(无固定,无标签)
$base-main-notag-top: 0;
// 边框配置
$base-border-width-mini: 1px;
$base-border-width-small: 3px;
$base-border-width-default: 5px;
$base-border-width-big: 10px;
$base-border-radius: 2px;
$base-border-radius-circle: 50%;
$base-border-none: none;
// 字体大小配置
$base-font-size-small: 12px;
$base-font-size-default: 14px;
$base-font-size-big: 16px;
$base-font-size-bigger: 18px;
$base-font-size-max: 22px;
$base-border-color: #dcdfe6;
// icon配置
$base-icon-width-default: 14px;
$base-icon-width-small: 12px;
$base-icon-width-big: 16px;
$base-icon-width-bigger: 18px;
$base-icon-width-max: 22px;
$base-icon-width-super-max: 34px;
$base-icon-height-super-max: 50px;
// 字体颜色
$base-font-color: #606266;
$base-color-6: #666666;
$base-color-3: #333333;
$base-color-blue: $base-color-primary;
$base-color-green: #91cc75;
$base-color-white: #fff;
$base-color-black: #000;
$base-color-yellow: #fac858;
$base-color-orange: #ff6700;
$base-color-red: #ee6666;
$base-color-gray: rgba(0, 0, 0, 0.65);
// paddiing
$base-main-padding: 20px 30px;
$base-content-padding: 15px 20px;
$base-padding: 20px;
$base-cell-item-padding: 8px 0;
$base-padding-20-10: 20px 10px;
$base-padding-10-20: 10px 20px;
$base-padding-5-15: 5px 15px;
$base-padding-10: 10px;
// margin
$base-margin-5: 5px;
$base-margin-10: 10px;
$base-margin-15: 15px;
$base-margin-20: 20px;
$base-margin-20: 25px;
//默认阴影
$base-box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
$base-tabs-bar-height: 55px;
$base-tag-item-height: 34px;
$base-nav-bar-height: 60px;
//默认动画
$base-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), border 0s,
background 0s, color 0s, font-size 0s;
//默认动画长
$base-transition-time: 0.3s;
$base-transition-time-4: 0.4s;
$base-color: #f45;
$green-color: #11d86c;
$color-red: red;
$color-green: green;
$color-blue: blue;
\ No newline at end of file
import { setting } from '@/config/setting';
const { tokenTableName } = setting;
import Cookies from 'js-cookie';
export function getAccessToken() {
return Cookies.get(tokenTableName);
}
export function setAccessToken(accessToken) {
return Cookies.set(tokenTableName, accessToken);
}
export function removeAccessToken() {
return Cookies.remove(tokenTableName);
}
import { setting } from '@/config/setting';
const { langKey, themeKey } = setting;
import Cookies from 'js-cookie';
export function getLanguage() {
return Cookies.get(langKey);
}
export function setLanguage(lang) {
return Cookies.set(langKey, lang);
}
export function getSettings() {
const settings = Cookies.get(themeKey);
return settings ? JSON.parse(settings) : null;
}
export function setSettings(theme) {
return Cookies.set(themeKey, JSON.stringify(theme));
}
import { asyncRoutes } from '@/router';
export function convertRouter(routers) {
return routers.map((route) => {
return setRoutes(route, asyncRoutes);
});
}
/**
* @description 处理后端路由数据
* @param {*} route 路由数据
* @param {*} list 前端路由 asyncRoutes配置项
* @returns {*}
*/
const setRoutes = (route, list) => {
list.filter((item) => {
if (item.path === route.path) {
route.component = item.component;
route.meta = item.meta;
route.name = item.name;
if (route.children && route.children.length) {
let children = [];
route.children.filter((option) => {
children.push(setRoutes(option, item.children));
});
route.children = children;
}
}
});
return route;
};
function hasPermission(permissions, route) {
if (route.meta && route.meta.permissions) {
return permissions.some((role) => route.meta.permissions.includes(role));
} else {
return true;
}
}
export function filterAsyncRoutes(routes, permissions) {
const finallyRoutes = [];
routes.forEach((route) => {
const item = { ...route };
if (hasPermission(permissions, item)) {
if (item.children) {
item.children = filterAsyncRoutes(item.children, permissions);
}
finallyRoutes.push(item);
}
});
return finallyRoutes;
}
/**
* @author hu-snail 1217437592@qq.com
* @description 常用公共工具函数
*/
import { setting } from '@/config/setting';
const { title } = setting;
export const getPageTitle = (pageTitle) => {
if (pageTitle) {
return `${pageTitle}-${title}`;
}
return `${title}`;
};
import axios from 'axios';
import { netConfig } from '@/config/net.config';
const { baseURL, contentType, invalidCode, noPermissionCode, requestTimeout, successCode } =
netConfig;
import store from '@/store/index.js';
import router from '@/router/index.js';
import { ElMessageBox, ElMessage } from 'element-plus';
import qs from 'qs';
import { setting } from '@/config/setting';
const { tokenName } = setting;
// eslint-disable-next-line no-unused-vars
let tokenLose = true;
/**
*
* @description 处理code异常
* @param {*} code
* @param {*} msg
*/
const handleCode = (code, msg) => {
switch (code) {
case invalidCode:
tokenLose = false;
ElMessageBox.confirm('您已掉线,或者访问权限出错,请重新登录!', '重新登录', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
// 处理重新登录逻辑
})
.catch(() => {
tokenLose = true;
});
break;
case noPermissionCode:
router.push({ path: '/401' }).catch(() => {});
break;
default:
console.log('---');
ElMessage.error(msg || `后端接口${code}异常`);
break;
}
};
const instance = axios.create({
baseURL,
timeout: requestTimeout,
headers: {
'Content-Type': contentType,
},
});
instance.interceptors.request.use(
(config) => {
if (store.getters['user/accessToken']) {
config.headers[tokenName] = store.getters['user/accessToken'];
}
if (
config.data &&
config.headers['Content-Type'] === 'application/x-www-form-urlencoded;charset=UTF-8'
)
config.data = qs.stringify(config.data);
return config;
},
(error) => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response) => {
const res = response.data;
const { data } = response;
const { code, msg } = data;
// 操作成功
if (successCode.indexOf(code) !== -1) {
return res;
} else {
console.log('---====', response);
handleCode(code, msg);
return Promise.reject();
}
},
(error) => {
const { response, message } = error;
console.log(error);
if (error.response && error.response.data) {
const { status, data } = response;
console.log('---===1222=', response);
handleCode(status, data.msg || message);
return Promise.reject(error);
} else {
let { message } = error;
if (message === 'Network Error') {
message = '后端接口连接异常';
}
if (message.includes('timeout')) {
message = '后端接口请求超时';
}
if (message.includes('Request failed with status code')) {
const code = message.substr(message.length - 3);
console.log('---===2244=', response);
message = '后端接口' + code + '异常';
}
console.log('---===224ee4=', response);
ElMessage.error(message || `后端接口未知异常`);
return Promise.reject(error);
}
}
);
export default instance;
File mode changed
<template>
<div class="echarts-container">
<Descrition :title="t('echarts.bar.title')">
<template #descrition>
Vue3-admin 推荐使用
<a href="https://echarts.apache.org/examples/zh/index.html#chart-type-line" target="_blank"
>Echarts</a
>
作为图表库
</template>
</Descrition>
<Descrition :title="t('echarts.demo')" :showDesc="false"></Descrition>
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
<Echarts
:title="t('echarts.bar.demo1Title')"
headerIcon="icon-chart-histogram"
:style="{
height: '280px',
}"
:options="{
series,
xAxis,
yAxis,
}"
/>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
<Echarts
:index="1"
:title="t('echarts.bar.demo2Title')"
headerIcon="icon-chart-histogram"
:style="{
height: '280px',
}"
:options="{
series: series2,
xAxis: xAxis2,
yAxis: yAxis2,
calculable,
grid,
}"
/>
</el-col>
</el-row>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue';
import Descrition from '@/components/Descrition/index.vue';
import Echarts from '@/components/Echarts/index.vue';
import { useI18n } from 'vue-i18n';
export default {
components: { Descrition, Echarts },
setup() {
const { t } = useI18n();
const state = reactive({
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
},
],
tooltip: {
trigger: 'axis',
},
calculable: true,
xAxis2: [
{
type: 'category',
data: [
'1月',
'2月',
'3月',
'4月',
'5月',
'6月',
'7月',
'8月',
'9月',
'10月',
'11月',
'12月',
],
},
],
yAxis2: [
{
type: 'value',
},
],
series2: [
{
name: '蒸发量',
type: 'bar',
data: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3],
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' },
],
},
markLine: {
data: [{ type: 'average', name: '平均值' }],
},
},
{
name: '降水量',
type: 'bar',
data: [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3],
markPoint: {
data: [
{ name: '年最高', value: 182.2, xAxis: 7, yAxis: 183 },
{ name: '年最低', value: 2.3, xAxis: 11, yAxis: 3 },
],
},
markLine: {
data: [{ type: 'average', name: '平均值' }],
},
},
],
grid: {
top: '24px',
right: '18px',
left: 0,
bottom: 0,
containLabel: true,
},
});
return {
...toRefs(state),
t,
};
},
};
</script>
<style lang="scss" scoped>
.echarts-container {
padding: $base-main-padding;
background-color: $base-color-white;
}
</style>
<template>
<router-view></router-view>
</template>
<template>
<div class="echarts-container">
<Descrition :title="t('echarts.line.title')">
<template #descrition>
Vue3-admin 推荐使用
<a href="https://echarts.apache.org/examples/zh/index.html#chart-type-line" target="_blank"
>Echarts</a
>
作为图表库
</template>
</Descrition>
<Descrition :title="t('echarts.demo')" :showDesc="false"></Descrition>
<el-row :gutter="10">
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<Echarts
:title="t('echarts.line.demo1Title')"
headerIcon="icon-chart-line"
:style="{
height: '200px',
}"
:options="{
series,
xAxis,
yAxis,
toolbox,
}"
/>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<Echarts
:title="t('echarts.line.demo2Title')"
:index="1"
headerIcon="icon-chart-line"
:style="{
height: '200px',
}"
:options="{
series: series2,
xAxis,
yAxis,
toolbox,
}"
/>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<Echarts
:title="t('echarts.line.demo3Title')"
:index="2"
headerIcon="icon-chart-line"
:style="{
height: '200px',
}"
:options="{
series: series3,
xAxis: xAxis3,
yAxis: yAxis3,
tooltip,
grid,
}"
/>
</el-col>
</el-row>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue';
import Descrition from '@/components/Descrition/index.vue';
import Echarts from '@/components/Echarts/index.vue';
import { useI18n } from 'vue-i18n';
export default {
components: { Descrition, Echarts },
setup() {
const { t } = useI18n();
const state = reactive({
series: [{ data: [150, 230, 224, 218, 135, 147, 260], type: 'line' }],
series2: [
{
data: [820, 932, 901, 934, 1290, 1330, 1320],
type: 'line',
smooth: true,
},
],
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
toolbox: {
show: true,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985',
},
},
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
},
xAxis3: [
{
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
],
yAxis3: [
{
type: 'value',
},
],
series3: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
areaStyle: {},
emphasis: {
focus: 'series',
},
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
areaStyle: {},
emphasis: {
focus: 'series',
},
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: '视频广告',
type: 'line',
stack: '总量',
areaStyle: {},
emphasis: {
focus: 'series',
},
data: [150, 232, 201, 154, 190, 330, 410],
},
{
name: '直接访问',
type: 'line',
stack: '总量',
areaStyle: {},
emphasis: {
focus: 'series',
},
data: [320, 332, 301, 334, 390, 330, 320],
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
label: {
show: true,
position: 'top',
},
areaStyle: {},
emphasis: {
focus: 'series',
},
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
grid: {
top: '5px',
left: 0,
right: '14px',
bottom: 0,
containLabel: true,
},
});
return {
...toRefs(state),
t,
};
},
};
</script>
<style lang="scss" scoped>
.echarts-container {
padding: $base-main-padding;
background-color: $base-color-white;
}
</style>
<template>
<div class="echarts-container">
<Descrition :title="t('echarts.other.title')">
<template #descrition>
Vue3-admin 推荐使用
<a href="https://echarts.apache.org/examples/zh/index.html#chart-type-line" target="_blank"
>Echarts</a
>
作为图表库
</template>
</Descrition>
<Descrition :title="t('echarts.demo')" :showDesc="false"></Descrition>
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<Echarts
:title="t('echarts.other.demo1Title')"
headerIcon="icon-chart-histogram"
:style="{
height: '200px',
}"
:options="{
series,
xAxis,
yAxis,
}"
/>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<Echarts
:index="1"
:title="t('echarts.other.demo2Title')"
headerIcon="icon-chart-histogram"
:style="{
height: '200px',
}"
:options="{
xAxis: {},
yAxis: {},
series: series2,
}"
/>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<Echarts
:index="2"
:title="t('echarts.other.demo3Title')"
headerIcon="icon-chart-histogram"
:style="{
height: '200px',
}"
:options="{
tooltip,
series: series3,
}"
/>
</el-col>
</el-row>
<el-row :gutter="20" class="row">
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<Echarts
:index="3"
:title="t('echarts.other.demo4Title')"
headerIcon="icon-chart-histogram"
:style="{
height: '200px',
}"
:options="{
radar,
series: series4,
}"
/>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<Echarts
:index="4"
:title="t('echarts.other.demo5Title')"
headerIcon="icon-chart-histogram"
:style="{
height: '200px',
}"
:options="{
tooltip: tooltip5,
series: series5,
textStyle: {
fontSize: 12,
},
}"
/>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
<Echarts
:index="5"
:title="t('echarts.other.demo6Title')"
headerIcon="icon-chart-histogram"
:style="{
height: '200px',
}"
:options="{
tooltip: tooltip6,
series: series6,
}"
/>
</el-col>
</el-row>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue';
import Descrition from '@/components/Descrition/index.vue';
import Echarts from '@/components/Echarts/index.vue';
import { useI18n } from 'vue-i18n';
export default {
components: { Descrition, Echarts },
setup() {
const { t } = useI18n();
const state = reactive({
xAxis: {
data: ['2017-10-24', '2017-10-25', '2017-10-26', '2017-10-27'],
},
yAxis: {},
series: [
{
type: 'k',
data: [
[20, 34, 10, 38],
[40, 35, 30, 50],
[31, 38, 33, 44],
[38, 15, 5, 42],
],
},
],
series2: [
{
symbolSize: 20,
data: [
[10.0, 8.04],
[8.07, 6.95],
[13.0, 7.58],
[9.05, 8.81],
[11.0, 8.33],
[14.0, 8.96],
[12.5, 6.82],
[9.15, 7.2],
[11.5, 7.2],
[3.03, 4.23],
[12.2, 7.83],
[2.02, 4.47],
[1.05, 3.33],
[4.05, 4.96],
[6.03, 7.24],
[7.08, 5.82],
[5.02, 5.68],
],
type: 'scatter',
},
],
tooltip: {
trigger: 'item',
},
series3: [
{
name: '访问来源',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '14',
},
},
labelLine: {
show: false,
},
data: [
{ value: 1048, name: '搜索引擎' },
{ value: 735, name: '直接访问' },
{ value: 580, name: '邮件营销' },
{ value: 484, name: '联盟广告' },
{ value: 300, name: '视频广告' },
],
},
],
radar: {
// shape: 'circle',
indicator: [
{ name: '销售(Sales)', max: 6500 },
{ name: '管理(Administration)', max: 16000 },
{ name: '信息技术(Information Technology)', max: 30000 },
{ name: '客服(Customer Support)', max: 38000 },
{ name: '研发(Development)', max: 52000 },
{ name: '市场(Marketing)', max: 25000 },
],
},
series4: [
{
name: '预算 vs 开销(Budget vs spending)',
type: 'radar',
data: [
{
value: [4200, 3000, 20000, 35000, 50000, 18000],
name: '预算分配(Allocated Budget)',
},
{
value: [5000, 14000, 28000, 26000, 42000, 21000],
name: '实际开销(Actual Spending)',
},
],
},
],
tooltip5: {
formatter: '{a} <br/>{b} : {c}%',
},
series5: [
{
name: 'Pressure',
type: 'gauge',
progress: {
show: true,
},
radius: '100%',
title: {
fontSize: 12,
},
detail: {
valueAnimation: true,
formatter: '{value}',
},
data: [
{
value: 50,
name: 'SCORE',
},
],
},
],
tooltip6: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c}%',
},
series6: [
{
name: '漏斗图',
type: 'funnel',
left: '10%',
top: 0,
//x2: 80,
bottom: 0,
width: '80%',
// height: {totalHeight} - y - y2,
min: 0,
max: 100,
minSize: '0%',
maxSize: '100%',
sort: 'descending',
gap: 2,
label: {
show: true,
position: 'inside',
},
labelLine: {
length: 20,
lineStyle: {
width: 1,
type: 'solid',
},
},
itemStyle: {
borderColor: '#fff',
borderWidth: 1,
},
emphasis: {
label: {
fontSize: 14,
},
},
data: [
{ value: 60, name: '访问' },
{ value: 40, name: '咨询' },
{ value: 20, name: '订单' },
{ value: 80, name: '点击' },
{ value: 100, name: '展现' },
],
},
],
});
return {
...toRefs(state),
t,
};
},
};
</script>
<style lang="scss" scoped>
.echarts-container {
padding: $base-main-padding;
background-color: $base-color-white;
.row {
margin-top: 20px;
}
}
</style>
<template>
<div class="element-container">
<Descrition :title="t('element.title')">
<template #descrition>
Vue3-admin-element-template使用的是
<a href="https://element-plus.gitee.io/#/zh-CN/component/installation" target="_blank"
>Element-Plus</a
>
UI组件库,以下是常用的组件
</template>
</Descrition>
<Descrition :showDesc="false" :title="t('element.btn')" />
<el-row>
<el-row>
<el-button round>圆角按钮</el-button>
<el-button type="primary" round>主要按钮</el-button>
<el-button type="success" round>成功按钮</el-button>
<el-button type="info" round>信息按钮</el-button>
<el-button type="warning" round>警告按钮</el-button>
<el-button type="danger" round>危险按钮</el-button>
</el-row>
</el-row>
<el-row class="row">
<el-button-group>
<el-button type="primary" :icon="ArrowLeft">上一页</el-button>
<el-button type="primary"
>下一页<el-icon class="el-icon--right"><ArrowRight /></el-icon>
</el-button>
</el-button-group>
<el-button-group class="group">
<el-button type="primary" :icon="Edit" />
<el-button type="primary" :icon="Share" />
<el-button type="primary" :icon="Delete" />
</el-button-group>
</el-row>
<Descrition :showDesc="false" :title="t('element.radio')" />
<el-row class="row">
<el-radio-group v-model="radio">
<el-radio :label="3">备选项</el-radio>
<el-radio :label="6">备选项</el-radio>
<el-radio :label="9">备选项</el-radio>
</el-radio-group>
<el-radio-group class="group" v-model="radio2">
<el-radio-button label="上海"></el-radio-button>
<el-radio-button label="北京"></el-radio-button>
<el-radio-button label="广州"></el-radio-button>
<el-radio-button label="深圳"></el-radio-button>
</el-radio-group>
</el-row>
<Descrition :showDesc="false" :title="t('element.checkBox')" />
<el-row class="row">
<el-checkbox v-model="checked1" label="备选项1"></el-checkbox>
<el-checkbox v-model="checked2" label="备选项1"></el-checkbox>
<el-checkbox-group class="group" v-model="checkbox">
<el-checkbox-button v-for="city in cities" :label="city" :key="city">{{
city
}}</el-checkbox-button>
</el-checkbox-group>
</el-row>
<Descrition :showDesc="false" :title="t('element.datePicker')" />
<el-date-picker
v-model="date"
type="monthrange"
range-separator="至"
start-placeholder="开始月份"
end-placeholder="结束月份"
>
</el-date-picker>
<Descrition :showDesc="false" :title="t('element.dateTimePicker')" />
<el-date-picker
v-model="dateTime"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
>
</el-date-picker>
</div>
</template>
<script setup>
import { ArrowLeft, ArrowRight, Delete, Edit, Share } from '@element-plus/icons-vue';
import Descrition from '@/components/Descrition/index.vue';
import { ref, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const cityOptions = ['上海', '北京', '广州', '深圳'];
const radio = ref(3);
const radio2 = ref('上海');
const checked1 = ref(true);
const checked2 = ref();
const checkbox = ref(['上海']);
const date = ref();
const dateTime = ref();
const cities = reactive(cityOptions);
</script>
<style lang="scss" scoped>
.element-container {
padding: $base-main-padding;
background-color: $base-color-white;
.row {
margin: 20px 0;
}
.group {
margin: 0 20px;
}
}
</style>
<template>
<ErrorPage type="401" :title="t('errorPages.401.desc')" :msg="t('errorPages.401.remark')" />
</template>
<script setup>
import ErrorPage from '@/components/ErrorPage/index.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<ErrorPage type="404" :title="t('errorPages.404.desc')" :msg="t('errorPages.404.remark')" />
</template>
<script setup>
import ErrorPage from '@/components/ErrorPage/index.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
<template>
<div class="icon-container">
<Descrition :title="t('iconPage.title')">
<template #descrition>
Vue3-admin 推荐使用
<a href="https://iconpark.oceanengine.com/official" target="_blank">IconPark</a>
作为图标库
</template>
</Descrition>
<Descrition :title="t('iconPage.demo')" :showDesc="false"></Descrition>
<div class="search">
<el-input
v-model="state.skey"
placeholder="请输入类型、名称、图标名"
class="input-with-select"
>
<template #append>
<el-button :icon="Search" @click="handleSearchIcon" />
</template>
</el-input>
</div>
<div class="icon-centent">
<div
class="icon-item"
v-for="(item, index) in state.list"
@click="handleClickChip('icon-' + item.name)"
:key="index"
>
<component class="icon-name" size="20" :is="'icon-' + item.name" />
<p class="icon-title">{{ item.name }}</p>
</div>
</div>
<div class="page">
<el-pagination
@current-change="handleCurrentChange"
v-model:currentPage="state.currentPage"
:page-size="49"
layout="total, prev, pager, next"
:total="state.searchFlag ? state.searchList.length : icons.length"
>
</el-pagination>
</div>
<Descrition :title="t('iconPage.props')" :showDesc="false"></Descrition>
<el-table :data="state.icon.props" border style="width: 100%">
<el-table-column prop="param" :label="t('iconPage.table.label1')" width="180">
</el-table-column>
<el-table-column prop="type" :label="t('iconPage.table.label2')" width="180">
</el-table-column>
<el-table-column prop="all" :label="t('iconPage.table.label3')"> </el-table-column>
<el-table-column prop="default" :label="t('iconPage.table.label4')"> </el-table-column>
<el-table-column prop="desc" :label="t('iconPage.table.label5')"> </el-table-column>
</el-table>
</div>
</template>
<script setup>
import { getIcons } from '@/api/icon';
import { reactive, toRefs, onBeforeMount } from 'vue';
import Descrition from '@/components/Descrition/index.vue';
import { useI18n } from 'vue-i18n';
import icons from '@icon-park/vue-next/icons.json';
import { ElMessage } from 'element-plus';
import { Search } from '@element-plus/icons-vue';
import { toClipboard } from '@soerenmartius/vue3-clipboard';
const { t } = useI18n();
const state = reactive({
icon: {},
list: [],
searchList: [],
currentPage: 1,
currTotal: 49,
skey: '',
searchFlag: false,
});
onBeforeMount(async () => {
state.list = icons.slice(state.currentPage - 1, state.currTotal);
await handleGetIcons();
});
const handleGetIcons = async () => {
getIcons().then((res) => {
state.icon = res.data;
});
};
const handleClickChip = (icon) => {
console.log(icon)
toClipboard(icon);
ElMessage({
message: '复制成功:' + icon,
type: 'success',
});
};
const handleCurrentChange = (val) => {
state.currentPage = val;
const start = val * 49 - 49;
const end = val * 49;
const iconArr = state.skey ? state.searchList : icons;
state.list = iconArr.slice(start, end);
};
const handleSearchIcon = () => {
state.currentPage = 1;
state.currTotal = 49;
if (!state.skey) {
state.searchFlag = false;
state.list = icons.slice(0, 49);
} else {
state.searchFlag = true;
let list = [];
icons.map((item) => {
if (
item.title.indexOf(state.skey) !== -1 ||
item.name.indexOf(state.skey) !== -1 ||
item.categoryCN.indexOf(state.skey) !== -1
) {
list.push(item);
}
});
state.searchList = list;
state.list = state.searchList.slice(state.currentPage - 1, state.currTotal);
}
};
</script>
<style lang="scss" scoped>
.icon-container {
padding: $base-main-padding;
background-color: $base-color-white;
.title {
&.reset {
margin-top: 40px;
}
}
.search {
width: 260px;
padding: 15px 0;
}
.icon-centent {
display: flex;
flex-wrap: wrap;
.icon-item {
display: flex;
align-items: center;
width: calc(100% / 9);
padding: 5px 15px;
margin: 5px;
cursor: pointer;
border-radius: 6px;
box-shadow: $base-box-shadow;
.icon-name {
width: 30px;
width: 30px;
}
.icon-title {
padding-left: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
.page {
padding: 10px 0;
}
}
</style>
<template>
<div class="index-conntainer">
首页
</div>
</template>
<script>
export default {
name: 'Index',
};
</script>
<script setup>
import { ref, computed, reactive, onBeforeMount } from 'vue';
import { CountTo } from 'vue3-count-to';
import Echarts from '@/components/Echarts/index.vue';
import packpage from '../../../package.json';
import { useI18n } from 'vue-i18n';
import { getResouceList } from '@/api/index';
import { useStore } from 'vuex';
const store = useStore();
const { t } = useI18n();
</script>
<style lang="scss" scoped>
.index-conntainer {
width: $base-width;
}
</style>
<template>
<el-form :model="ruleForm" :rules="rules" ref="validateForm" class="login-ruleForm">
<el-form-item prop="username">
<el-input :placeholder="t('login.username')" v-model="ruleForm.username">
<template #prefix>
<icon-user theme="outline" size="16" fill="#999" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
@keyup.enter="handleLogin"
:placeholder="t('login.password')"
type="password"
v-model="ruleForm.password"
>
<template #prefix>
<icon-lock theme="outline" size="16" fill="#999" />
</template>
</el-input>
</el-form-item>
<el-form-item>
<div class="login-check">
<el-checkbox v-model="checkedPwd">{{ t('login.rememberPwd') }}</el-checkbox>
<el-button text type="primary">{{ t('login.forgotPwd') }}</el-button>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" class="login-btn" round @click="handleLogin">{{
t('login.loginBtn')
}}</el-button>
</el-form-item>
<el-divider>{{ t('login.thirdparty') }}</el-divider>
<el-form-item>
<div class="login-methods">
<icon-wechat theme="outline" size="24" fill="#333" />
<icon-alipay theme="outline" size="24" fill="#333" />
<icon-github theme="outline" size="24" fill="#333" />
<icon-twitter theme="outline" size="24" fill="#333" />
<icon-google theme="outline" size="24" fill="#333" />
</div>
</el-form-item>
</el-form>
</template>
<script>
import { reactive, toRefs, ref, unref, watch } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
export default {
setup() {
const { t } = useI18n();
const store = useStore();
const router = useRouter();
const validateForm = ref(null);
const state = reactive({
ruleForm: {
username: 'admin',
password: 'admin',
},
loading: false,
checkedPwd: false,
redirect: undefined,
rules: {
username: [{ required: true, message: t('login.rules.username'), trigger: 'blur' }],
password: [{ required: true, message: t('login.rules.password'), trigger: 'blur' }],
},
});
watch(
() => router.currentRoute,
({ _value }) => {
const route = _value;
state.redirect = (route.query && route.query.redirect) || '/';
},
{
immediate: true,
}
);
const handleLogin = async () => {
const form = unref(validateForm);
if (!form) return;
await form.validate((valid) => {
if (valid) {
state.valid = true;
state.loading = true;
store
.dispatch('user/login', state.ruleForm)
.then(() => {
const routerPath =
state.redirect === '/404' || state.redirect === '/401' ? '/' : state.redirect;
router.push(routerPath).catch(() => {});
state.loading = false;
})
.catch(() => {
state.loading = false;
});
}
});
};
return {
...toRefs(state),
validateForm,
handleLogin,
t,
};
},
};
</script>
<style lang="scss" scoped>
.login-ruleForm {
margin-top: 1rem;
:deep(.el-input__prefix) {
top: 2px;
padding: 0 4px;
}
.login-methods {
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
}
.login-btn {
width: 100%;
}
.login-check {
width: 100%;
display: flex;
align-content: center;
justify-content: space-between;
}
}
</style>
<template>
<el-form :model="form" ref="form" class="login-ruleForm">
<el-form-item prop="name">
<el-input :placeholder="t('register.username')" v-model="form.name">
<template #prefix>
<icon-user theme="outline" size="16" fill="#999" />
</template>
</el-input>
</el-form-item>
<el-form-item>
<div class="form-code">
<el-input :placeholder="t('register.smsCode')" v-model="form.name">
<template #prefix>
<icon-user theme="outline" size="16" fill="#999" />
</template>
</el-input>
<el-button type="primary" class="code-btn">{{ t('register.smsbtn') }}</el-button>
</div>
</el-form-item>
<el-form-item prop="password">
<el-input :placeholder="t('register.password')" type="password" v-model="form.password">
<template #prefix>
<icon-lock theme="outline" size="16" fill="#999" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input :placeholder="t('register.confirmPwd')" type="password" v-model="form.password">
<template #prefix>
<icon-lock theme="outline" size="16" fill="#999" />
</template>
</el-input>
</el-form-item>
<el-form-item>
<div class="login-check">
<el-checkbox v-model="checkedPwd">{{ t('register.checkText') }}</el-checkbox>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" class="login-btn" round>{{ t('register.registerBtn') }}</el-button>
</el-form-item>
</el-form>
</template>
<script>
import { reactive, toRefs } from 'vue';
import { useI18n } from 'vue-i18n';
export default {
setup() {
const { t } = useI18n();
const state = reactive({
form: {
name: '',
password: '',
},
checkedPwd: false,
});
return {
...toRefs(state),
t,
};
},
};
</script>
<style lang="scss" scoped>
.login-ruleForm {
margin-top: 1rem;
:deep(.el-input__prefix) {
top: 2px;
padding: 0 4px;
}
.login-methods {
display: flex;
align-items: center;
justify-content: space-around;
}
.login-btn {
width: 100%;
margin-bottom: 1rem;
}
.login-check {
display: flex;
align-content: center;
justify-content: space-between;
}
.form-code {
display: flex;
align-content: center;
justify-content: space-between;
.code-btn {
margin-left: 1rem;
}
}
}
</style>
<template>
<div class="login-wrapper">
<el-header class="header">
<Logo class="logo" />
<LangChange class="lang" color="#fff" />
</el-header>
<div class="login-container">
<div class="login-left hidden-sm-and-down">
<div class="login-left-wrap">
<img class="img" src="@/assets/login.png" alt="login-bg" />
<h2 class="desc">{{ t('login.desc') }}</h2>
<p class="tip">{{ t('login.tip') }}</p>
</div>
</div>
<div class="login-form" :class="{ 'is-mobile': isMobile }">
<div class="form-warp">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane :label="t('login.title')" name="first">
<LoginForm />
</el-tab-pane>
<el-tab-pane :label="t('register.title')" name="second">
<RegisterForm />
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import LoginForm from 'views/login/comp/LoginForm.vue';
import RegisterForm from './comp/RegisterForm.vue';
import LangChange from '@/components/LangChange/index.vue';
import { useI18n } from 'vue-i18n';
const store = useStore();
const { t } = useI18n();
const activeName = ref('first');
const isMobile = computed(() => {
return store.getters['setting/isMobile'];
});
const handleClick = (val) => {
console.log(val);
};
</script>
<style lang="scss" scoped>
.login-wrapper {
position: relative;
.header {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 99;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 40px;
color: #fff;
background: transparent;
.logo {
justify-content: start;
:deep(.logo-title) {
color: #fff !important;
}
}
.lang:hover {
background: transparent;
}
}
.login-container {
display: flex;
width: 100%;
height: 100vh;
overflow: hidden;
background-color: $dark-bg-color;
.login-left {
position: relative;
display: flex;
flex-direction: column;
width: 50vw;
height: 100%;
background-image: url('@/assets/login-bg-dark.svg');
background-repeat: no-repeat;
background-position: 100%;
background-size: auto 100%;
&-wrap {
height: 80vh;
margin: auto;
.img {
width: 280px;
margin-top: 10vh;
}
.title,
.desc {
max-width: 500px;
font-weight: bold;
color: #fff;
letter-spacing: 1.2px;
}
.desc {
font-size: 28px;
}
.tip {
color: #fff;
}
}
}
.login-form {
display: flex;
align-items: center;
justify-content: center;
width: 50vw;
height: 100vh;
.form-warp {
width: 400px;
padding: 1rem 3rem 0 3rem;
margin: auto;
background-color: #fff;
border-radius: 8px;
}
&.is-mobile {
width: 100%;
.form-warp {
width: 100%;
margin: 0 15px;
}
}
}
}
}
</style>
module.exports = {
root: true,
plugins: ['stylelint-config-recess-order'],
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
rules: {
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global'],
},
],
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep'],
},
],
'at-rule-no-unknown': [
true,
{
ignoreAtRules: [
'tailwind',
'apply',
'variants',
'responsive',
'screen',
'function',
'if',
'each',
'include',
'mixin',
],
},
],
'no-empty-source': null,
'named-grid-areas-no-invalid': null,
'unicode-bom': 'never',
'no-descending-specificity': null,
'font-family-no-missing-generic-family-keyword': null,
'declaration-colon-space-after': 'always-single-line',
'declaration-colon-space-before': 'never',
// 'declaration-block-trailing-semicolon': 'always',
'rule-empty-line-before': [
'always',
{
ignore: ['after-comment', 'first-nested'],
},
],
'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
'order/order': [
[
'dollar-variables',
'custom-properties',
'at-rules',
'declarations',
{
type: 'at-rule',
name: 'supports',
},
{
type: 'at-rule',
name: 'media',
},
'rules',
],
{ severity: 'warning' },
],
},
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
};
import { defineConfig } from 'vite';
const path = require('path');
import vue from '@vitejs/plugin-vue';
import legacy from '@vitejs/plugin-legacy';
import { viteMockServe } from 'vite-plugin-mock';
import { setting } from './src/config/setting';
import { svgBuilder } from './src/plugin/svgBuilder';
import OptimizationPersist from 'vite-plugin-optimize-persist';
import PkgConfig from 'vite-plugin-package-config';
import vueI18n from '@intlify/vite-plugin-vue-i18n';
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
import Icons from 'unplugin-icons/vite';
import IconsResolver from 'unplugin-icons/resolver';
const {
base,
publicDir,
outDir,
assetsDir,
sourcemap,
cssCodeSplit,
host,
port,
strictPort,
open,
cors,
brotliSize,
logLevel,
clearScreen,
drop_console,
drop_debugger,
chunkSizeWarningLimit,
} = setting;
const isDev = process.env.NODE_ENV === 'development';
const loadI18n = isDev ? vueI18n({ include: path.resolve(__dirname, './src/locales/**') }) : '';
// https://vitejs.dev/config/
export default defineConfig({
root: process.cwd(),
base,
publicDir,
logLevel,
clearScreen,
plugins: [
vue(),
PkgConfig(),
OptimizationPersist(),
loadI18n,
legacy({
polyfills: ['es.promise.finally', 'es/map', 'es/set'],
modernPolyfills: ['es.promise.finally'],
}),
AutoImport({
// 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
imports: ['vue'],
resolvers: [
ElementPlusResolver(),
// 自动导入图标组件
IconsResolver({
prefix: 'Icon',
}),
],
}),
Components({
resolvers: [
ElementPlusResolver({
importStyle: 'sass',
// directives: true,
// version: "2.1.5",
}),
// 自动注册图标组件
IconsResolver({
enabledCollections: ['ep'],
}),
],
}),
Icons({
autoInstall: true,
}),
viteMockServe({
mockPath: 'mock',
supportTs: false,
localEnabled: isDev,
prodEnabled: !isDev,
injectCode: `
import { setupProdMockServer } from './mockProdServer';
setupProdMockServer();
`,
}),
svgBuilder('./src/icons/svg/'),
],
server: {
host,
port,
cors,
strictPort,
open,
fs: {
strict: false,
},
},
resolve: {
// 设置别名
alias: {
views: path.resolve(__dirname, 'src/views'),
styles: path.resolve(__dirname, 'src/styles'),
'@': path.resolve(__dirname, 'src'),
},
},
css: {
preprocessorOptions: {
// 引入公用的样式
scss: {
additionalData: `@use "@/styles/index.scss" as *; @use "@/styles/element/index.scss" as *;`,
charset: false,
},
},
},
corePlugins: {
preflight: false,
},
build: {
target: 'es2015',
outDir,
assetsDir,
sourcemap,
cssCodeSplit,
brotliSize,
// rollupOptions: {
// output: {
// // chunkFileNames: 'static/js/[name]-[hash].js',
// // entryFileNames: 'static/js/[name]-[hash].js',
// // assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
// },
// },
terserOptions: {
compress: {
keep_infinity: true,
drop_console,
drop_debugger,
},
},
chunkSizeWarningLimit,
},
optimizeDeps: {
// 检测需要预构建的依赖项
include: [],
},
});
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!