Compare commits

...

35 Commits

Author SHA1 Message Date
xiaoxian521
dbe0815ac4 feat: add vxe-table/lib/locale/lang optimizeDeps 2022-02-05 17:36:21 +08:00
xiaoxian521
fd4cad8d4c release: update 2.9.0 2022-02-05 17:34:01 +08:00
xiaoxian521
e33bdb52f3 fix: vite@2.7.0-beta.8 build incompatible template 2022-01-21 18:25:29 +08:00
xiaoxian521
46a48a5650 release: update 2.8.5 2022-01-21 16:46:48 +08:00
xiaoxian521
8fa3448af2 chore: update typescript 2022-01-21 16:33:45 +08:00
xiaoxian521
f236829b0f refactor: use @iconify-icons/ep 2022-01-21 16:10:10 +08:00
xiaoxian521
47dea87275 fix: update lintstagedrc 2022-01-18 17:06:15 +08:00
xiaoxian521
5e9198c2e0 fix: vscode setting 2022-01-18 17:05:42 +08:00
xiaoxian521
c9c8f20cc5 docs: update 2022-01-09 18:44:23 +08:00
xiaoxian521
a19c97f152 fix: add vite-plugin-live-reload 2022-01-09 12:11:50 +08:00
xiaoxian521
5e1e9d3c5f feat: 添加线上环境删console插件vite-plugin-remove-console 2022-01-07 17:57:55 +08:00
xiaoxian521
96153f8a14 feat: 添加WindiCSS支持 2022-01-07 17:20:25 +08:00
xiaoxian521
14adf692ab perf: theme 2022-01-07 14:45:21 +08:00
xiaoxian521
aa845fc3f5 release: update 2.8.0 2022-01-04 19:52:05 +08:00
xiaoxian521
079a455181 perf: router 2022-01-04 17:28:36 +08:00
xiaoxian521
07794d000c chore: update element-plus@1.3.0-beta.1 2021-12-31 14:09:59 +08:00
啝裳
e787f46414 Merge pull request #166 from xiaoxian521/perf/storage
perf: storage
2021-12-30 13:57:30 +08:00
lrl
e61d6109d7 perf: modify showLogo type: from string to Boolean 2021-12-29 22:45:18 +08:00
lrl
34eda14473 perf: storage 2021-12-29 21:51:05 +08:00
xiaoxian521
0eb0b7395e perf: layout style 2021-12-29 13:26:56 +08:00
xiaoxian521
73705eb0e4 perf: 优化国际化 2021-12-29 11:21:59 +08:00
xiaoxian521
d94fb0ef06 docs: update 2021-12-28 15:09:17 +08:00
xiaoxian521
17470ecce2 docs: update 2021-12-28 13:21:16 +08:00
一万
12db6966fb feat: guide page (#164)
* feat: add guide page

* perf: guide page
2021-12-27 10:51:25 +08:00
xiaoxian521
5070568b89 perf: theme 2021-12-24 10:46:39 +08:00
lrl
ef91f113ee fix: 动态标签页标记改为refreshRedirect 2021-12-22 22:49:34 +08:00
xiaoxian521
c875d20e83 perf: layout set 2021-12-22 13:40:59 +08:00
xiaoxian521
c86aae7b8b fix: 首页同一图表组件引入多次,只显示一个 2021-12-22 09:53:59 +08:00
一万
ee30cba471 perf: theme (#157) 2021-12-21 22:06:09 +08:00
xiaoxian521
7bcd8a800a perf: theme 2021-12-21 16:57:35 +08:00
xiaoxian521
903e298951 fix: delete realPath 2021-12-21 10:34:28 +08:00
啝裳
955b76f30a feat: ep theme (#156)
* feat: ep-theme

* perf: ep-theme

Co-authored-by: lrl <742798240@qq.com>
2021-12-20 22:27:47 +08:00
lrl
1b052023b6 perf: multiTags route delete realPath 2021-12-20 22:25:45 +08:00
xiaoxian521
3af52cf6c4 docs: update 2021-12-20 16:10:36 +08:00
xiaoxian521
660b6f4be8 feat: 暗黑模式 2021-12-20 14:19:38 +08:00
103 changed files with 3180 additions and 2378 deletions

View File

@@ -1,8 +1,6 @@
module.exports = {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
"prettier --write--parser json"
],
"{!(package)*.json,.!(browserslist)*rc}": ["prettier --write--parser json"],
"package.json": ["prettier --write"],
"*.vue": ["eslint --fix", "prettier --write", "stylelint --fix"],
"*.{vue,css,scss,postcss,less}": ["stylelint --fix", "prettier --write"],

16
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
"recommendations": [
"johnsoncodehk.vscode-typescript-vue-plugin",
"voorjaar.windicss-intellisense",
"vscode-icons-team.vscode-icons",
"davidanson.vscode-markdownlint",
"stylelint.vscode-stylelint",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"johnsoncodehk.volar",
"lokalise.i18n-ally",
"mikestead.dotenv",
"eamodio.gitlens",
"antfu.iconify"
]
}

24
.vscode/settings.json vendored
View File

@@ -1,12 +1,4 @@
{
// You should install these plugins:
// ESLint
// Prettier - Code formatter
// stylelint
// vscode-icons
// TypeScript Vue Plugin (Volar)
// Vue Language Features (Volar)
"terminal.integrated.rendererType": "dom",
"editor.formatOnType": true,
"editor.formatOnSave": true,
"javascript.updateImportsOnFileMove.enabled": "always",
@@ -24,26 +16,28 @@
"editor.suggestSelection": "first",
"editor.acceptSuggestionOnCommitCharacter": false,
"css.lint.propertyIgnoredDueToDisplay": "ignore",
// Prevent inline styles from being automatically formatted to all lowercase
"editor.quickSuggestions": {
"other": true,
"comments": true,
"strings": true
},
// Automatically fix some syntax errors of ts
"tslint.autoFixOnSave": true,
"files.associations": {
// Specifies the location of snippets in the suggestion widget
"editor.snippetSuggestions": "top"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"cSpell.userWords": ["sourcemap", "vite"],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"volar.tsPlugin": true,
"typescript.tsdk": "node_modules/typescript/lib",
"i18n-ally.localesPaths": ["src/plugins/i18n"]
"i18n-ally.localesPaths": ["src/plugins/i18n"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"]
}

17
.vscode/vue3.2.code-snippets vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"Vue3.2+快速生成模板": {
"prefix": "Vue3.2+",
"body": [
"<script setup lang='ts'>",
"</script>\n",
"<template>",
"\t<div>\n",
"\t</div>",
"</template>\n",
"<style scoped>\n",
"</style>",
"$2"
],
"description": "Vue3.2+"
}
}

View File

@@ -1,20 +0,0 @@
{
"Vue3.2快速生成模板": {
"prefix": "Vue3.2",
"body": [
"<!-- $1 -->",
"<script setup lang='ts'>",
"\t$2",
"</script>\n",
"<template>",
"\t<div>",
"\t\t$3",
"\t</div>",
"</template>\n",
"<style scoped>",
"\t$4",
"</style>"
],
"description": "Vue3.2"
}
}

View File

@@ -1,3 +1,39 @@
# 2.9.0 (2022-2-5)
### 🎫 Feat
- Added package size analysis, command `pnpm report`
### 🍏 Perf
- Use `iconify` to introduce icons on demand, optimize icon size, and reduce network requests
- Optimize the route, the route can not pass `showLink: true`, it is displayed by default
# 2.8.5 (2022-1-21)
### 🎫 Feat
- Added `WindiCSS` support
- Add online environment remove console plugin `vite-plugin-remove-console`
### ✔️ refactor
- Replace `@element-plus/icons-vue` with `@iconify-icons/ep`
# 2.8.0(2022-1-4)
### 🎫 Feat
-Added dark theme
-Add element-plus custom theme
-Add guide page
### 🍏 Perf
-Optimize internationalization, compatible with the vscode plug-in i18n Ally smart reminder
-Optimize the back-end return routing structure
-Optimize local storage, with four built-in buttons `responsive-configure`, `responsive-locale`, `responsive-layout`, `responsive-tags`, which are basic configuration, international configuration, layout configuration, and tab persistent configuration
# 2.7.0(2021-12-18)
### 🎫 Feat

View File

@@ -1,3 +1,39 @@
# 2.9.0 (2022-2-5)
### 🎫 Feat
- Added package size analysis, command `pnpm report`
### 🍏 Perf
- Use `iconify` to introduce icons on demand, optimize icon size, and reduce network requests
- Optimize the route, the route can not pass `showLink: true`, it is displayed by default
# 2.8.5 (2022-1-21)
### 🎫 Feat
- Added `WindiCSS` support
- Add online environment remove console plugin `vite-plugin-remove-console`
### ✔️ refactor
- Replace `@element-plus/icons-vue` with `@iconify-icons/ep`
# 2.8.0(2022-1-4)
### 🎫 Feat
-Added dark theme
-Add element-plus custom theme
-Add guide page
### 🍏 Perf
-Optimize internationalization, compatible with the vscode plug-in i18n Ally smart reminder
-Optimize the back-end return routing structure
-Optimize local storage, with four built-in buttons `responsive-configure`, `responsive-locale`, `responsive-layout`, `responsive-tags`, which are basic configuration, international configuration, layout configuration, and tab persistent configuration
# 2.7.0(2021-12-18)
### 🎫 Feat

View File

@@ -1,3 +1,39 @@
# 2.9.0(2022-2-5)
### 🎫 Feat
- 添加打包大小分析,命令`pnpm report`
### 🍏 Perf
- 采用`iconify`按需引入图标,优化图标大小,减少网络请求
- 优化路由,路由可不传`showLink: true`,默认显示
# 2.8.5(2022-1-21)
### 🎫 Feat
- 添加 `WindiCSS` 支持
- 添加线上环境删 console 插件`vite-plugin-remove-console`
### ✔️ refactor
- 使用`@iconify-icons/ep`替换`@element-plus/icons-vue`
# 2.8.0(2022-1-4)
### 🎫 Feat
- 添加暗黑主题
- 添加 element-plus 自定义主题
- 添加引导页
### 🍏 Perf
- 优化国际化,兼容 vscode 插件 i18n Ally 智能提醒
- 优化后端返回路由结构
- 优化本地存储,内置四个键`responsive-configure``responsive-locale``responsive-layout``responsive-tags`,分别为基本配置、国际化配置、布局配置、标签页持久化配置
# 2.7.0(2021-12-18)
### 🎫 Feat

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 啝裳
Copyright (c) 2022 啝裳
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,6 +1,8 @@
<h1>vue-pure-admin</h1>
[![license](https://img.shields.io/github/license/xiaoxian521/vue-pure-admin.svg)](LICENSE)
![GitHub license](https://img.shields.io/github/license/xiaoxian521/vue-pure-admin?style=flat)
![GitHub stars](https://img.shields.io/github/stars/xiaoxian521/vue-pure-admin?color=fa6470&style=flat)
![GitHub forks](https://img.shields.io/github/forks/xiaoxian521/vue-pure-admin?style=flat)
**English** | [中文](./README.md)
@@ -10,16 +12,16 @@ vue-pure-admin is a free and open source middle and back-end template. Using the
## Supporting Video
Tutorial: <https://www.bilibili.com/video/BV1534y1S7HV/>
UI Design: <https://www.bilibili.com/video/BV17g411T7rq/>
- [Click Watch Tutorial](https://www.bilibili.com/video/BV1534y1S7HV)
- [Click Watch UI Design](https://www.bilibili.com/video/BV17g411T7rq)
## Docs
<https://pure-admin-doc.vercel.app/>
- [Click Watch Docs](https://pure-admin-doc.vercel.app)
## Thin
Github Address: <https://github.com/xiaoxian521/pure-admin-thin>
- [Click Watch Thin](https://github.com/xiaoxian521/pure-admin-thin)
## Preview
@@ -28,6 +30,7 @@ Github Address: <https://github.com/xiaoxian521/pure-admin-thin>
<p align="center">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4857fc7eb7d4c0f8deeefc644c1f7dd~tplv-k3u1fbpfcp-watermark.awebp?">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/549c3184697f4d268a78c9833e5ec2ea~tplv-k3u1fbpfcp-watermark.awebp?">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/381fc957fac143db9f06efdd389d88a3~tplv-k3u1fbpfcp-watermark.awebp?">
</p>
### Use Gitpod
@@ -117,9 +120,15 @@ Support modern browsers, not IE
## Donate
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support!
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support
<img src="http://yiming_chang.gitee.io/manages/pay.png" width="360px" height="480px" />
<img src="http://yiming_chang.gitee.io/manages/pay.jpg" width="150px" height="150px" />
## WeChat Exchange Group
For the better development of the project, you can choose to donate 10 yuan and add the following WeChat to pull you into the group. After adding, please consciously send a screenshot of the donation
<img src="http://yiming_chang.gitee.io/manages/kf.jpg" width="150px" height="195px" />
## License

View File

@@ -1,6 +1,8 @@
<h1>vue-pure-admin</h1>
[![license](https://img.shields.io/github/license/xiaoxian521/vue-pure-admin.svg)](LICENSE)
![GitHub license](https://img.shields.io/github/license/xiaoxian521/vue-pure-admin?style=flat)
![GitHub stars](https://img.shields.io/github/stars/xiaoxian521/vue-pure-admin?color=fa6470&style=flat)
![GitHub forks](https://img.shields.io/github/forks/xiaoxian521/vue-pure-admin?style=flat)
**中文** | [English](./README.en-US.md)
@@ -10,16 +12,16 @@ vue-pure-admin 是一个免费开源的中后台模版。使用了最新的`vue3
## 配套视频
教程:<https://www.bilibili.com/video/BV1534y1S7HV/>
UI 设计:<https://www.bilibili.com/video/BV17g411T7rq/>
- [点我查看教程](https://www.bilibili.com/video/BV1534y1S7HV)
- [点我查看 UI 设计](https://www.bilibili.com/video/BV17g411T7rq)
## 配套文档
<https://pure-admin-doc.vercel.app/>
- [点我查看文档](https://pure-admin-doc.vercel.app)
## 精简版
仓库地址:<https://github.com/xiaoxian521/pure-admin-thin>
- [点我查看精简版](https://github.com/xiaoxian521/pure-admin-thin)
## 预览
@@ -28,6 +30,7 @@ UI 设计:<https://www.bilibili.com/video/BV17g411T7rq/>
<p align="center">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4857fc7eb7d4c0f8deeefc644c1f7dd~tplv-k3u1fbpfcp-watermark.awebp?">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/549c3184697f4d268a78c9833e5ec2ea~tplv-k3u1fbpfcp-watermark.awebp?">
<img alt="PureAdmin Logo" width="100%" src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/381fc957fac143db9f06efdd389d88a3~tplv-k3u1fbpfcp-watermark.awebp?">
</p>
### 使用 Gitpod
@@ -121,11 +124,11 @@ pnpm build
<img src="http://yiming_chang.gitee.io/manages/pay.jpg" width="150px" height="150px" />
## 付费咨询、需求定制
## 微信交流群
作者精力有限,需要提供技术服务的可扫下面的二维码加微信,添加请备注来意
为了项目更好的发展,你可选择捐赠 10 元后添加下图微信拉你进群,添加后请自觉发捐赠截图
<img src="http://yiming_chang.gitee.io/manages/wechat.jpg" width="150px" height="150px" />
<img src="http://yiming_chang.gitee.io/manages/kf.jpg" width="150px" height="195px" />
## 许可证

View File

@@ -1,6 +0,0 @@
const productPlugins = [];
process.env.NODE_ENV === "production" &&
productPlugins.push("transform-remove-console");
module.exports = {
plugins: [...productPlugins]
};

121
build/plugins.ts Normal file
View File

@@ -0,0 +1,121 @@
import vue from "@vitejs/plugin-vue";
import svgLoader from "vite-svg-loader";
import legacy from "@vitejs/plugin-legacy";
import vueJsx from "@vitejs/plugin-vue-jsx";
import WindiCSS from "vite-plugin-windicss";
import { viteMockServe } from "vite-plugin-mock";
import liveReload from "vite-plugin-live-reload";
import styleImport from "vite-plugin-style-import";
import ElementPlus from "unplugin-element-plus/vite";
import { visualizer } from "rollup-plugin-visualizer";
import removeConsole from "vite-plugin-remove-console";
import themePreprocessorPlugin from "@zougt/vite-plugin-theme-preprocessor";
export function getPluginsList(command, VITE_LEGACY) {
const prodMock = true;
const lifecycle = process.env.npm_lifecycle_event;
return [
vue(),
// jsx、tsx语法支持
vueJsx(),
WindiCSS(),
// 线上环境删除console
removeConsole(),
// 修改layout文件夹下的文件时自动重载浏览器 解决 https://github.com/xiaoxian521/vue-pure-admin/issues/170
liveReload(["src/layout/**/*", "src/router/**/*"]),
// 自定义主题
themePreprocessorPlugin({
scss: {
multipleScopeVars: [
{
scopeName: "layout-theme-default",
path: "src/layout/theme/default-vars.scss"
},
{
scopeName: "layout-theme-light",
path: "src/layout/theme/light-vars.scss"
},
{
scopeName: "layout-theme-dusk",
path: "src/layout/theme/dusk-vars.scss"
},
{
scopeName: "layout-theme-volcano",
path: "src/layout/theme/volcano-vars.scss"
},
{
scopeName: "layout-theme-yellow",
path: "src/layout/theme/yellow-vars.scss"
},
{
scopeName: "layout-theme-mingQing",
path: "src/layout/theme/mingQing-vars.scss"
},
{
scopeName: "layout-theme-auroraGreen",
path: "src/layout/theme/auroraGreen-vars.scss"
},
{
scopeName: "layout-theme-pink",
path: "src/layout/theme/pink-vars.scss"
},
{
scopeName: "layout-theme-saucePurple",
path: "src/layout/theme/saucePurple-vars.scss"
}
],
// 默认取 multipleScopeVars[0].scopeName
defaultScopeName: "",
// 在生产模式是否抽取独立的主题css文件extract为true以下属性有效
extract: true,
// 独立主题css文件的输出路径默认取 viteConfig.build.assetsDir 相对于 (viteConfig.build.outDir)
outputDir: "",
// 会选取defaultScopeName对应的主题css文件在html添加link
themeLinkTagId: "head",
// "head"||"head-prepend" || "body" ||"body-prepend"
themeLinkTagInjectTo: "head",
// 是否对抽取的css文件内对应scopeName的权重类名移除
removeCssScopeName: false,
// 可以自定义css文件名称的函数
customThemeCssFileName: scopeName => scopeName
}
}),
// svg组件化支持
svgLoader(),
// 按需加载vxe-table
styleImport({
libs: [
{
libraryName: "vxe-table",
esModule: true,
ensureStyleFile: true,
resolveComponent: name => `vxe-table/es/${name}`,
resolveStyle: name => `vxe-table/es/${name}/style.css`
}
]
}),
ElementPlus({}),
// mock支持
viteMockServe({
mockPath: "mock",
localEnabled: command === "serve",
prodEnabled: command !== "serve" && prodMock,
injectCode: `
import { setupProdMockServer } from './mockProdServer';
setupProdMockServer();
`,
logger: true
}),
// 是否为打包后的文件提供传统浏览器兼容性支持
VITE_LEGACY
? legacy({
targets: ["ie >= 11"],
additionalLegacyPolyfills: ["regenerator-runtime/runtime"]
})
: null,
// 打包分析
lifecycle === "report"
? visualizer({ open: true, brotliSize: true, filename: "report.html" })
: null
];
}

View File

@@ -3,7 +3,6 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/iconfont.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vue-pure-admin</title>
<script src="/sortable.min.js"></script>
@@ -27,94 +26,78 @@
display: flex;
justify-content: center;
align-items: center;
background: #000;
overflow: hidden;
font-family: "Reggae One", cursive;
}
p {
font-size: 8vw;
overflow: hidden;
-webkit-text-stroke: 3px #7272a5;
.loader,
.loader:before,
.loader:after {
border-radius: 50%;
width: 2.5em;
height: 2.5em;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
-webkit-animation: loadAnimation 1.8s infinite ease-in-out;
animation: loadAnimation 1.8s infinite ease-in-out;
}
span {
display: block;
font-size: 20px;
overflow: hidden;
color: green;
text-align: center;
.loader {
color: #406eeb;
font-size: 10px;
margin: 80px auto;
position: relative;
text-indent: -9999em;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
p::before {
content: " ";
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-image: linear-gradient(45deg, #ff269b, #2ab5f5, #ffbf00);
mix-blend-mode: multiply;
}
p::after {
.loader:before,
.loader:after {
content: "";
background: radial-gradient(circle, #fff, #000 50%);
background-size: 25% 25%;
position: absolute;
top: -100%;
left: -100%;
right: 0;
bottom: 0;
mix-blend-mode: color-dodge;
animation: mix 2s linear infinite;
top: 0;
}
@keyframes mix {
to {
transform: translate(50%, 50%);
.loader:before {
left: -3.5em;
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.loader:after {
left: 3.5em;
}
@-webkit-keyframes loadAnimation {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em;
}
40% {
box-shadow: 0 2.5em 0 0;
}
}
@keyframes loadAnimation {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em;
}
40% {
box-shadow: 0 2.5em 0 0;
}
}
</style>
<div class="g-container">
<p>Pure-Admin</p>
<span class="_develop"></span>
</div>
<div class="loader">Loading...</div>
</div>
<script>
// 此代码仅用于开发环境的友好提示项目打包前请去掉这段js代码 This code is only used as a friendly reminder of the development environment, please remove this js code before packaging the project
window.onload = function () {
(function () {
const ua = navigator.userAgent.toLowerCase();
const re = /(msie|firefox|chrome|opera|version).*?([\d.]+)/;
const m = ua.match(re);
const Sys = {
browser: m[1].replace(/version/, "'safari"),
version: m[2]
};
const browser = Array.of("chrome", "firefox").includes(Sys.browser);
const version = parseFloat(Sys.version);
const el = document.querySelector("._develop");
if (el) {
if (browser && version >= 90) {
let success =
document.createTextNode("当前浏览器版本很适合开发!!! 😃");
el.appendChild(success);
} else {
let warn = document.createTextNode(
"当前浏览器版本不适合开发,建议使用最新版本的谷歌或者火狐浏览器!!!😯"
);
el.appendChild(warn);
el.style.color = "red";
}
}
return Sys;
})();
};
</script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -5,31 +5,28 @@ import { MockMethod } from "vite-plugin-mock";
const systemRouter = {
path: "/system",
name: "system",
redirect: "/system/user",
redirect: "/system/user/index",
meta: {
icon: "Setting",
title: "message.hssysManagement",
icon: "setting",
title: "menus.hssysManagement",
i18n: true,
showLink: true,
rank: 6
},
children: [
{
path: "/system/user",
path: "/system/user/index",
name: "user",
meta: {
title: "message.hsBaseinfo",
i18n: true,
showLink: true
title: "menus.hsBaseinfo",
i18n: true
}
},
{
path: "/system/dict",
path: "/system/dict/index",
name: "dict",
meta: {
title: "message.hsDict",
title: "menus.hsDict",
i18n: true,
showLink: true,
keepAlive: true
}
}
@@ -39,31 +36,28 @@ const systemRouter = {
const permissionRouter = {
path: "/permission",
name: "permission",
redirect: "/permission/page",
redirect: "/permission/page/index",
meta: {
title: "message.permission",
icon: "Lollipop",
title: "menus.permission",
icon: "lollipop",
i18n: true,
showLink: true,
rank: 3
},
children: [
{
path: "/permission/page",
path: "/permission/page/index",
name: "permissionPage",
meta: {
title: "message.permissionPage",
i18n: true,
showLink: true
title: "menus.permissionPage",
i18n: true
}
},
{
path: "/permission/button",
path: "/permission/button/index",
name: "permissionButton",
meta: {
title: "message.permissionButton",
title: "menus.permissionButton",
i18n: true,
showLink: true,
authority: []
}
}
@@ -76,9 +70,8 @@ const tabsRouter = {
redirect: "/tabs/index",
meta: {
icon: "IF-team-icontabs",
title: "message.hstabs",
title: "menus.hstabs",
i18n: true,
showLink: true,
rank: 8
},
children: [
@@ -86,8 +79,7 @@ const tabsRouter = {
path: "/tabs/index",
name: "reTabs",
meta: {
title: "message.hstabs",
showLink: true,
title: "menus.hstabs",
i18n: true
}
},
@@ -99,7 +91,6 @@ const tabsRouter = {
showLink: false,
i18n: false,
dynamicLevel: 3,
realPath: "/tabs/detail",
refreshRedirect: "/tabs/index"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "vue-pure-admin",
"version": "2.7.0",
"version": "2.9.0",
"private": true,
"engines": {
"node": ">= 16",
@@ -10,6 +10,7 @@
"dev": "cross-env --max_old_space_size=4096 vite",
"serve": "pnpm dev",
"build": "rimraf dist && cross-env vite build",
"report": "rimraf dist && cross-env vite build",
"preview": "vite preview",
"preview:build": "pnpm build && vite preview",
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install",
@@ -18,7 +19,7 @@
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,css,scss,postcss,less}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
"lint:pretty": "pretty-quick --staged",
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint && pnpm lint:pretty",
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
"prepare": "husky install",
"preinstall": "npx only-allow pnpm"
},
@@ -29,54 +30,52 @@
],
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@element-plus/icons-vue": "^0.2.4",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^3.0.0-5",
"@ctrl/tinycolor": "^3.4.0",
"@logicflow/core": "0.7.1",
"@logicflow/extension": "0.7.1",
"@vueuse/core": "^6.7.1",
"@vueuse/motion": "^2.0.0-beta.4",
"@vueuse/core": "^7.5.5",
"@vueuse/motion": "^2.0.0-beta.9",
"@vueuse/shared": "^7.5.5",
"animate.css": "^4.1.1",
"await-to-js": "^3.0.0",
"axios": "^0.21.1",
"axios": "^0.25.0",
"cropperjs": "^1.5.11",
"css-color-function": "^1.3.3",
"dayjs": "^1.10.7",
"driver.js": "^0.9.8",
"echarts": "^5.2.1",
"element-plus": "1.2.0-beta.6",
"element-plus": "1.3.0-beta.1",
"element-resize-detector": "^1.2.3",
"font-awesome": "^4.7.0",
"js-cookie": "^3.0.1",
"lodash-es": "^4.17.21",
"lowdb": "^3.0.0",
"mitt": "^3.0.0",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"path": "^0.12.7",
"path-to-regexp": "^6.2.0",
"pinia": "^2.0.0-rc.14",
"pinia": "^2.0.11",
"qs": "^6.10.1",
"remixicon": "^2.5.0",
"resize-observer-polyfill": "^1.5.1",
"responsive-storage": "^1.0.11",
"sortablejs": "1.13.0",
"typescript-cookie": "^1.0.0",
"rgb-hex": "^4.0.0",
"v-contextmenu": "3.0.0",
"vue": "^3.2.24",
"vue-i18n": "^9.2.0-beta.3",
"vue": "^3.2.29",
"vue-i18n": "^9.2.0-beta.30",
"vue-json-pretty": "^2.0.2",
"vue-router": "^4.0.12",
"vue-types": "^4.1.0",
"vue-types": "^4.1.1",
"vuedraggable": "4.1.0",
"vxe-table": "4.0.30",
"vxe-table": "^4.1.18",
"wangeditor": "^4.7.9",
"xe-ajax": "4.0.5",
"xe-utils": "3.4.0",
"xe-utils": "^3.5.2",
"xgplayer": "2.28.0"
},
"devDependencies": {
"@commitlint/cli": "13.1.0",
"@commitlint/config-conventional": "13.1.0",
"@iconify-icons/ep": "^1.1.3",
"@iconify-icons/fa": "^1.1.1",
"@iconify-icons/fa-solid": "^1.1.2",
"@iconify-icons/ri": "^1.1.1",
"@iconify/vue": "^3.1.3",
"@types/element-resize-detector": "1.1.3",
"@types/js-cookie": "^3.0.1",
"@types/mockjs": "1.0.3",
@@ -86,15 +85,12 @@
"@typescript-eslint/eslint-plugin": "4.31.0",
"@typescript-eslint/parser": "4.31.0",
"@vitejs/plugin-legacy": "^1.6.4",
"@vitejs/plugin-vue": "^1.10.2",
"@vitejs/plugin-vue-jsx": "^1.3.1",
"@vue/compiler-sfc": "^3.2.24",
"@vitejs/plugin-vue": "^2.1.0",
"@vitejs/plugin-vue-jsx": "^1.3.3",
"@vue/eslint-config-prettier": "6.0.0",
"@vue/eslint-config-typescript": "7.0.0",
"@zougt/vite-plugin-theme-preprocessor": "^1.4.0",
"autoprefixer": "10.2.4",
"babel-plugin-transform-remove-console": "6.9.4",
"chalk": "2.4.2",
"@zougt/vite-plugin-theme-preprocessor": "^1.4.4",
"autoprefixer": "^10.4.2",
"cross-env": "7.0.3",
"eslint": "7.30.0",
"eslint-plugin-prettier": "3.4.0",
@@ -106,19 +102,24 @@
"prettier": "2.3.2",
"pretty-quick": "3.1.1",
"rimraf": "3.0.2",
"sass": "^1.45.0",
"sass-loader": "^12.3.0",
"rollup-plugin-visualizer": "^5.5.4",
"sass": "^1.49.7",
"sass-loader": "^12.4.0",
"stylelint": "13.13.1",
"stylelint-config-prettier": "8.0.2",
"stylelint-config-standard": "22.0.0",
"stylelint-order": "4.1.0",
"typescript": "4.4.2",
"unplugin-element-plus": "^0.1.3",
"vite": "2.6.14",
"typescript": "^4.5.5",
"unplugin-element-plus": "^0.2.0",
"vite": "^2.7.13",
"vite-plugin-live-reload": "^2.1.0",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-style-import": "^1.2.1",
"vite-svg-loader": "^2.2.0",
"vue-eslint-parser": "7.10.0"
"vite-plugin-remove-console": "^0.0.6",
"vite-plugin-style-import": "^1.4.1",
"vite-plugin-windicss": "^1.6.1",
"vite-svg-loader": "2.2.0",
"vue-eslint-parser": "7.10.0",
"windicss": "^3.4.3"
},
"repository": "git@github.com:xiaoxian521/vue-pure-admin.git",
"author": "xiaoxian521",

2299
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
@font-face {
font-family: "iconfont"; /* project id 1098500 */
src: url("//at.alicdn.com/t/font_1098500_3d6un9zwltz.eot");
src: url("//at.alicdn.com/t/font_1098500_3d6un9zwltz.eot?#iefix")
format("embedded-opentype"),
url("//at.alicdn.com/t/font_1098500_3d6un9zwltz.woff2") format("woff2"),
url("//at.alicdn.com/t/font_1098500_3d6un9zwltz.woff") format("woff"),
url("//at.alicdn.com/t/font_1098500_3d6un9zwltz.ttf") format("truetype"),
url("//at.alicdn.com/t/font_1098500_3d6un9zwltz.svg#iconfont") format("svg");
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View File

@@ -1,5 +1,5 @@
{
"Version": "2.7.0",
"Version": "2.9.0",
"Title": "PureAdmin",
"FixedHeader": true,
"HiddenSideBar": false,
@@ -8,9 +8,14 @@
"Locale": "zh",
"Layout": "vertical",
"Theme": "default",
"DarkMode": false,
"Grey": false,
"Weak": false,
"HideTabs": false,
"SidebarStatus": true,
"EpThemeColor": "#409EFF",
"ShowLogo": true,
"ShowModel": "smart",
"MapConfigure": {
"amapKey": "97b3248d1553172e81f168cf94ea667e",
"options": {

View File

@@ -5,10 +5,11 @@
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { ElConfigProvider } from "element-plus";
import zhCn from "element-plus/lib/locale/lang/zh-cn";
import en from "element-plus/lib/locale/lang/en";
export default {
export default defineComponent({
name: "app",
components: {
[ElConfigProvider.name]: ElConfigProvider
@@ -18,5 +19,5 @@ export default {
return this.$storage.locale?.locale === "zh" ? zhCn : en;
}
}
};
});
</script>

1
src/assets/svg/dark.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.38 2.019a7.5 7.5 0 1 0 10.6 10.6C21.662 17.854 17.316 22 12.001 22 6.477 22 2 17.523 2 12c0-5.315 4.146-9.661 9.38-9.981z"/></svg>

After

Width:  |  Height:  |  Size: 263 B

1
src/assets/svg/day.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></svg>

After

Width:  |  Height:  |  Size: 480 B

View File

@@ -12,8 +12,15 @@ import { useEventListener, tryOnUnmounted, useTimeoutFn } from "@vueuse/core";
let echartInstance: ECharts;
const props = defineProps({
index: {
type: Number,
default: 0
}
});
function initechartInstance() {
const echartDom = document.querySelector(".bar");
const echartDom = document.querySelector(".bar" + props.index);
if (!echartDom) return;
// @ts-ignore
echartInstance = echarts.init(echartDom);
@@ -85,12 +92,5 @@ tryOnUnmounted(() => {
</script>
<template>
<div class="bar"></div>
<div :class="'bar' + props.index" style="width: 100%; height: 35vh"></div>
</template>
<style scoped>
.bar {
width: 100%;
height: 35vh;
}
</style>

View File

@@ -10,52 +10,48 @@ const lists = ref([
</script>
<template>
<el-descriptions
class="margin-top"
direction="vertical"
:column="3"
size="medium"
border
>
<el-descriptions class="margin-top" direction="vertical" :column="3" border>
<el-descriptions-item>
<template #label>
<i class="el-icon-user"></i>
<el-icon>
<IconifyIconOffline icon="user" />
</el-icon>
用户名
</template>
xiaoxian
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<i class="el-icon-mobile-phone"></i>
<el-icon>
<IconifyIconOffline icon="iphone" />
</el-icon>
手机号
</template>
123456789
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<i class="el-icon-location-outline"></i>
<el-icon>
<IconifyIconOffline icon="location" />
</el-icon>
居住地
</template>
上海
</el-descriptions-item>
</el-descriptions>
<el-descriptions
class="margin-top"
direction="vertical"
:column="2"
size="medium"
border
>
<el-descriptions class="margin-top" direction="vertical" :column="2" border>
<el-descriptions-item>
<template #label>
<i class="el-icon-tickets"></i>
<el-icon>
<IconifyIconOffline icon="tickets" />
</el-icon>
标签
</template>
<el-tag
v-for="item in lists"
:key="item.label"
:type="item.type"
size="mini"
size="small"
effect="dark"
>
{{ item.label }}
@@ -63,22 +59,20 @@ const lists = ref([
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<i class="el-icon-office-building"></i>
<el-icon>
<IconifyIconOffline icon="office-building" />
</el-icon>
联系地址
</template>
上海市徐汇区
</el-descriptions-item>
</el-descriptions>
<el-descriptions
class="margin-top"
direction="vertical"
:column="1"
size="medium"
border
>
<el-descriptions class="margin-top" direction="vertical" :column="1" border>
<el-descriptions-item>
<template #label>
<i class="el-icon-notebook-1"></i>
<el-icon>
<IconifyIconOffline icon="notebook" />
</el-icon>
留言
</template>
好好学习天天向上
@@ -87,7 +81,7 @@ const lists = ref([
</template>
<style scoped>
.el-tag--mini {
.el-tag {
margin-right: 10px !important;
}
</style>

View File

@@ -12,8 +12,15 @@ import { useEventListener, tryOnUnmounted, useTimeoutFn } from "@vueuse/core";
let echartInstance: ECharts;
const props = defineProps({
index: {
type: Number,
default: 0
}
});
function initechartInstance() {
const echartDom = document.querySelector(".line");
const echartDom = document.querySelector(".line" + props.index);
if (!echartDom) return;
// @ts-ignore
echartInstance = echarts.init(echartDom);
@@ -73,12 +80,5 @@ tryOnUnmounted(() => {
</script>
<template>
<div class="line"></div>
<div :class="'line' + props.index" style="width: 100%; height: 35vh"></div>
</template>
<style scoped>
.line {
width: 100%;
height: 35vh;
}
</style>

View File

@@ -12,8 +12,15 @@ import { useEventListener, tryOnUnmounted, useTimeoutFn } from "@vueuse/core";
let echartInstance: ECharts;
const props = defineProps({
index: {
type: Number,
default: 0
}
});
function initechartInstance() {
const echartDom = document.querySelector(".pie");
const echartDom = document.querySelector(".pie" + props.index);
if (!echartDom) return;
// @ts-ignore
echartInstance = echarts.init(echartDom);
@@ -76,12 +83,5 @@ tryOnUnmounted(() => {
</script>
<template>
<div class="pie"></div>
<div :class="'pie' + props.index" style="width: 100%; height: 35vh"></div>
</template>
<style scoped>
.pie {
width: 100%;
height: 35vh;
}
</style>

View File

@@ -1,86 +1,13 @@
import { App, defineComponent } from "vue";
import icon from "./src/Icon.vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { iconComponents } from "/@/plugins/element-plus";
import iconifyIconOffline from "./src/iconifyIconOffline";
import iconifyIconOnline from "./src/iconifyIconOnline";
import fontIcon from "./src/iconfont";
/**
* find icon component
* @param icon icon图标
* @returns component
*/
export function findIconReg(icon: string) {
// fontawesome
const faReg = /^FA-/;
// iconfont
const iFReg = /^IF-/;
// remixicon
const riReg = /^RI-/;
// typeof icon === "function" 属于SVG
if (faReg.test(icon)) {
const text = icon.split(faReg)[1];
return findIcon(
text.slice(0, text.indexOf(" ")),
"FA",
text.slice(text.indexOf(" ") + 1, text.length)
);
} else if (iFReg.test(icon)) {
return findIcon(icon.split(iFReg)[1], "IF");
} else if (typeof icon === "function") {
return findIcon(icon, "SVG");
} else if (riReg.test(icon)) {
return findIcon(icon.split(riReg)[1], "RI");
} else {
return findIcon(icon, "EL");
}
}
// 支持fontawesome、iconfont、remixicon、element-plus/icons、自定义svg
export function findIcon(icon: String, type = "EL", property?: string) {
if (type === "FA") {
return defineComponent({
name: "FaIcon",
setup() {
return { icon, property };
},
components: { FontAwesomeIcon },
template: `<font-awesome-icon :icon="icon" v-bind:[property]="true" />`
});
} else if (type === "IF") {
return defineComponent({
name: "IfIcon",
data() {
return { icon: `iconfont ${icon}` };
},
template: `<i :class="icon" />`
});
} else if (type === "RI") {
return defineComponent({
name: "RIIcon",
data() {
return { icon: `ri-${icon}` };
},
template: `<i :class="icon" />`
});
} else if (type === "EL") {
const components = iconComponents.filter(
component => component.name === icon
);
if (components.length > 0) {
return components[0];
} else {
return null;
}
} else if (type === "SVG") {
return icon;
}
}
export const Icon = Object.assign(icon, {
install(app: App) {
app.component(icon.name, icon);
}
});
export const IconifyIconOffline = iconifyIconOffline;
export const IconifyIconOnline = iconifyIconOnline;
export const FontIcon = fontIcon;
export default {
Icon
IconifyIconOffline,
IconifyIconOnline,
FontIcon
};

View File

@@ -1,97 +0,0 @@
<script lang="ts">
export default {
name: "Icon"
};
</script>
<script setup lang="ts">
import { ref, computed } from "vue";
const props = defineProps({
content: {
type: String,
default: ""
},
size: {
type: Number,
default: 18
},
width: {
type: Number,
default: 20
},
height: {
type: Number,
default: 20
},
color: {
type: String,
default: ""
},
svg: {
type: Boolean,
default: false
}
});
const emit = defineEmits<{
(e: "click"): void;
}>();
let text = ref("");
let className = computed(() => {
if (props.content.indexOf("fa-") > -1) {
return props.content.indexOf("fa ") === 0
? props.content
: ["fa", props.content];
} else if (props.content.indexOf("el-icon-") > -1) {
return props.content;
} else if (props.content.indexOf("#") > -1) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
text.value = props.content;
return "iconfont";
} else {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
text.value = props.content;
return "";
}
});
let iconStyle = computed(() => {
return (
"font-size: " +
props.size +
"px; color: " +
props.color +
"; width: " +
props.width +
"px; height: " +
props.height +
"px; font-style: normal;"
);
});
const clickHandle = () => {
emit("click");
};
</script>
<template>
<i
v-if="!props.svg"
:class="className"
:style="iconStyle"
v-html="text"
@click="clickHandle"
></i>
<svg
class="icon-svg"
v-if="props.svg"
aria-hidden="true"
:style="iconStyle"
@click="clickHandle"
>
<use :xlink:href="`#${props.content}`" />
</svg>
</template>

View File

@@ -0,0 +1,39 @@
import { h, defineComponent, Component } from "vue";
import { IconifyIconOffline, FontIcon } from "../index";
// 支持fontawesome4、5+、iconfont、remixicon、element-plus的icons、自定义svg
export function useRenderIcon(icon: string): Component {
// iconfont
const ifReg = /^IF-/;
// typeof icon === "function" 属于SVG
if (ifReg.test(icon)) {
// iconfont
const name = icon.split(ifReg)[1];
const iconName = name.slice(
0,
name.indexOf(" ") == -1 ? name.length : name.indexOf(" ")
);
const iconType = name.slice(name.indexOf(" ") + 1, name.length);
return defineComponent({
name: "FontIcon",
render() {
return h(FontIcon, {
icon: iconName,
iconType
});
}
});
} else if (typeof icon === "function") {
// svg
return icon;
} else {
return defineComponent({
name: "Icon",
render() {
return h(IconifyIconOffline, {
icon: icon
});
}
});
}
}

View File

@@ -0,0 +1,48 @@
import { h, defineComponent } from "vue";
// 封装iconfont组件默认`font-class`引用模式,支持`unicode`引用、`font-class`引用、`symbol`引用 https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.20&helptype=code
export default defineComponent({
name: "fontIcon",
props: {
icon: {
type: String,
default: ""
}
},
render() {
const attrs = this.$attrs;
if (Object.keys(attrs).includes("uni") || attrs?.iconType === "uni") {
return h(
"i",
{
class: "iconfont",
...attrs
},
this.icon
);
} else if (
Object.keys(attrs).includes("svg") ||
attrs?.iconType === "svg"
) {
return h(
"svg",
{
class: "icon-svg",
"aria-hidden": true
},
{
default: () => [
h("use", {
"xlink:href": `#${this.icon}`
})
]
}
);
} else {
return h("i", {
class: `iconfont ${this.icon}`,
...attrs
});
}
}
});

View File

@@ -0,0 +1,91 @@
import { h, defineComponent } from "vue";
import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline";
// element-plus icon
import Check from "@iconify-icons/ep/check";
import Menu from "@iconify-icons/ep/menu";
import HomeFilled from "@iconify-icons/ep/home-filled";
import SetUp from "@iconify-icons/ep/set-up";
import Edit from "@iconify-icons/ep/edit";
import Setting from "@iconify-icons/ep/setting";
import Lollipop from "@iconify-icons/ep/lollipop";
import Link from "@iconify-icons/ep/link";
import Position from "@iconify-icons/ep/position";
import Histogram from "@iconify-icons/ep/histogram";
import RefreshRight from "@iconify-icons/ep/refresh-right";
import ArrowDown from "@iconify-icons/ep/arrow-down";
import Close from "@iconify-icons/ep/close";
import CloseBold from "@iconify-icons/ep/close-bold";
import Bell from "@iconify-icons/ep/bell";
import Guide from "@iconify-icons/ep/guide";
import User from "@iconify-icons/ep/user";
import Iphone from "@iconify-icons/ep/iphone";
import Location from "@iconify-icons/ep/location";
import Tickets from "@iconify-icons/ep/tickets";
import OfficeBuilding from "@iconify-icons/ep/office-building";
import Notebook from "@iconify-icons/ep/notebook";
addIcon("check", Check);
addIcon("menu", Menu);
addIcon("home-filled", HomeFilled);
addIcon("set-up", SetUp);
addIcon("edit", Edit);
addIcon("setting", Setting);
addIcon("lollipop", Lollipop);
addIcon("link", Link);
addIcon("position", Position);
addIcon("histogram", Histogram);
addIcon("refresh-right", RefreshRight);
addIcon("arrow-down", ArrowDown);
addIcon("close", Close);
addIcon("close-bold", CloseBold);
addIcon("bell", Bell);
addIcon("guide", Guide);
addIcon("user", User);
addIcon("iphone", Iphone);
addIcon("location", Location);
addIcon("tickets", Tickets);
addIcon("office-building", OfficeBuilding);
addIcon("notebook", Notebook);
// remixicon
import arrowRightSLine from "@iconify-icons/ri/arrow-right-s-line";
import arrowLeftSLine from "@iconify-icons/ri/arrow-left-s-line";
import logoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
import nodeTree from "@iconify-icons/ri/node-tree";
addIcon("arrow-right-s-line", arrowRightSLine);
addIcon("arrow-left-s-line", arrowLeftSLine);
addIcon("logout-circle-r-line", logoutCircleRLine);
addIcon("node-tree", nodeTree);
// Font Awesome 4
import faUser from "@iconify-icons/fa/user";
import faLock from "@iconify-icons/fa/lock";
import faSignOut from "@iconify-icons/fa/sign-out";
addIcon("fa-user", faUser);
addIcon("fa-lock", faLock);
addIcon("fa-sign-out", faSignOut);
// Iconify Icon在Vue里离线使用用于内网环境https://docs.iconify.design/icon-components/vue/offline.html
export default defineComponent({
name: "IconifyIcon",
components: { IconifyIcon },
props: {
icon: {
type: String,
default: ""
}
},
render() {
const attrs = this.$attrs;
return h(
IconifyIcon,
{
icon: `${this.icon}`,
...attrs
},
{
default: () => []
}
);
}
});

View File

@@ -0,0 +1,32 @@
import { h, defineComponent } from "vue";
import { Icon as IconifyIcon } from "@iconify/vue";
// Iconify Icon在Vue里在线使用用于外网环境 https://docs.iconify.design/icon-components/vue/offline.html
export default defineComponent({
name: "IconifyIcon",
components: { IconifyIcon },
props: {
icon: {
type: String,
default: ""
},
// default element plus icon
type: {
type: String,
default: "ep:"
}
},
render() {
const attrs = this.$attrs;
return h(
IconifyIcon,
{
icon: `${this.type}${this.icon}`,
...attrs
},
{
default: () => []
}
);
}
});

View File

@@ -8,7 +8,7 @@ import {
getCurrentInstance
} from "vue";
import { RouterView } from "vue-router";
import backTop from "/@/assets/svg/back_top.svg";
import backTop from "/@/assets/svg/back_top.svg?component";
import { usePermissionStoreHook } from "/@/store/modules/permission";
const props = defineProps({
@@ -27,12 +27,23 @@ const transitions = computed(() => {
});
const hideTabs = computed(() => {
return instance?.sets.hideTabs;
return instance?.configure.hideTabs;
});
const layout = computed(() => {
return instance?.layout.layout === "vertical";
});
const getSectionStyle = computed(() => {
return [
hideTabs.value && layout ? "padding-top: 48px;" : "",
!hideTabs.value && layout ? "padding-top: 85px;" : "",
hideTabs.value && !layout.value ? "padding-top: 48px" : "",
!hideTabs.value && !layout.value ? "padding-top: 85px;" : "",
props.fixedHeader ? "" : "padding-top: 0;"
];
});
const transitionMain = defineComponent({
render() {
return h(
@@ -71,12 +82,7 @@ const transitionMain = defineComponent({
<template>
<section
:class="[props.fixedHeader ? 'app-main' : 'app-main-nofixed-header']"
:style="[
hideTabs && layout ? 'padding-top: 48px;' : '',
!hideTabs && layout ? 'padding-top: 85px;' : '',
hideTabs && !layout ? 'padding-top: 48px' : '',
!hideTabs && !layout ? 'padding-top: 85px;' : ''
]"
:style="getSectionStyle"
>
<router-view>
<template #default="{ Component, route }">

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { emitter } from "/@/utils/mitt";
import Notice from "./notice/index.vue";
@@ -12,7 +13,8 @@ import { useAppStoreHook } from "/@/store/modules/app";
import { unref, watch, getCurrentInstance } from "vue";
import { deviceDetection } from "/@/utils/deviceDetection";
import screenfull from "../components/screenfull/index.vue";
import globalization from "/@/assets/svg/globalization.svg";
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
import globalization from "/@/assets/svg/globalization.svg?component";
const instance =
getCurrentInstance().appContext.config.globalProperties.$storage;
@@ -22,6 +24,15 @@ const route = useRoute();
let usename = storageSession.getItem("info")?.username;
const { locale } = useI18n();
const getDropdownItemStyle = computed(() => {
return t => {
return {
background: locale.value === t ? useEpThemeStoreHook().epThemeColor : "",
color: locale.value === t ? "#f4f4f5" : "#000"
};
};
});
watch(
() => locale.value,
() => {
@@ -73,32 +84,28 @@ function translationEn() {
<div class="vertical-header-right">
<!-- 通知 -->
<Notice />
<Notice id="header-notice" />
<!-- 全屏 -->
<screenfull v-show="!deviceDetection()" />
<screenfull id="header-screenfull" v-show="!deviceDetection()" />
<!-- 国际化 -->
<el-dropdown trigger="click">
<el-dropdown id="header-translation" trigger="click">
<globalization />
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="{
background: locale === 'zh' ? '#1b2a47' : '',
color: locale === 'zh' ? '#f4f4f5' : '#000'
}"
:style="getDropdownItemStyle('zh')"
@click="translationCh"
><el-icon class="check-zh" v-show="locale === 'zh'"
><check /></el-icon
>简体中文</el-dropdown-item
><IconifyIconOffline
class="check-zh"
v-show="locale === 'zh'"
icon="check"
/>简体中文</el-dropdown-item
>
<el-dropdown-item
:style="{
background: locale === 'en' ? '#1b2a47' : '',
color: locale === 'en' ? '#f4f4f5' : '#000'
}"
:style="getDropdownItemStyle('en')"
@click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'"
><check /></el-icon
><IconifyIconOffline icon="check" /></el-icon
>English</el-dropdown-item
>
</el-dropdown-menu>
@@ -113,18 +120,20 @@ function translationEn() {
<template #dropdown>
<el-dropdown-menu class="logout">
<el-dropdown-item @click="logout">
<i class="ri-logout-circle-r-line"></i
>{{ $t("message.hsLoginOut") }}</el-dropdown-item
<IconifyIconOffline
icon="logout-circle-r-line"
style="margin: 5px"
/>{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-icon
class="el-icon-setting"
:title="$t('message.hssystemSet')"
:title="$t('buttons.hssystemSet')"
@click="onPanel"
>
<Setting />
<IconifyIconOffline icon="setting" />
</el-icon>
</div>
</div>
@@ -145,10 +154,6 @@ function translationEn() {
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
}
.vertical-header-right {
@@ -230,7 +235,7 @@ function translationEn() {
.translation {
.el-dropdown-menu__item {
padding: 0 40px !important;
padding: 5px 40px !important;
}
.el-dropdown-menu__item:focus,
@@ -242,12 +247,10 @@ function translationEn() {
.check-zh {
position: absolute;
left: 20px;
top: 13px;
}
.check-en {
position: absolute;
bottom: 13px;
left: 20px;
}
}
@@ -259,7 +262,6 @@ function translationEn() {
min-width: 100%;
display: inline-flex;
flex-wrap: wrap;
padding: 0 18px !important;
}
.el-dropdown-menu__item:focus,

View File

@@ -108,7 +108,7 @@ export const noticesData: TabItem[] = [
{
avatar: "",
title: "任务名称",
description: "任务需要在 2021-11-16 20:00 前启动",
description: "任务需要在 2022-11-16 20:00 前启动",
datetime: "",
extra: "未开始",
status: "info",
@@ -118,7 +118,7 @@ export const noticesData: TabItem[] = [
avatar: "",
title: "第三方紧急代码变更",
description:
"一拳提交于 2021-11-16需在 2021-11-18 前完成代码变更任务",
"一拳提交于 2022-11-16需在 2022-11-18 前完成代码变更任务",
datetime: "",
extra: "马上到期",
status: "danger",
@@ -127,7 +127,7 @@ export const noticesData: TabItem[] = [
{
avatar: "",
title: "信息安全考试",
description: "指派小仙于 2021-12-12 前完成更新并发布",
description: "指派小仙于 2022-12-12 前完成更新并发布",
datetime: "",
extra: "已耗时 8 天",
status: "warning",

View File

@@ -16,7 +16,9 @@ notices.value.forEach(notice => {
<el-dropdown trigger="click" placement="bottom-end">
<span class="dropdown-badge">
<el-badge :value="noticesNum" :max="99">
<el-icon class="header-notice-icon"><bell /></el-icon>
<el-icon class="header-notice-icon"
><IconifyIconOffline icon="bell"
/></el-icon>
</el-badge>
</span>
<template #dropdown>

View File

@@ -23,7 +23,7 @@ emitter.on("openPanel", () => {
<div class="project-configuration">
<h3>项目配置</h3>
<el-icon title="关闭配置" class="el-icon-close" @click="show = !show">
<Close />
<IconifyIconOffline icon="close" />
</el-icon>
</div>
<div style="border-bottom: 1px solid #dcdfe6"></div>

View File

@@ -5,18 +5,14 @@ const { isFullscreen, toggle } = useFullscreen();
<template>
<div class="screen-full" @click="toggle">
<i
<FontIcon
:title="
isFullscreen
? $t('message.hsexitfullscreen')
: $t('message.hsfullscreen')
? $t('buttons.hsexitfullscreen')
: $t('buttons.hsfullscreen')
"
:class="
isFullscreen
? 'iconfont team-iconexit-fullscreen'
: 'iconfont team-iconfullscreen'
"
></i>
:icon="isFullscreen ? 'team-iconexit-fullscreen' : 'team-iconfullscreen'"
/>
</div>
</template>

View File

@@ -9,20 +9,29 @@ import {
useCssModule,
getCurrentInstance
} from "vue";
import panel from "../panel/index.vue";
import rgbHex from "rgb-hex";
import { find } from "lodash-es";
import { getConfig } from "/@/config";
import { useRouter } from "vue-router";
import panel from "../panel/index.vue";
import { emitter } from "/@/utils/mitt";
import { templateRef } from "@vueuse/core";
import { debounce } from "/@/utils/debounce";
import { themeColorsType } from "../../types";
import { useAppStoreHook } from "/@/store/modules/app";
import { shadeBgColor } from "../../theme/element-plus";
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
import { storageLocal, storageSession } from "/@/utils/storage";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
import { createNewStyle, writeNewStyle } from "../../theme/element-plus";
import { toggleTheme } from "@zougt/vite-plugin-theme-preprocessor/dist/browser-utils";
import dayIcon from "/@/assets/svg/day.svg?component";
import darkIcon from "/@/assets/svg/dark.svg?component";
const router = useRouter();
const { isSelect } = useCssModule();
const body = document.documentElement as HTMLElement;
const instance =
getCurrentInstance().appContext.app.config.globalProperties.$storage;
@@ -71,17 +80,33 @@ if (unref(layoutTheme)) {
}
// 默认灵动模式
const markValue = ref(storageLocal.getItem("showModel") || "smart");
const markValue = ref(instance.configure?.showModel ?? "smart");
const logoVal = ref(storageLocal.getItem("logoVal") || "1");
const logoVal = ref(instance.configure?.showLogo ?? true);
const epThemeColor = ref(useEpThemeStoreHook().getEpThemeColor);
const settings = reactive({
greyVal: instance.sets.grey,
weakVal: instance.sets.weak,
tabsVal: instance.sets.hideTabs,
multiTagsCache: instance.sets.multiTagsCache
greyVal: instance.configure.grey,
weakVal: instance.configure.weak,
tabsVal: instance.configure.hideTabs,
showLogo: instance.configure.showLogo,
showModel: instance.configure.showModel,
multiTagsCache: instance.configure.multiTagsCache
});
const getThemeColorStyle = computed(() => {
return rgb => {
return { background: `rgb(${rgb})` };
};
});
function changeStorageConfigure(key, val) {
const storageConfigure = instance.configure;
storageConfigure[key] = val;
instance.configure = storageConfigure;
}
function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
const targetEl = target || document.body;
let { className } = targetEl;
@@ -92,12 +117,7 @@ function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
// 灰色模式设置
const greyChange = (value): void => {
toggleClass(settings.greyVal, "html-grey", document.querySelector("html"));
instance.sets = {
grey: value,
weak: instance.sets.weak,
hideTabs: instance.sets.hideTabs,
multiTagsCache: instance.sets.multiTagsCache
};
changeStorageConfigure("grey", value);
};
// 色弱模式设置
@@ -107,77 +127,57 @@ const weekChange = (value): void => {
"html-weakness",
document.querySelector("html")
);
instance.sets = {
grey: instance.sets.grey,
weak: value,
hideTabs: instance.sets.hideTabs,
multiTagsCache: instance.sets.multiTagsCache
};
changeStorageConfigure("weak", value);
};
const tagsChange = () => {
let showVal = settings.tabsVal;
instance.sets = {
grey: instance.sets.grey,
weak: instance.sets.weak,
hideTabs: showVal,
multiTagsCache: instance.sets.multiTagsCache
};
changeStorageConfigure("hideTabs", showVal);
emitter.emit("tagViewsChange", showVal);
};
const multiTagsCacheChange = () => {
let multiTagsCache = settings.multiTagsCache;
instance.sets = {
grey: instance.sets.grey,
weak: instance.sets.weak,
hideTabs: instance.sets.hideTabs,
multiTagsCache: multiTagsCache
};
changeStorageConfigure("multiTagsCache", multiTagsCache);
useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache);
};
//初始化项目配置
nextTick(() => {
settings.greyVal &&
document.querySelector("html")?.setAttribute("class", "html-grey");
settings.weakVal &&
document.querySelector("html")?.setAttribute("class", "html-weakness");
settings.tabsVal && tagsChange();
});
// 清空缓存并返回登录页
function onReset() {
storageLocal.clear();
storageSession.clear();
toggleClass(false, "html-grey", document.querySelector("html"));
toggleClass(false, "html-weakness", document.querySelector("html"));
toggleClass(getConfig().Grey, "html-grey", document.querySelector("html"));
toggleClass(
getConfig().Weak,
"html-weakness",
document.querySelector("html")
);
useMultiTagsStoreHook().handleTags("equal", [
{
path: "/welcome",
parentPath: "/",
meta: {
title: "message.hshome",
icon: "el-icon-s-home",
i18n: true,
showLink: true
title: "menus.hshome",
icon: "home-filled",
i18n: true
}
}
]);
useMultiTagsStoreHook().multiTagsCacheChange(getConfig().MultiTagsCache);
useEpThemeStoreHook().setEpThemeColor(getConfig().EpThemeColor);
storageLocal.clear();
storageSession.clear();
router.push("/login");
}
function onChange(label) {
storageLocal.setItem("showModel", label);
changeStorageConfigure("showModel", label);
emitter.emit("tagViewsShowModel", label);
}
// 侧边栏Logo
function logoChange() {
unref(logoVal) === "1"
? storageLocal.setItem("logoVal", "1")
: storageLocal.setItem("logoVal", "-1");
unref(logoVal)
? changeStorageConfigure("showLogo", true)
: changeStorageConfigure("showLogo", false);
emitter.emit("logoChange", unref(logoVal));
}
@@ -223,23 +223,97 @@ const getThemeColor = computed(() => {
function setLayoutModel(layout: string) {
layoutTheme.value.layout = layout;
window.document.body.setAttribute("layout", layout);
instance.layout = { layout, theme: layoutTheme.value.theme };
instance.layout = {
layout,
theme: layoutTheme.value.theme,
darkMode: instance.layout.darkMode,
sidebarStatus: instance.layout.sidebarStatus,
epThemeColor: instance.layout.epThemeColor
};
useAppStoreHook().setLayout(layout);
}
// 存放夜间主题切换前的导航主题色
let tempLayoutThemeColor;
// 设置导航主题色
function setLayoutThemeColor(theme: string) {
tempLayoutThemeColor = instance.layout.theme;
layoutTheme.value.theme = theme;
toggleTheme({
scopeName: `layout-theme-${theme}`
});
instance.layout = { layout: useAppStoreHook().layout, theme };
instance.layout = {
layout: useAppStoreHook().layout,
theme,
darkMode: dataTheme.value,
sidebarStatus: instance.layout.sidebarStatus,
epThemeColor: instance.layout.epThemeColor
};
if (theme === "default" || theme === "light") {
setEpThemeColor(getConfig().EpThemeColor);
} else {
const colors = find(themeColors.value, { themeColor: theme });
const color = "#" + rgbHex(colors.rgb);
setEpThemeColor(color);
}
}
// 设置ep主题色
const setEpThemeColor = (color: string) => {
writeNewStyle(createNewStyle(color));
useEpThemeStoreHook().setEpThemeColor(color);
body.style.setProperty("--el-color-primary-active", shadeBgColor(color));
};
let dataTheme = ref<boolean>(instance.layout.darkMode);
// 日间、夜间主题切换
function dataThemeChange() {
if (dataTheme.value) {
body.setAttribute("data-theme", "dark");
setLayoutThemeColor("light");
} else {
body.setAttribute("data-theme", "");
tempLayoutThemeColor && setLayoutThemeColor(tempLayoutThemeColor);
instance.layout = {
layout: useAppStoreHook().layout,
theme: instance.layout.theme,
darkMode: dataTheme.value,
sidebarStatus: instance.layout.sidebarStatus,
epThemeColor: instance.layout.epThemeColor
};
}
}
//初始化项目配置
nextTick(() => {
settings.greyVal &&
document.querySelector("html")?.setAttribute("class", "html-grey");
settings.weakVal &&
document.querySelector("html")?.setAttribute("class", "html-weakness");
settings.tabsVal && tagsChange();
writeNewStyle(createNewStyle(epThemeColor.value));
dataThemeChange();
});
</script>
<template>
<panel>
<el-divider>主题风格</el-divider>
<el-divider>主题</el-divider>
<el-switch
v-model="dataTheme"
inline-prompt
class="pure-datatheme"
:active-icon="dayIcon"
:inactive-icon="darkIcon"
@change="dataThemeChange"
>
</el-switch>
<el-divider>导航栏模式</el-divider>
<ul class="pure-theme">
<el-tooltip class="item" content="左侧菜单模式" placement="bottom">
<li
@@ -264,12 +338,12 @@ function setLayoutThemeColor(theme: string) {
</el-tooltip>
</ul>
<el-divider>主题色</el-divider>
<ul class="theme-color">
<el-divider v-show="!dataTheme">主题色</el-divider>
<ul class="theme-color" v-show="!dataTheme">
<li
v-for="(item, index) in themeColors"
:key="index"
:style="{ background: `rgb(${item.rgb})` }"
:style="getThemeColorStyle(item.rgb)"
@click="setLayoutThemeColor(item.themeColor)"
>
<el-icon
@@ -277,14 +351,14 @@ function setLayoutThemeColor(theme: string) {
:size="17"
:color="getThemeColor(item.themeColor)"
>
<Check />
<IconifyIconOffline icon="check" />
</el-icon>
</li>
</ul>
<el-divider>界面显示</el-divider>
<ul class="setting">
<li>
<li v-show="!dataTheme">
<span>灰色模式</span>
<el-switch
v-model="settings.greyVal"
@@ -296,7 +370,7 @@ function setLayoutThemeColor(theme: string) {
>
</el-switch>
</li>
<li>
<li v-show="!dataTheme">
<span>色弱模式</span>
<el-switch
v-model="settings.weakVal"
@@ -325,8 +399,8 @@ function setLayoutThemeColor(theme: string) {
<el-switch
v-model="logoVal"
inline-prompt
active-value="1"
inactive-value="-1"
:active-value="true"
:inactive-value="false"
inactive-color="#a6a6a6"
active-text=""
inactive-text=""
@@ -362,7 +436,12 @@ function setLayoutThemeColor(theme: string) {
style="width: 90%; margin: 24px 15px"
@click="onReset"
>
<i class="fa fa-sign-out"></i>
<IconifyIconOffline
icon="fa-sign-out"
width="15"
height="15"
style="margin-right: 4px"
/>
清空缓存并返回登录页</el-button
>
</panel>
@@ -370,7 +449,7 @@ function setLayoutThemeColor(theme: string) {
<style scoped module>
.isSelect {
border: 2px solid #0960bd;
border: 2px solid var(--el-color-primary);
}
</style>
@@ -391,6 +470,14 @@ function setLayoutThemeColor(theme: string) {
font-weight: 700;
}
.pure-datatheme {
width: 100%;
height: 50px;
text-align: center;
display: block;
padding-top: 25px;
}
.pure-theme {
margin-top: 25px;
width: 100%;

View File

@@ -65,7 +65,7 @@ const getBreadcrumb = (): void => {
{
path: "/welcome",
parentPath: "/",
meta: { title: "message.hshome", i18n: true }
meta: { title: "menus.hshome", i18n: true }
} as unknown as RouteLocationMatched
].concat(matched);
}

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import { ref } from "vue";
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
export interface Props {
isActive: boolean;
}
@@ -7,6 +9,8 @@ const props = withDefaults(defineProps<Props>(), {
isActive: false
});
const fillColor = ref<string>("");
const emit = defineEmits<{
(e: "toggleClick"): void;
}>();
@@ -21,8 +25,11 @@ const toggleClick = () => {
:class="classes.container"
:title="props.isActive ? '点击折叠' : '点击展开'"
@click="toggleClick"
@mouseenter="fillColor = useEpThemeStoreHook().epThemeColor"
@mouseleave="fillColor = ''"
>
<svg
:fill="fillColor"
:class="['hamburger', props.isActive ? 'is-active' : '']"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -13,14 +13,12 @@ import Notice from "../notice/index.vue";
import { templateRef } from "@vueuse/core";
import SidebarItem from "./sidebarItem.vue";
import avatars from "/@/assets/avatars.jpg";
import { algorithm } from "/@/utils/algorithm";
import screenfull from "../screenfull/index.vue";
import { useRoute, useRouter } from "vue-router";
import { storageSession } from "/@/utils/storage";
import Icon from "/@/components/ReIcon/src/Icon.vue";
import { deviceDetection } from "/@/utils/deviceDetection";
import globalization from "/@/assets/svg/globalization.svg";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import globalization from "/@/assets/svg/globalization.svg?component";
const instance =
getCurrentInstance().appContext.config.globalProperties.$storage;
@@ -35,6 +33,15 @@ const routers = useRouter().options.routes;
let usename = storageSession.getItem("info")?.username;
const { locale, t } = useI18n();
const getDropdownItemStyle = computed(() => {
return t => {
return {
background: locale.value === t ? "#1b2a47" : "",
color: locale.value === t ? "#f4f4f5" : "#000"
};
};
});
watch(
() => locale.value,
() => {
@@ -83,7 +90,7 @@ const menuSelect = (indexPath: string): void => {
}
});
}
findCurrentRoute(algorithm.increaseIndexes(routers));
findCurrentRoute(routers);
};
function backHome() {
@@ -119,7 +126,11 @@ onMounted(() => {
<template>
<div class="horizontal-header">
<div class="horizontal-header-left" @click="backHome">
<Icon svg :width="35" :height="35" content="team-iconlogo" />
<FontIcon
icon="team-iconlogo"
svg
style="width: 35px; height: 35px"
></FontIcon>
<h4>{{ title }}</h4>
</div>
<el-menu
@@ -140,32 +151,26 @@ onMounted(() => {
</el-menu>
<div class="horizontal-header-right">
<!-- 通知 -->
<Notice />
<Notice id="header-notice" />
<!-- 全屏 -->
<screenfull v-show="!deviceDetection()" />
<screenfull id="header-screenfull" v-show="!deviceDetection()" />
<!-- 国际化 -->
<el-dropdown trigger="click">
<el-dropdown id="header-translation" trigger="click">
<globalization />
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="{
background: locale === 'zh' ? '#1b2a47' : '',
color: locale === 'zh' ? '#f4f4f5' : '#000'
}"
:style="getDropdownItemStyle('zh')"
@click="translationCh"
><el-icon class="check-zh" v-show="locale === 'zh'"
><check /></el-icon
><IconifyIconOffline icon="check" /></el-icon
>简体中文</el-dropdown-item
>
<el-dropdown-item
:style="{
background: locale === 'en' ? '#1b2a47' : '',
color: locale === 'en' ? '#f4f4f5' : '#000'
}"
:style="getDropdownItemStyle('en')"
@click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'"
><check /></el-icon
><IconifyIconOffline icon="check" /></el-icon
>English</el-dropdown-item
>
</el-dropdown-menu>
@@ -180,18 +185,21 @@ onMounted(() => {
<template #dropdown>
<el-dropdown-menu class="logout">
<el-dropdown-item @click="logout">
<i class="ri-logout-circle-r-line"></i
>{{ $t("message.hsLoginOut") }}</el-dropdown-item
<IconifyIconOffline
icon="logout-circle-r-line"
style="margin: 5px"
/>
{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-icon
class="el-icon-setting"
:title="$t('message.hssystemSet')"
:title="$t('buttons.hssystemSet')"
@click="onPanel"
>
<Setting />
<IconifyIconOffline icon="setting" />
</el-icon>
</div>
</div>
@@ -200,7 +208,7 @@ onMounted(() => {
<style lang="scss" scoped>
.translation {
.el-dropdown-menu__item {
padding: 0 40px !important;
padding: 5px 40px !important;
}
.el-dropdown-menu__item:focus,
@@ -212,12 +220,10 @@ onMounted(() => {
.check-zh {
position: absolute;
left: 20px;
top: 13px;
}
.check-en {
position: absolute;
bottom: 13px;
left: 20px;
}
}
@@ -229,7 +235,6 @@ onMounted(() => {
min-width: 100%;
display: inline-flex;
flex-wrap: wrap;
padding: 0 18px !important;
}
.el-dropdown-menu__item:focus,

View File

@@ -1,6 +1,5 @@
<script setup lang="ts">
import { getCurrentInstance } from "vue";
import Icon from "/@/components/ReIcon/src/Icon.vue";
const props = defineProps({
collapse: Boolean
});
@@ -19,7 +18,11 @@ const title =
class="sidebar-logo-link"
to="/"
>
<Icon svg :width="35" :height="35" content="team-iconlogo" />
<FontIcon
icon="team-iconlogo"
svg
style="width: 35px; height: 35px"
></FontIcon>
<span class="sidebar-title">{{ title }}</span>
</router-link>
<router-link
@@ -29,7 +32,11 @@ const title =
class="sidebar-logo-link"
to="/"
>
<Icon svg :width="35" :height="35" content="team-iconlogo" />
<FontIcon
icon="team-iconlogo"
svg
style="width: 35px; height: 35px"
></FontIcon>
<span class="sidebar-title">{{ title }}</span>
</router-link>
</transition>

View File

@@ -1,11 +1,18 @@
<script setup lang="ts">
import {
ref,
PropType,
nextTick,
computed,
CSSProperties,
getCurrentInstance
} from "vue";
import path from "path";
import { PropType, ref, nextTick, getCurrentInstance } from "vue";
import { childrenType } from "../../types";
import { useAppStoreHook } from "/@/store/modules/app";
import Icon from "/@/components/ReIcon/src/Icon.vue";
import { transformI18n } from "/@/plugins/i18n";
import { findIconReg } from "/@/components/ReIcon";
import { useAppStoreHook } from "/@/store/modules/app";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
const instance = getCurrentInstance().appContext.app.config.globalProperties;
const menuMode = instance.$storage.layout?.layout === "vertical";
const pureApp = useAppStoreHook();
@@ -24,6 +31,61 @@ const props = defineProps({
}
});
const getExtraIconStyle = computed((): CSSProperties => {
if (useAppStoreHook().getSidebarStatus) {
return {
position: "absolute",
right: "10px"
};
} else {
return {
position: "static"
};
}
});
const getNoDropdownStyle = computed((): CSSProperties => {
return {
display: "flex",
alignItems: "center"
};
});
const getDivStyle = computed((): CSSProperties => {
return {
width: pureApp.sidebar.opened ? "" : "100%",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
overflow: "hidden"
};
});
const getMenuTextStyle = computed((): CSSProperties => {
return {
width: pureApp.sidebar.opened ? "125px" : "",
overflow: "hidden",
textOverflow: "ellipsis",
outline: "none"
};
});
const getSubTextStyle = computed((): CSSProperties => {
return {
width: pureApp.sidebar.opened ? "125px" : "",
display: "inline-block",
overflow: "hidden",
textOverflow: "ellipsis"
};
});
const getSpanStyle = computed((): CSSProperties => {
return {
overflow: "hidden",
textOverflow: "ellipsis"
};
});
const onlyOneChild: childrenType = ref(null);
// 存放菜单是否存在showTooltip属性标识
const hoverMenuMap = new WeakMap();
@@ -88,12 +150,12 @@ function resolvePath(routePath) {
<el-menu-item
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
style="display: flex; align-items: center"
:style="getNoDropdownStyle"
>
<el-icon v-show="props.item.meta.icon">
<component
:is="
findIconReg(
useRenderIcon(
onlyOneChild.meta.icon ||
(props.item.meta && props.item.meta.icon)
)
@@ -101,15 +163,7 @@ function resolvePath(routePath) {
></component>
</el-icon>
<template #title>
<div
:style="{
width: pureApp.sidebar.opened ? '' : '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
overflow: 'hidden'
}"
>
<div :style="getDivStyle">
<span v-if="!menuMode">{{
transformI18n(onlyOneChild.meta.title, onlyOneChild.meta.i18n)
}}</span>
@@ -126,12 +180,7 @@ function resolvePath(routePath) {
</template>
<span
ref="menuTextRef"
:style="{
width: pureApp.sidebar.opened ? '125px' : '',
overflow: 'hidden',
textOverflow: 'ellipsis',
outline: 'none'
}"
:style="getMenuTextStyle"
@mouseover="hoverMenu(onlyOneChild)"
>
{{
@@ -139,11 +188,14 @@ function resolvePath(routePath) {
}}
</span>
</el-tooltip>
<Icon
<FontIcon
v-if="onlyOneChild.meta.extraIcon"
width="30px"
height="30px"
:style="getExtraIconStyle"
:icon="onlyOneChild.meta.extraIcon.name"
:svg="onlyOneChild.meta.extraIcon.svg ? true : false"
:content="`${onlyOneChild.meta.extraIcon.name}`"
/>
></FontIcon>
</div>
</template>
</el-menu-item>
@@ -158,7 +210,7 @@ function resolvePath(routePath) {
<template #title>
<el-icon v-show="props.item.meta.icon" :class="props.item.meta.icon">
<component
:is="findIconReg(props.item.meta && props.item.meta.icon)"
:is="useRenderIcon(props.item.meta && props.item.meta.icon)"
></component>
</el-icon>
<span v-if="!menuMode">{{
@@ -175,24 +227,22 @@ function resolvePath(routePath) {
</template>
<div
ref="menuTextRef"
:style="{
width: pureApp.sidebar.opened ? '125px' : '',
display: 'inline-block',
overflow: 'hidden',
textOverflow: 'ellipsis'
}"
:style="getSubTextStyle"
@mouseover="hoverMenu(props.item)"
>
<span style="overflow: hidden; text-overflow: ellipsis">
<span :style="getSpanStyle">
{{ transformI18n(props.item.meta.title, props.item.meta.i18n) }}
</span>
</div>
</el-tooltip>
<Icon
<FontIcon
v-if="props.item.meta.extraIcon"
width="30px"
height="30px"
style="position: absolute; right: 10px"
:icon="props.item.meta.extraIcon.name"
:svg="props.item.meta.extraIcon.svg ? true : false"
:content="`${props.item.meta.extraIcon.name}`"
/>
></FontIcon>
</template>
<sidebar-item
v-for="child in props.item.children"

View File

@@ -2,7 +2,6 @@
import Logo from "./logo.vue";
import { emitter } from "/@/utils/mitt";
import SidebarItem from "./sidebarItem.vue";
import { algorithm } from "/@/utils/algorithm";
import { storageLocal } from "/@/utils/storage";
import { useRoute, useRouter } from "vue-router";
import { computed, ref, onBeforeMount } from "vue";
@@ -12,7 +11,9 @@ import { usePermissionStoreHook } from "/@/store/modules/permission";
const route = useRoute();
const pureApp = useAppStoreHook();
const router = useRouter().options.routes;
const showLogo = ref(storageLocal.getItem("logoVal") || "1");
const showLogo = ref(
storageLocal.getItem("responsive-configure")?.showLogo ?? true
);
const isCollapse = computed(() => {
return !pureApp.getSidebarStatus;
});
@@ -46,7 +47,7 @@ const menuSelect = (indexPath: string): void => {
}
});
}
findCurrentRoute(algorithm.increaseIndexes(router));
findCurrentRoute(router);
};
onBeforeMount(() => {
@@ -58,7 +59,7 @@ onBeforeMount(() => {
<template>
<div :class="['sidebar-container', showLogo ? 'has-logo' : '']">
<Logo v-if="showLogo === '1'" :collapse="isCollapse" />
<Logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"

View File

@@ -207,7 +207,6 @@
li {
width: 100%;
margin: 0;
padding: 0 12px;
cursor: pointer;
display: flex;
align-items: center;
@@ -254,13 +253,23 @@
}
}
.ri-arrow-left-s-line {
.arrow-left,
.arrow-right {
width: 40px;
height: 38px;
line-height: 38px;
text-align: center;
font-size: 20px;
color: #00000073;
position: relative;
svg {
width: 20px;
height: 20px;
position: absolute;
left: 50%;
transform: translate(-50%, 50%);
}
}
.arrow-left {
box-shadow: 5px 0 5px -6px #ccc;
&:hover {
@@ -268,15 +277,9 @@
}
}
.ri-arrow-right-s-line {
width: 40px;
height: 38px;
line-height: 38px;
text-align: center;
font-size: 20px;
border-right: 1px solid #ccc;
color: #00000073;
.arrow-right {
box-shadow: -5px 0 5px -6px #ccc;
border-right: 1px solid #ccc;
&:hover {
cursor: e-resize;

View File

@@ -2,22 +2,25 @@
import {
ref,
watch,
onBeforeMount,
unref,
reactive,
nextTick,
computed,
getCurrentInstance,
ComputedRef
ComputedRef,
CSSProperties,
onBeforeMount,
getCurrentInstance
} from "vue";
import close from "/@/assets/svg/close.svg";
import refresh from "/@/assets/svg/refresh.svg";
import closeAll from "/@/assets/svg/close_all.svg";
import closeLeft from "/@/assets/svg/close_left.svg";
import closeOther from "/@/assets/svg/close_other.svg";
import closeRight from "/@/assets/svg/close_right.svg";
import close from "/@/assets/svg/close.svg?component";
import refresh from "/@/assets/svg/refresh.svg?component";
import closeAll from "/@/assets/svg/close_all.svg?component";
import closeLeft from "/@/assets/svg/close_left.svg?component";
import closeOther from "/@/assets/svg/close_other.svg?component";
import closeRight from "/@/assets/svg/close_right.svg?component";
import { emitter } from "/@/utils/mitt";
import { $t as t } from "/@/plugins/i18n";
import { isEqual, isEmpty } from "lodash-es";
import { transformI18n } from "/@/plugins/i18n";
import { storageLocal } from "/@/utils/storage";
@@ -28,7 +31,6 @@ import { handleAliveRoute, delAliveRoutes } from "/@/router/utils";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import { toggleClass, removeClass, hasClass } from "/@/utils/operate";
import { templateRef, useResizeObserver, useDebounceFn } from "@vueuse/core";
const route = useRoute();
@@ -38,11 +40,11 @@ const activeIndex = ref<number>(-1);
let refreshButton = "refresh-button";
const instance = getCurrentInstance();
const pureSetting = useSettingStoreHook();
const showTags = ref(storageLocal.getItem("tagsVal") || false);
const tabDom = templateRef<HTMLElement | null>("tabDom", null);
const containerDom = templateRef<HTMLElement | null>("containerDom", null);
const scrollbarDom = templateRef<HTMLElement | null>("scrollbarDom", null);
const showTags =
ref(storageLocal.getItem("responsive-configure").hideTabs) ?? "false";
let multiTags: ComputedRef<Array<RouteConfigs>> = computed(() => {
return useMultiTagsStoreHook()?.multiTags;
});
@@ -126,15 +128,15 @@ const moveToView = (index: number): void => {
if (!instance.refs["dynamic" + index]) {
return;
}
const tabItemEl = instance.refs["dynamic" + index];
const tabItemElOffsetLeft = (tabItemEl as HTMLElement).offsetLeft;
const tabItemOffsetWidth = (tabItemEl as HTMLElement).offsetWidth;
const tabItemEl = instance.refs["dynamic" + index][0];
const tabItemElOffsetLeft = (tabItemEl as HTMLElement)?.offsetLeft;
const tabItemOffsetWidth = (tabItemEl as HTMLElement)?.offsetWidth;
// 标签页导航栏可视长度(不包含溢出部分)
const scrollbarDomWidth = scrollbarDom.value
? scrollbarDom.value.offsetWidth
? scrollbarDom.value?.offsetWidth
: 0;
// 已有标签页总长度(包含溢出部分)
const tabDomWidth = tabDom.value ? tabDom.value.offsetWidth : 0;
const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) {
translateX.value = 0;
@@ -184,45 +186,45 @@ const handleScroll = (offset: number): void => {
}
};
const tagsViews = ref<Array<tagsViewsType>>([
const tagsViews = reactive<Array<tagsViewsType>>([
{
icon: refresh,
text: "message.hsreload",
text: t("buttons.hsreload"),
divided: false,
disabled: false,
show: true
},
{
icon: close,
text: "message.hscloseCurrentTab",
text: t("buttons.hscloseCurrentTab"),
divided: false,
disabled: multiTags.value.length > 1 ? false : true,
show: true
},
{
icon: closeLeft,
text: "message.hscloseLeftTabs",
text: t("buttons.hscloseLeftTabs"),
divided: true,
disabled: multiTags.value.length > 1 ? false : true,
show: true
},
{
icon: closeRight,
text: "message.hscloseRightTabs",
text: t("buttons.hscloseRightTabs"),
divided: false,
disabled: multiTags.value.length > 1 ? false : true,
show: true
},
{
icon: closeOther,
text: "message.hscloseOtherTabs",
text: t("buttons.hscloseOtherTabs"),
divided: true,
disabled: multiTags.value.length > 2 ? false : true,
show: true
},
{
icon: closeAll,
text: "message.hscloseAllTabs",
text: t("buttons.hscloseAllTabs"),
divided: false,
disabled: multiTags.value.length > 1 ? false : true,
show: true
@@ -230,9 +232,13 @@ const tagsViews = ref<Array<tagsViewsType>>([
]);
// 显示模式,默认灵动模式显示
const showModel = ref(storageLocal.getItem("showModel") || "smart");
const showModel = ref(
storageLocal.getItem("responsive-configure")?.showModel || "smart"
);
if (!showModel.value) {
storageLocal.setItem("showModel", "card");
const configure = storageLocal.getItem("responsive-configure");
configure.showModel = "card";
storageLocal.setItem("responsive-configure", configure);
}
let visible = ref(false);
@@ -306,10 +312,9 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
path: "/welcome",
parentPath: "/",
meta: {
title: "message.hshome",
title: "menus.hshome",
i18n: true,
icon: "el-icon-s-home",
showLink: true
icon: "home-filled"
}
},
obj
@@ -429,13 +434,13 @@ function closeMenu() {
function showMenus(value: boolean) {
Array.of(1, 2, 3, 4, 5).forEach(v => {
tagsViews.value[v].show = value;
tagsViews[v].show = value;
});
}
function disabledMenus(value: boolean) {
Array.of(1, 2, 3, 4, 5).forEach(v => {
tagsViews.value[v].disabled = value;
tagsViews[v].disabled = value;
});
}
@@ -457,35 +462,34 @@ function showMenuModel(
showMenus(true);
if (refresh) {
tagsViews.value[0].show = true;
tagsViews[0].show = true;
}
/**
* currentIndex为1时左侧的菜单是首页则不显示关闭左侧标签页
* 如果currentIndex等于routeLength-1右侧没有菜单则不显示关闭右侧标签页
*/
if (currentIndex === 1 && routeLength !== 2) {
// 左侧的菜单是首页,右侧存在别的菜单
tagsViews.value[2].show = false;
tagsViews[2].show = false;
Array.of(1, 3, 4, 5).forEach(v => {
tagsViews.value[v].disabled = false;
tagsViews[v].disabled = false;
});
tagsViews.value[2].disabled = true;
tagsViews[2].disabled = true;
} else if (currentIndex === 1 && routeLength === 2) {
disabledMenus(false);
// 左侧的菜单是首页,右侧不存在别的菜单
Array.of(2, 3, 4).forEach(v => {
tagsViews.value[v].show = false;
tagsViews.value[v].disabled = true;
tagsViews[v].show = false;
tagsViews[v].disabled = true;
});
} else if (routeLength - 1 === currentIndex && currentIndex !== 0) {
// 当前路由是所有路由中的最后一个
tagsViews.value[3].show = false;
tagsViews[3].show = false;
Array.of(1, 2, 4, 5).forEach(v => {
tagsViews.value[v].disabled = false;
tagsViews[v].disabled = false;
});
tagsViews.value[3].disabled = true;
tagsViews[3].disabled = true;
} else if (currentIndex === 0 || currentPath === "/redirect/welcome") {
// 当前路由为首页
disabledMenus(true);
@@ -499,10 +503,10 @@ function openMenu(tag, e) {
if (tag.path === "/welcome") {
// 右键菜单为首页,只显示刷新
showMenus(false);
tagsViews.value[0].show = true;
tagsViews[0].show = true;
} else if (route.path !== tag.path) {
// 右键菜单不匹配当前路由,隐藏刷新
tagsViews.value[0].show = false;
tagsViews[0].show = false;
showMenuModel(tag.path, tag.query);
} else if (
// eslint-disable-next-line no-dupe-else-if
@@ -511,7 +515,7 @@ function openMenu(tag, e) {
) {
showMenus(true);
// 只有两个标签时不显示关闭其他标签页
tagsViews.value[4].show = false;
tagsViews[4].show = false;
} else if (route.path === tag.path) {
// 右键当前激活的菜单
showMenuModel(tag.path, tag.query, true);
@@ -546,30 +550,32 @@ function tagOnClick(item) {
}
// 鼠标移入
function onMouseenter(item, index) {
function onMouseenter(index) {
if (index) activeIndex.value = index;
if (unref(showModel) === "smart") {
if (hasClass(instance.refs["schedule" + index], "schedule-active")) return;
toggleClass(true, "schedule-in", instance.refs["schedule" + index]);
toggleClass(false, "schedule-out", instance.refs["schedule" + index]);
if (hasClass(instance.refs["schedule" + index][0], "schedule-active"))
return;
toggleClass(true, "schedule-in", instance.refs["schedule" + index][0]);
toggleClass(false, "schedule-out", instance.refs["schedule" + index][0]);
} else {
if (hasClass(instance.refs["dynamic" + index], "card-active")) return;
toggleClass(true, "card-in", instance.refs["dynamic" + index]);
toggleClass(false, "card-out", instance.refs["dynamic" + index]);
if (hasClass(instance.refs["dynamic" + index][0], "card-active")) return;
toggleClass(true, "card-in", instance.refs["dynamic" + index][0]);
toggleClass(false, "card-out", instance.refs["dynamic" + index][0]);
}
}
// 鼠标移出
function onMouseleave(item, index) {
function onMouseleave(index) {
activeIndex.value = -1;
if (unref(showModel) === "smart") {
if (hasClass(instance.refs["schedule" + index], "schedule-active")) return;
toggleClass(false, "schedule-in", instance.refs["schedule" + index]);
toggleClass(true, "schedule-out", instance.refs["schedule" + index]);
if (hasClass(instance.refs["schedule" + index][0], "schedule-active"))
return;
toggleClass(false, "schedule-in", instance.refs["schedule" + index][0]);
toggleClass(true, "schedule-out", instance.refs["schedule" + index][0]);
} else {
if (hasClass(instance.refs["dynamic" + index], "card-active")) return;
toggleClass(false, "card-in", instance.refs["dynamic" + index]);
toggleClass(true, "card-out", instance.refs["dynamic" + index]);
if (hasClass(instance.refs["dynamic" + index][0], "card-active")) return;
toggleClass(false, "card-in", instance.refs["dynamic" + index][0]);
toggleClass(true, "card-out", instance.refs["dynamic" + index][0]);
}
}
@@ -609,17 +615,25 @@ onBeforeMount(() => {
});
});
});
const getTabStyle = computed((): CSSProperties => {
return {
transform: `translateX(${translateX.value}px)`
};
});
const getContextMenuStyle = computed((): CSSProperties => {
return { left: buttonLeft.value + "px", top: buttonTop.value + "px" };
});
</script>
<template>
<div ref="containerDom" class="tags-view" v-if="!showTags">
<i class="ri-arrow-left-s-line" @click="handleScroll(200)"></i>
<div class="arrow-left">
<IconifyIconOffline icon="arrow-left-s-line" @click="handleScroll(200)" />
</div>
<div ref="scrollbarDom" class="scroll-container">
<div
class="tab"
ref="tabDom"
:style="{ transform: `translateX(${translateX}px)` }"
>
<div class="tab" ref="tabDom" :style="getTabStyle">
<div
:ref="'dynamic' + index"
v-for="(item, index) in multiTags"
@@ -632,8 +646,8 @@ onBeforeMount(() => {
: ''
]"
@contextmenu.prevent="openMenu(item, $event)"
@mouseenter.prevent="onMouseenter(item, index)"
@mouseleave.prevent="onMouseleave(item, index)"
@mouseenter.prevent="onMouseenter(index)"
@mouseleave.prevent="onMouseleave(index)"
@click="tagOnClick(item)"
>
<router-link :to="item.path"
@@ -647,7 +661,7 @@ onBeforeMount(() => {
class="el-icon-close"
@click.stop="deleteMenu(item)"
>
<CloseBold />
<IconifyIconOffline icon="close-bold" />
</el-icon>
<div
:ref="'schedule' + index"
@@ -657,13 +671,18 @@ onBeforeMount(() => {
</div>
</div>
</div>
<i class="ri-arrow-right-s-line" @click="handleScroll(-200)"></i>
<span class="arrow-right">
<IconifyIconOffline
icon="arrow-right-s-line"
@click="handleScroll(-200)"
/>
</span>
<!-- 右键菜单按钮 -->
<transition name="el-zoom-in-top">
<ul
v-show="visible"
:key="Math.random()"
:style="{ left: buttonLeft + 'px', top: buttonTop + 'px' }"
:style="getContextMenuStyle"
class="contextmenu"
>
<div
@@ -682,17 +701,17 @@ onBeforeMount(() => {
<ul class="right-button">
<li>
<el-icon
:title="$t('message.hsrefreshRoute')"
:title="$t('buttons.hsrefreshRoute')"
class="el-icon-refresh-right rotate"
@click="onFresh"
>
<RefreshRight />
<IconifyIconOffline icon="refresh-right" />
</el-icon>
</li>
<li>
<el-dropdown trigger="click" placement="bottom-end">
<el-icon>
<ArrowDown />
<IconifyIconOffline icon="arrow-down" />
</el-icon>
<template #dropdown>
<el-dropdown-menu>

View File

@@ -11,13 +11,14 @@ import { setType } from "./types";
import { useI18n } from "vue-i18n";
import { routerArrays } from "./types";
import { emitter } from "/@/utils/mitt";
import backTop from "/@/assets/svg/back_top.svg";
import { useAppStoreHook } from "/@/store/modules/app";
import fullScreen from "/@/assets/svg/full_screen.svg";
import exitScreen from "/@/assets/svg/exit_screen.svg";
import { deviceDetection } from "/@/utils/deviceDetection";
import { useSettingStoreHook } from "/@/store/modules/settings";
import { useMultiTagsStore } from "/@/store/modules/multiTags";
import { useSettingStoreHook } from "/@/store/modules/settings";
import backTop from "/@/assets/svg/back_top.svg?component";
import fullScreen from "/@/assets/svg/full_screen.svg?component";
import exitScreen from "/@/assets/svg/exit_screen.svg?component";
import navbar from "./components/navbar.vue";
import tag from "./components/tag/index.vue";
@@ -51,16 +52,21 @@ const layout = computed(() => {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
instance.$storage.layout = {
layout: instance.$config?.Layout ?? "vertical",
theme: instance.$config?.Theme ?? "default"
theme: instance.$config?.Theme ?? "default",
darkMode: instance.$config?.DarkMode ?? false,
sidebarStatus: instance.$config?.SidebarStatus ?? true,
epThemeColor: instance.$config?.EpThemeColor ?? "#409EFF"
};
}
// 灰色模式、色弱模式、隐藏标签页
if (!instance.$storage.sets) {
if (!instance.$storage.configure) {
// eslint-disable-next-line
instance.$storage.sets = {
instance.$storage.configure = {
grey: instance.$config?.Grey ?? false,
weak: instance.$config?.Weak ?? false,
hideTabs: instance.$config?.HideTabs ?? false,
showLogo: instance.$config?.ShowLogo ?? true,
showModel: instance.$config?.ShowModel ?? "smart",
multiTagsCache: instance.$config?.MultiTagsCache ?? false
};
}
@@ -90,7 +96,7 @@ const set: setType = reactive({
}),
hideTabs: computed(() => {
return instance.$storage?.sets.hideTabs;
return instance.$storage?.configure.hideTabs;
})
});
@@ -98,7 +104,10 @@ function setTheme(layoutModel: string) {
window.document.body.setAttribute("layout", layoutModel);
instance.$storage.layout = {
layout: `${layoutModel}`,
theme: instance.$storage.layout?.theme
theme: instance.$storage.layout?.theme,
darkMode: instance.$storage.layout?.darkMode,
sidebarStatus: instance.$storage.layout?.sidebarStatus,
epThemeColor: instance.$storage.layout?.epThemeColor
};
}

View File

@@ -0,0 +1,80 @@
/* 动态改变element-plus主题色 */
import rgbHex from "rgb-hex";
import { convert } from "css-color-function";
import { TinyColor } from "@ctrl/tinycolor";
import epCss from "element-plus/dist/index.css";
// 色值表
const formula = {
"shade-1": "color(primary shade(10%))",
"light-1": "color(primary tint(10%))",
"light-2": "color(primary tint(20%))",
"light-3": "color(primary tint(30%))",
"light-4": "color(primary tint(40%))",
"light-5": "color(primary tint(50%))",
"light-6": "color(primary tint(60%))",
"light-7": "color(primary tint(70%))",
"light-8": "color(primary tint(80%))",
"light-9": "color(primary tint(90%))"
};
// 把生成的样式表写入到style中
export const writeNewStyle = (newStyle: string): void => {
const style = window.document.createElement("style");
style.innerText = newStyle;
window.document.head.appendChild(style);
};
// 根据主题色,生成最新的样式表
export const createNewStyle = (primaryStyle: string): string => {
// 根据主色生成色值表
const colors = createColors(primaryStyle);
// 在当前ep的默认样式表中标记需要替换的色值
let cssText = getStyleTemplate(epCss);
// 遍历生成的色值表,在 默认样式表 进行全局替换
Object.keys(colors).forEach(key => {
cssText = cssText.replace(
new RegExp("(:|\\s+)" + key, "g"),
"$1" + colors[key]
);
});
return cssText;
};
export const createColors = (primary: string) => {
if (!primary) return;
const colors = {
primary
};
Object.keys(formula).forEach(key => {
const value = formula[key].replace(/primary/, primary);
colors[key] = "#" + rgbHex(convert(value));
});
return colors;
};
const getStyleTemplate = (data: string): string => {
const colorMap = {
"#3a8ee6": "shade-1",
"#409eff": "primary",
"#53a8ff": "light-1",
"#66b1ff": "light-2",
"#79bbff": "light-3",
"#8cc5ff": "light-4",
"#a0cfff": "light-5",
"#b3d8ff": "light-6",
"#c6e2ff": "light-7",
"#d9ecff": "light-8",
"#ecf5ff": "light-9"
};
Object.keys(colorMap).forEach(key => {
const value = colorMap[key];
data = data.replace(new RegExp(key, "ig"), value);
});
return data;
};
// 自动计算hover和active颜色 https://element-plus.gitee.io/zh-CN/component/button.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%A2%9C%E8%89%B2-%E6%B5%8B%E8%AF%95%E7%89%88
export const shadeBgColor = (color: string): string => {
return new TinyColor(color).shade(10).toString();
};

View File

@@ -1,12 +1,12 @@
import { Component } from "vue";
export const routerArrays: Array<RouteConfigs> = [
{
path: "/welcome",
parentPath: "/",
meta: {
title: "message.hshome",
title: "menus.hshome",
i18n: true,
icon: "el-icon-s-home",
showLink: true
icon: "home-filled"
}
}
];
@@ -32,7 +32,7 @@ export type multiTagsType = {
};
export type tagsViewsType = {
icon: string;
icon: Component;
text: string;
divided: boolean;
disabled: boolean;

View File

@@ -6,11 +6,11 @@ import { createApp, Directive } from "vue";
import { usI18n } from "../src/plugins/i18n";
import { MotionPlugin } from "@vueuse/motion";
import { useTable } from "../src/plugins/vxe-table";
import { useFontawesome } from "../src/plugins/fontawesome";
import { useElementPlus } from "../src/plugins/element-plus";
import { injectResponsiveStorage } from "/@/utils/storage/responsive";
import "animate.css";
import "virtual:windi.css";
// 导入公共样式
import "./style/index.scss";
// 导入字体图标
@@ -26,6 +26,16 @@ Object.keys(directives).forEach(key => {
app.directive(key, (directives as { [key: string]: Directive })[key]);
});
// 全局注册`@iconify/vue`图标库
import {
IconifyIconOffline,
IconifyIconOnline,
FontIcon
} from "./components/ReIcon";
app.component("IconifyIconOffline", IconifyIconOffline);
app.component("IconifyIconOnline", IconifyIconOnline);
app.component("FontIcon", FontIcon);
getServerConfig(app).then(async config => {
injectResponsiveStorage(app, config);
setupStore(app);
@@ -34,8 +44,7 @@ getServerConfig(app).then(async config => {
.use(MotionPlugin)
.use(useElementPlus)
.use(useTable)
.use(usI18n)
.use(useFontawesome);
.use(usI18n);
await router.isReady();
app.mount("#app");
});

View File

@@ -99,44 +99,6 @@ const components = [
ElTreeV2
];
// https://element-plus.org/zh-CN/component/icon.html
import {
Check,
Menu,
HomeFilled,
SetUp,
Edit,
Setting,
Lollipop,
Link,
Position,
Histogram,
RefreshRight,
ArrowDown,
Close,
CloseBold,
Bell
} from "@element-plus/icons-vue";
// Icon
export const iconComponents = [
Check,
Menu,
HomeFilled,
SetUp,
Edit,
Setting,
Lollipop,
Link,
Position,
Histogram,
RefreshRight,
ArrowDown,
Close,
CloseBold,
Bell
];
export function useElementPlus(app: App) {
// 注册组件
components.forEach((component: Component) => {
@@ -144,10 +106,7 @@ export function useElementPlus(app: App) {
});
// 注册指令
plugins.forEach(plugin => {
// @ts-ignore
app.use(plugin);
});
// 注册图标
iconComponents.forEach((component: Component) => {
app.component(component.name, component);
});
}

View File

@@ -1,21 +0,0 @@
/** 兼容fontawesome4和5版本
* 4版本: www.fontawesome.com.cn/faicons/
* 5版本https://fontawesome.com/v5.15/icons?d=gallery&p=2&m=free
* https://github.com/FortAwesome/vue-fontawesome
*/
import { App } from "vue";
import "font-awesome/css/font-awesome.css";
import { library } from "@fortawesome/fontawesome-svg-core";
import {
faUserSecret,
faCoffee,
faSpinner
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
// github.com/Remix-Design/RemixIcon/blob/master/README_CN.md#%E5%AE%89%E8%A3%85%E5%BC%95%E5%85%A5
import "remixicon/fonts/remixicon.css";
export function useFontawesome(app: App) {
library.add(faUserSecret, faCoffee, faSpinner);
app.component("font-awesome-icon", FontAwesomeIcon);
}

View File

@@ -1,4 +1,4 @@
// 菜单国际化配置
import { siphonI18n } from "./index";
// vxe-table组件国际化
import zhVxeTable from "vxe-table/lib/locale/lang/zh-CN";
import enVxeTable from "vxe-table/lib/locale/lang/en-US";
@@ -7,156 +7,18 @@ import enVxeTable from "vxe-table/lib/locale/lang/en-US";
import enLocale from "element-plus/lib/locale/lang/en";
import zhLocale from "element-plus/lib/locale/lang/zh-cn";
// 导航菜单配置
export const menusConfig = {
zh: {
message: {
hshome: "首页",
hssysManagement: "系统管理",
hsBaseinfo: "基础信息",
hsDict: "字典管理",
hseditor: "编辑器",
hserror: "错误页面",
hsfourZeroFour: "404",
hsfourZeroOne: "401",
hscomponents: "组件",
hsvideo: "视频组件",
hsmap: "地图组件",
hsdraggable: "拖拽组件",
hssplitPane: "切割面板",
hsbutton: "按钮组件",
hscropping: "图片裁剪",
hscountTo: "数字动画",
hsselector: "选择器组件",
hsflowChart: "流程图",
hsseamless: "无缝滚动",
hscontextmenu: "右键菜单",
hsmenus: "多级菜单",
hsmenu1: "菜单1",
"hsmenu1-1": "菜单1-1",
"hsmenu1-2": "菜单1-2",
"hsmenu1-2-1": "菜单1-2-1",
"hsmenu1-2-2": "菜单1-2-2",
"hsmenu1-3": "菜单1-3",
hsmenu2: "菜单2",
permission: "权限管理",
permissionPage: "页面权限",
permissionButton: "按钮权限",
hstabs: "标签页操作",
hsMenuTree: "菜单树结构",
externalLink: "外链"
}
},
en: {
message: {
hshome: "Home",
hssysManagement: "System Manage",
hsBaseinfo: "Base Info",
hsDict: "Dict Manage",
hseditor: "Editor",
hserror: "Error Page",
hsfourZeroFour: "404",
hsfourZeroOne: "401",
hscomponents: "Components",
hsvideo: "Video Components",
hsmap: "Map Components",
hsdraggable: "Draggable Components",
hssplitPane: "Split Pane",
hsbutton: "Button Components",
hscropping: "Picture Cropping",
hscountTo: "Digital Animation",
hsselector: "Selector Components",
hsflowChart: "Flow Chart",
hsseamless: "Seamless Scroll",
hscontextmenu: "Context Menu",
hsmenus: "MultiLevel Menu",
hsmenu1: "Menu1",
"hsmenu1-1": "Menu1-1",
"hsmenu1-2": "Menu1-2",
"hsmenu1-2-1": "Menu1-2-1",
"hsmenu1-2-2": "Menu1-2-2",
"hsmenu1-3": "Menu1-3",
hsmenu2: "Menu2",
permission: "Permission Manage",
permissionPage: "Page Permission",
permissionButton: "Button Permission",
hstabs: "Tabs Operate",
hsMenuTree: "Menu Tree",
externalLink: "External Link"
}
}
};
// 按钮配置
export const buttonConfig = {
zh: {
message: {
hsLoginOut: "退出系统",
hsfullscreen: "全屏",
hsexitfullscreen: "退出全屏",
hsrefreshRoute: "刷新路由",
hslogin: "登陆",
hsadd: "新增",
hsmark: "标记/取消",
hssave: "保存",
hssearch: "搜索",
hsexpendAll: "全部展开",
hscollapseAll: "全部折叠",
hssystemSet: "打开项目配置",
hsdelete: "删除",
hsreload: "重新加载",
hscloseCurrentTab: "关闭当前标签页",
hscloseLeftTabs: "关闭左侧标签页",
hscloseRightTabs: "关闭右侧标签页",
hscloseOtherTabs: "关闭其他标签页",
hscloseAllTabs: "关闭全部标签页"
}
},
en: {
message: {
hsLoginOut: "loginOut",
hsfullscreen: "fullScreen",
hsexitfullscreen: "exitFullscreen",
hsrefreshRoute: "refreshRoute",
hslogin: "login",
hsadd: "Add",
hsmark: "Mark/Cancel",
hssave: "Save",
hssearch: "Search",
hsexpendAll: "Expand All",
hscollapseAll: "Collapse All",
hssystemSet: "Open ProjectConfig",
hsdelete: "Delete",
hsreload: "Reload",
hscloseCurrentTab: "Close CurrentTab",
hscloseLeftTabs: "Close LeftTabs",
hscloseRightTabs: "Close RightTabs",
hscloseOtherTabs: "Close OtherTabs",
hscloseAllTabs: "Close AllTabs"
}
}
};
// 配置
// export const xxxx = {
// zh: {
// message: {},
// },
// en: {
// message: {},
// },
// };
const localesList = [menusConfig, buttonConfig];
// 项目内自定义国际化
const zhModules = import.meta.globEager("./zh-CN/**/*.ts");
const enModules = import.meta.globEager("./en/**/*.ts");
export const localesConfigs = {
zh: {
message: Object.assign({}, ...localesList.map(v => v.zh.message)),
...siphonI18n(zhModules, "zh-CN"),
...zhVxeTable,
...zhLocale
},
en: {
message: Object.assign({}, ...localesList.map(v => v.en.message)),
...siphonI18n(enModules, "en"),
...enVxeTable,
...enLocale
}

View File

@@ -0,0 +1,21 @@
export default {
hsLoginOut: "LoginOut",
hsfullscreen: "FullScreen",
hsexitfullscreen: "ExitFullscreen",
hsrefreshRoute: "RefreshRoute",
hslogin: "Login",
hsadd: "Add",
hsmark: "Mark/Cancel",
hssave: "Save",
hssearch: "Search",
hsexpendAll: "Expand All",
hscollapseAll: "Collapse All",
hssystemSet: "Open ProjectConfig",
hsdelete: "Delete",
hsreload: "Reload",
hscloseCurrentTab: "Close CurrentTab",
hscloseLeftTabs: "Close LeftTabs",
hscloseRightTabs: "Close RightTabs",
hscloseOtherTabs: "Close OtherTabs",
hscloseAllTabs: "Close AllTabs"
};

View File

@@ -0,0 +1,38 @@
export default {
hshome: "Home",
hslogin: "Login",
hssysManagement: "System Manage",
hsBaseinfo: "Base Info",
hsDict: "Dict Manage",
hseditor: "Editor",
hserror: "Error Page",
hsfourZeroFour: "404",
hsfourZeroOne: "401",
hscomponents: "Components",
hsvideo: "Video Components",
hsmap: "Map Components",
hsdraggable: "Draggable Components",
hssplitPane: "Split Pane",
hsbutton: "Button Components",
hscropping: "Picture Cropping",
hscountTo: "Digital Animation",
hsselector: "Selector Components",
hsflowChart: "Flow Chart",
hsseamless: "Seamless Scroll",
hscontextmenu: "Context Menu",
hsmenus: "MultiLevel Menu",
hsmenu1: "Menu1",
"hsmenu1-1": "Menu1-1",
"hsmenu1-2": "Menu1-2",
"hsmenu1-2-1": "Menu1-2-1",
"hsmenu1-2-2": "Menu1-2-2",
"hsmenu1-3": "Menu1-3",
hsmenu2: "Menu2",
permission: "Permission Manage",
permissionPage: "Page Permission",
permissionButton: "Button Permission",
hstabs: "Tabs Operate",
hsMenuTree: "Menu Tree",
hsguide: "Guide",
externalLink: "External Link"
};

View File

@@ -1,19 +1,10 @@
// 多组件库的国际化和本地项目国际化兼容
import { App } from "vue";
import { set } from "lodash-es";
import { createI18n } from "vue-i18n";
import { localesConfigs } from "./config";
import { storageLocal } from "/@/utils/storage";
export const i18n = createI18n({
locale: storageLocal.getItem("responsive-locale")?.locale ?? "zh",
fallbackLocale: "en",
messages: localesConfigs
});
export function usI18n(app: App) {
app.use(i18n);
}
/**
* 国际化转换工具函数
* @param message message
@@ -37,3 +28,47 @@ export function transformI18n(message: string | object = "", isI18n = false) {
return message;
}
}
/**
* 从模块中抽取国际化
* @param langs 存放国际化模块
* @param prefix 语言 默认 zh-CN
* @returns obj 格式:{模块名.**}
*/
export function siphonI18n(
langs: Record<string, Record<string, any>>,
prefix = "zh-CN"
) {
const langsObj: Recordable = {};
Object.keys(langs).forEach((key: string) => {
let fileName = key.replace(`./${prefix}/`, "").replace(/^\.\//, "");
fileName = fileName.substring(0, fileName.lastIndexOf("."));
const keyList = fileName.split("/");
const moduleName = keyList.shift();
const objKey = keyList.join(".");
const langFileModule = langs[key].default;
if (moduleName) {
if (objKey) {
set(langsObj, moduleName, langsObj[moduleName] || {});
set(langsObj[moduleName], objKey, langFileModule);
} else {
set(langsObj, moduleName, langFileModule || {});
}
}
});
return langsObj;
}
// 此函数只是配合i18n Ally插件来进行国际化智能提示并无实际意义只对提示起作用如果不需要国际化可删除
export const $t = (key: string) => key;
export const i18n = createI18n({
locale: storageLocal.getItem("responsive-locale")?.locale ?? "zh",
fallbackLocale: "en",
messages: localesConfigs
});
export function usI18n(app: App) {
app.use(i18n);
}

View File

@@ -0,0 +1,21 @@
export default {
hsLoginOut: "退出系统",
hsfullscreen: "全屏",
hsexitfullscreen: "退出全屏",
hsrefreshRoute: "刷新路由",
hslogin: "登陆",
hsadd: "新增",
hsmark: "标记/取消",
hssave: "保存",
hssearch: "搜索",
hsexpendAll: "全部展开",
hscollapseAll: "全部折叠",
hssystemSet: "打开项目配置",
hsdelete: "删除",
hsreload: "重新加载",
hscloseCurrentTab: "关闭当前标签页",
hscloseLeftTabs: "关闭左侧标签页",
hscloseRightTabs: "关闭右侧标签页",
hscloseOtherTabs: "关闭其他标签页",
hscloseAllTabs: "关闭全部标签页"
};

View File

@@ -0,0 +1,38 @@
export default {
hshome: "首页",
hslogin: "登陆",
hssysManagement: "系统管理",
hsBaseinfo: "基础信息",
hsDict: "字典管理",
hseditor: "编辑器",
hserror: "错误页面",
hsfourZeroFour: "404",
hsfourZeroOne: "401",
hscomponents: "组件",
hsvideo: "视频组件",
hsmap: "地图组件",
hsdraggable: "拖拽组件",
hssplitPane: "切割面板",
hsbutton: "按钮组件",
hscropping: "图片裁剪",
hscountTo: "数字动画",
hsselector: "选择器组件",
hsflowChart: "流程图",
hsseamless: "无缝滚动",
hscontextmenu: "右键菜单",
hsmenus: "多级菜单",
hsmenu1: "菜单1",
"hsmenu1-1": "菜单1-1",
"hsmenu1-2": "菜单1-2",
"hsmenu1-2-1": "菜单1-2-1",
"hsmenu1-2-2": "菜单1-2-2",
"hsmenu1-3": "菜单1-3",
hsmenu2: "菜单2",
permission: "权限管理",
permissionPage: "页面权限",
permissionButton: "按钮权限",
hstabs: "标签页操作",
hsMenuTree: "菜单树结构",
hsguide: "引导页",
externalLink: "外链"
};

View File

@@ -65,8 +65,8 @@ VXETable.setup({
i18n: (key, args) => i18n.global.t(key, args),
// 可选,对参数中的列头、校验提示..等进行自动翻译(只对支持国际化的有效)
translate(key, args) {
// 例如,只翻译 "message." 开头的键值
if (key && key.indexOf("message.") > -1) {
// 例如,只翻译 "buttons." 开头的键值
if (key && key.indexOf("buttons.") > -1) {
return i18n.global.t(key, args);
}
if (key && key.indexOf("el.") > -1) {

View File

@@ -89,7 +89,7 @@ router.beforeEach((to: toRouteType, _from, next) => {
});
};
// 未开启标签页缓存,刷新页面重定向到顶级路由(参考标签页操作例子,只针对静态路由)
if (to.meta?.realPath) {
if (to.meta?.refreshRedirect) {
const routes = router.options.routes;
const { refreshRedirect } = to.meta;
const { name, meta } = findRouteByPath(refreshRedirect, routes);
@@ -117,8 +117,9 @@ router.beforeEach((to: toRouteType, _from, next) => {
route?.meta?.rank !== 0 &&
routePartent.length === 0
) {
if (!route?.meta?.refreshRedirect) return;
const { name, meta } = findRouteByPath(
route?.meta?.refreshRedirect,
route.meta.refreshRedirect,
routes
);
handTag(

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const componentsRouter = {
@@ -6,10 +7,9 @@ const componentsRouter = {
component: Layout,
redirect: "/components/video",
meta: {
icon: "Menu",
title: "message.hscomponents",
icon: "menu",
title: $t("menus.hscomponents"),
i18n: true,
showLink: true,
rank: 4
},
children: [
@@ -18,8 +18,7 @@ const componentsRouter = {
name: "video",
component: () => import("/@/views/components/video/index.vue"),
meta: {
title: "message.hsvideo",
showLink: true,
title: $t("menus.hsvideo"),
i18n: true
}
},
@@ -28,8 +27,7 @@ const componentsRouter = {
name: "map",
component: () => import("/@/views/components/map/index.vue"),
meta: {
title: "message.hsmap",
showLink: true,
title: $t("menus.hsmap"),
keepAlive: true,
i18n: true,
transition: {
@@ -42,8 +40,7 @@ const componentsRouter = {
name: "draggable",
component: () => import("/@/views/components/draggable/index.vue"),
meta: {
title: "message.hsdraggable",
showLink: true,
title: $t("menus.hsdraggable"),
i18n: true,
transition: {
enterTransition: "animate__zoomIn",
@@ -57,8 +54,7 @@ const componentsRouter = {
name: "splitPane",
component: () => import("/@/views/components/split-pane/index.vue"),
meta: {
title: "message.hssplitPane",
showLink: true,
title: $t("menus.hssplitPane"),
i18n: true,
extraIcon: {
svg: true,
@@ -71,9 +67,8 @@ const componentsRouter = {
name: "button",
component: () => import("/@/views/components/button/index.vue"),
meta: {
title: "message.hsbutton",
i18n: true,
showLink: true
title: $t("menus.hsbutton"),
i18n: true
}
},
{
@@ -81,9 +76,8 @@ const componentsRouter = {
name: "cropping",
component: () => import("/@/views/components/cropping/index.vue"),
meta: {
title: "message.hscropping",
i18n: true,
showLink: true
title: $t("menus.hscropping"),
i18n: true
}
},
{
@@ -91,9 +85,8 @@ const componentsRouter = {
name: "countTo",
component: () => import("/@/views/components/count-to/index.vue"),
meta: {
title: "message.hscountTo",
i18n: true,
showLink: true
title: $t("menus.hscountTo"),
i18n: true
}
},
{
@@ -101,9 +94,8 @@ const componentsRouter = {
name: "selector",
component: () => import("/@/views/components/selector/index.vue"),
meta: {
title: "message.hsselector",
i18n: true,
showLink: true
title: $t("menus.hsselector"),
i18n: true
}
},
{
@@ -111,9 +103,8 @@ const componentsRouter = {
name: "seamlessScroll",
component: () => import("/@/views/components/seamless-scroll/index.vue"),
meta: {
title: "message.hsseamless",
i18n: true,
showLink: true
title: $t("menus.hsseamless"),
i18n: true
}
},
{
@@ -121,9 +112,8 @@ const componentsRouter = {
name: "contextmenu",
component: () => import("/@/views/components/contextmenu/index.vue"),
meta: {
title: "message.hscontextmenu",
i18n: true,
showLink: true
title: $t("menus.hscontextmenu"),
i18n: true
}
}
]

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const editorRouter = {
@@ -6,10 +7,9 @@ const editorRouter = {
component: Layout,
redirect: "/editor/index",
meta: {
icon: "Edit",
title: "message.hseditor",
icon: "edit",
title: $t("menus.hseditor"),
i18n: true,
showLink: true,
rank: 2
},
children: [
@@ -18,8 +18,7 @@ const editorRouter = {
name: "reEditor",
component: () => import("/@/views/editor/index.vue"),
meta: {
title: "message.hseditor",
showLink: true,
title: $t("menus.hseditor"),
i18n: true,
keepAlive: true,
extraIcon: {

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const errorRouter = {
@@ -6,9 +7,8 @@ const errorRouter = {
component: Layout,
redirect: "/error/401",
meta: {
icon: "Position",
title: "message.hserror",
showLink: true,
icon: "position",
title: $t("menus.hserror"),
i18n: true,
rank: 7
},
@@ -18,9 +18,8 @@ const errorRouter = {
name: "401",
component: () => import("/@/views/error/401.vue"),
meta: {
title: "message.hsfourZeroOne",
i18n: true,
showLink: true
title: $t("menus.hsfourZeroOne"),
i18n: true
}
},
{
@@ -28,9 +27,8 @@ const errorRouter = {
name: "404",
component: () => import("/@/views/error/404.vue"),
meta: {
title: "message.hsfourZeroFour",
i18n: true,
showLink: true
title: $t("menus.hsfourZeroFour"),
i18n: true
}
}
]

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const externalLink = {
@@ -5,9 +6,8 @@ const externalLink = {
name: "external",
component: Layout,
meta: {
icon: "Link",
title: "message.externalLink",
showLink: true,
icon: "link",
title: $t("menus.externalLink"),
i18n: true,
rank: 190
},
@@ -15,8 +15,7 @@ const externalLink = {
{
path: "https://github.com/xiaoxian521/vue-pure-admin",
meta: {
title: "message.externalLink",
showLink: true,
title: $t("menus.externalLink"),
i18n: true,
rank: 191
}

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const flowChartRouter = {
@@ -6,9 +7,8 @@ const flowChartRouter = {
component: Layout,
redirect: "/flowChart/index",
meta: {
icon: "SetUp",
title: "message.hsflowChart",
showLink: true,
icon: "set-up",
title: $t("menus.hsflowChart"),
i18n: true,
rank: 1
},
@@ -18,9 +18,8 @@ const flowChartRouter = {
name: "flowChart",
component: () => import("/@/views/flow-chart/index.vue"),
meta: {
title: "message.hsflowChart",
i18n: true,
showLink: true
title: $t("menus.hsflowChart"),
i18n: true
}
}
]

View File

@@ -0,0 +1,28 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const guideRouter = {
path: "/guide",
name: "reGuide",
component: Layout,
redirect: "/guide/index",
meta: {
icon: "guide",
title: $t("menus.hsguide"),
i18n: true,
rank: 10
},
children: [
{
path: "/guide/index",
name: "reGuide",
component: () => import("/@/views/guide/index.vue"),
meta: {
title: $t("menus.hsguide"),
i18n: true
}
}
]
};
export default guideRouter;

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const homeRouter = {
@@ -6,9 +7,8 @@ const homeRouter = {
component: Layout,
redirect: "/welcome",
meta: {
icon: "HomeFilled",
title: "message.hshome",
showLink: true,
icon: "home-filled",
title: $t("menus.hshome"),
i18n: true,
rank: 0
},
@@ -18,9 +18,8 @@ const homeRouter = {
name: "welcome",
component: () => import("/@/views/welcome.vue"),
meta: {
title: "message.hshome",
i18n: true,
showLink: true
title: $t("menus.hshome"),
i18n: true
}
}
]

View File

@@ -1,6 +1,7 @@
// 静态路由
import homeRouter from "./home";
import errorRouter from "./error";
import guideRouter from "./guide";
import editorRouter from "./editor";
import nestedRouter from "./nested";
import menuTreeRouter from "./menuTree";
@@ -20,6 +21,7 @@ import {
const routes = [
homeRouter,
errorRouter,
guideRouter,
nestedRouter,
externalLink,
editorRouter,

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const menuTreeRouter = {
@@ -6,10 +7,9 @@ const menuTreeRouter = {
component: Layout,
redirect: "/menuTree/index",
meta: {
icon: "RI-node-tree",
title: "message.hsMenuTree",
icon: "node-tree",
title: $t("menus.hsMenuTree"),
i18n: true,
showLink: true,
rank: 9
},
children: [
@@ -18,8 +18,7 @@ const menuTreeRouter = {
name: "reMenuTree",
component: () => import("/@/views/menu-tree/index.vue"),
meta: {
title: "message.hsMenuTree",
showLink: true,
title: $t("menus.hsMenuTree"),
i18n: true
}
}

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const nestedRouter = {
@@ -6,9 +7,8 @@ const nestedRouter = {
redirect: "/nested/menu1/menu1-1",
name: "Nested",
meta: {
title: "message.hsmenus",
icon: "Histogram",
showLink: true,
title: $t("menus.hsmenus"),
icon: "histogram",
i18n: true,
rank: 5
},
@@ -18,8 +18,7 @@ const nestedRouter = {
component: () => import("/@/layout/routerView/parent.vue"),
name: "Menu1",
meta: {
title: "message.hsmenu1",
showLink: true,
title: $t("menus.hsmenu1"),
i18n: true,
keepAlive: true
},
@@ -30,8 +29,7 @@ const nestedRouter = {
component: () => import("/@/views/nested/menu1/menu1-1/index.vue"),
name: "Menu1-1",
meta: {
title: "message.hsmenu1-1",
showLink: true,
title: $t("menus.hsmenu1-1"),
i18n: true,
keepAlive: true
}
@@ -42,8 +40,7 @@ const nestedRouter = {
name: "Menu1-2",
redirect: "/nested/menu1/menu1-2/menu1-2-1",
meta: {
title: "message.hsmenu1-2",
showLink: true,
title: $t("menus.hsmenu1-2"),
i18n: true,
keepAlive: true
},
@@ -54,8 +51,7 @@ const nestedRouter = {
import("/@/views/nested/menu1/menu1-2/menu1-2-1/index.vue"),
name: "Menu1-2-1",
meta: {
title: "message.hsmenu1-2-1",
showLink: true,
title: $t("menus.hsmenu1-2-1"),
i18n: true,
keepAlive: true
}
@@ -66,8 +62,7 @@ const nestedRouter = {
import("/@/views/nested/menu1/menu1-2/menu1-2-2/index.vue"),
name: "Menu1-2-2",
meta: {
title: "message.hsmenu1-2-2",
showLink: true,
title: $t("menus.hsmenu1-2-2"),
keepAlive: true,
i18n: true,
extraIcon: {
@@ -83,8 +78,7 @@ const nestedRouter = {
component: () => import("/@/views/nested/menu1/menu1-3/index.vue"),
name: "Menu1-3",
meta: {
title: "message.hsmenu1-3",
showLink: true,
title: $t("menus.hsmenu1-3"),
i18n: true,
keepAlive: true
}
@@ -96,8 +90,7 @@ const nestedRouter = {
name: "Menu2",
component: () => import("/@/views/nested/menu2/index.vue"),
meta: {
title: "message.hsmenu2",
showLink: true,
title: $t("menus.hsmenu2"),
i18n: true,
keepAlive: true
}

View File

@@ -1,3 +1,4 @@
import { $t } from "/@/plugins/i18n";
import Layout from "/@/layout/index.vue";
const remainingRouter = [
@@ -6,7 +7,7 @@ const remainingRouter = [
name: "login",
component: () => import("/@/views/login.vue"),
meta: {
title: "message.hslogin",
title: $t("menus.hslogin"),
showLink: false,
i18n: true,
rank: 101
@@ -17,8 +18,8 @@ const remainingRouter = [
name: "redirect",
component: Layout,
meta: {
icon: "HomeFilled",
title: "message.hshome",
icon: "home-filled",
title: $t("menus.hshome"),
i18n: true,
showLink: false,
rank: 104

View File

@@ -2,8 +2,8 @@ import { RouteLocationNormalized } from "vue-router";
export interface toRouteType extends RouteLocationNormalized {
meta: {
keepAlive: boolean;
keepAlive?: boolean;
refreshRedirect: string;
realPath: string;
dynamicLevel?: string;
};
}

View File

@@ -13,43 +13,43 @@ import { useTimeoutFn } from "@vueuse/core";
import { RouteConfigs } from "/@/layout/types";
import { usePermissionStoreHook } from "/@/store/modules/permission";
// https://cn.vitejs.dev/guide/features.html#glob-import
const modulesRoutes = import.meta.glob("/src/views/*/*/*.vue");
const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}");
// 动态路由
import { getAsyncRoutes } from "/@/api/routes";
// 按照路由中meta下的rank等级升序来排序路由
const ascending = (arr: any[]) => {
function ascending(arr: any[]) {
return arr.sort(
(a: { meta: { rank: number } }, b: { meta: { rank: number } }) => {
return a?.meta?.rank - b?.meta?.rank;
}
);
};
}
// 过滤meta中showLink为false的路由
const filterTree = (data: RouteComponent[]) => {
function filterTree(data: RouteComponent[]) {
const newTree = data.filter(
(v: { meta: { showLink: boolean } }) => v.meta.showLink
(v: { meta: { showLink: boolean } }) => v.meta?.showLink !== false
);
newTree.forEach(
(v: { children }) => v.children && (v.children = filterTree(v.children))
);
return newTree;
};
}
// 批量删除缓存路由(keepalive)
const delAliveRoutes = (delAliveRouteList: Array<RouteConfigs>) => {
function delAliveRoutes(delAliveRouteList: Array<RouteConfigs>) {
delAliveRouteList.forEach(route => {
usePermissionStoreHook().cacheOperate({
mode: "delete",
name: route?.name
});
});
};
}
// 通过path获取父级路径
const getParentPaths = (path: string, routes: RouteRecordRaw[]) => {
function getParentPaths(path: string, routes: RouteRecordRaw[]) {
// 深度遍历查找
function dfs(routes: RouteRecordRaw[], path: string, parents: string[]) {
for (let i = 0; i < routes.length; i++) {
@@ -70,10 +70,10 @@ const getParentPaths = (path: string, routes: RouteRecordRaw[]) => {
}
return dfs(routes, path, []);
};
}
// 查找对应path的路由信息
const findRouteByPath = (path: string, routes: RouteRecordRaw[]) => {
function findRouteByPath(path: string, routes: RouteRecordRaw[]) {
let res = routes.find((item: { path: string }) => item.path == path);
if (res) {
return res;
@@ -91,20 +91,20 @@ const findRouteByPath = (path: string, routes: RouteRecordRaw[]) => {
}
return null;
}
};
}
// 重置路由
const resetRouter = (): void => {
function resetRouter(): void {
router.getRoutes().forEach(route => {
const { name } = route;
if (name) {
router.hasRoute(name) && router.removeRoute(name);
}
});
};
}
// 初始化路由
const initRouter = (name: string) => {
function initRouter(name: string) {
return new Promise(resolve => {
getAsyncRoutes({ name }).then(({ info }) => {
if (info.length === 0) {
@@ -137,15 +137,15 @@ const initRouter = (name: string) => {
});
});
});
};
}
/**
* 将多级嵌套路由处理成一维数组
* @param routesList 传入路由
* @returns 返回处理后的一维路由
*/
const formatFlatteningRoutes = (routesList: RouteRecordRaw[]) => {
if (routesList.length <= 0) return routesList;
function formatFlatteningRoutes(routesList: RouteRecordRaw[]) {
if (routesList.length === 0) return routesList;
for (let i = 0; i < routesList.length; i++) {
if (routesList[i].children) {
routesList = routesList
@@ -154,7 +154,7 @@ const formatFlatteningRoutes = (routesList: RouteRecordRaw[]) => {
}
}
return routesList;
};
}
/**
* 一维数组处理成多级嵌套数组三级及以上的路由全部拍成二级keep-alive 只支持到二级缓存)
@@ -162,8 +162,8 @@ const formatFlatteningRoutes = (routesList: RouteRecordRaw[]) => {
* @param routesList 处理后的一维路由菜单数组
* @returns 返回将一维数组重新处理成规定路由的格式
*/
const formatTwoStageRoutes = (routesList: RouteRecordRaw[]) => {
if (routesList.length <= 0) return routesList;
function formatTwoStageRoutes(routesList: RouteRecordRaw[]) {
if (routesList.length === 0) return routesList;
const newRoutesList: RouteRecordRaw[] = [];
routesList.forEach((v: RouteRecordRaw) => {
if (v.path === "/") {
@@ -180,10 +180,10 @@ const formatTwoStageRoutes = (routesList: RouteRecordRaw[]) => {
}
});
return newRoutesList;
};
}
// 处理缓存路由(添加、删除、刷新)
const handleAliveRoute = (matched: RouteRecordNormalized[], mode?: string) => {
function handleAliveRoute(matched: RouteRecordNormalized[], mode?: string) {
switch (mode) {
case "add":
matched.forEach(v => {
@@ -207,30 +207,28 @@ const handleAliveRoute = (matched: RouteRecordNormalized[], mode?: string) => {
});
}, 100);
}
};
}
// 过滤后端传来的动态路由 重新生成规范路由
const addAsyncRoutes = (arrRoutes: Array<RouteRecordRaw>) => {
function addAsyncRoutes(arrRoutes: Array<RouteRecordRaw>) {
if (!arrRoutes || !arrRoutes.length) return;
const modulesRoutesKeys = Object.keys(modulesRoutes);
arrRoutes.forEach((v: RouteRecordRaw) => {
if (v.redirect) {
v.component = Layout;
} else {
if (v.meta.realPath) {
v.component = modulesRoutes[`/src/views${v.meta.realPath}/index.vue`];
} else {
v.component = modulesRoutes[`/src/views${v.path}/index.vue`];
}
const index = modulesRoutesKeys.findIndex(ev => ev.includes(v.path));
v.component = modulesRoutes[modulesRoutesKeys[index]];
}
if (v.children) {
addAsyncRoutes(v.children);
}
});
return arrRoutes;
};
}
// 获取路由历史模式 https://next.router.vuejs.org/zh/guide/essentials/history-mode.html
const getHistoryMode = (): RouterHistory => {
function getHistoryMode(): RouterHistory {
const routerHistory = loadEnv().VITE_ROUTER_HISTORY;
// len为1 代表只有历史模式 为2 代表历史模式中存在base参数 https://next.router.vuejs.org/zh/api/#%E5%8F%82%E6%95%B0-1
const historyMode = routerHistory.split(",");
@@ -251,10 +249,10 @@ const getHistoryMode = (): RouterHistory => {
return createWebHistory(rightMode);
}
}
};
}
// 是否有权限
const hasPermissions = (value: Array<string>): boolean => {
function hasPermissions(value: Array<string>): boolean {
if (value && value instanceof Array && value.length > 0) {
const roles = usePermissionStoreHook().buttonAuth;
const permissionRoles = value;
@@ -270,7 +268,7 @@ const hasPermissions = (value: Array<string>): boolean => {
} else {
return false;
}
};
}
export {
ascending,

View File

@@ -1,17 +1,17 @@
import { storageLocal } from "/@/utils/storage";
import { deviceDetection } from "/@/utils/deviceDetection";
import { store } from "/@/store";
import { appType } from "./types";
import { defineStore } from "pinia";
import { getConfig } from "/@/config";
import { storageLocal } from "/@/utils/storage";
import { deviceDetection } from "/@/utils/deviceDetection";
export const useAppStore = defineStore({
id: "pure-app",
state: (): appType => ({
sidebar: {
opened: storageLocal.getItem("sidebarStatus")
? !!+storageLocal.getItem("sidebarStatus")
: true,
opened:
storageLocal.getItem("responsive-layout")?.sidebarStatus ??
getConfig().SidebarStatus,
withoutAnimation: false,
isClickHamburger: false
},
@@ -30,20 +30,22 @@ export const useAppStore = defineStore({
},
actions: {
TOGGLE_SIDEBAR(opened?: boolean, resize?: string) {
const layout = storageLocal.getItem("responsive-layout");
if (opened && resize) {
this.sidebar.withoutAnimation = true;
this.sidebar.opened = true;
storageLocal.setItem("sidebarStatus", true);
layout.sidebarStatus = true;
} else if (!opened && resize) {
this.sidebar.withoutAnimation = true;
this.sidebar.opened = false;
storageLocal.setItem("sidebarStatus", false);
layout.sidebarStatus = false;
} else if (!opened && !resize) {
this.sidebar.withoutAnimation = false;
this.sidebar.opened = !this.sidebar.opened;
this.sidebar.isClickHamburger = !this.sidebar.opened;
storageLocal.setItem("sidebarStatus", this.sidebar.opened);
layout.sidebarStatus = this.sidebar.opened;
}
storageLocal.setItem("responsive-layout", layout);
},
TOGGLE_DEVICE(device: string) {
this.device = device;

View File

@@ -0,0 +1,30 @@
import { store } from "/@/store";
import { defineStore } from "pinia";
import { getConfig } from "/@/config";
import { storageLocal } from "/@/utils/storage";
export const useEpThemeStore = defineStore({
id: "pure-epTheme",
state: () => ({
epThemeColor:
storageLocal.getItem("responsive-layout")?.epThemeColor ??
getConfig().EpThemeColor
}),
getters: {
getEpThemeColor() {
return this.epThemeColor;
}
},
actions: {
setEpThemeColor(newColor) {
const layout = storageLocal.getItem("responsive-layout");
this.epThemeColor = newColor;
layout.epThemeColor = newColor;
storageLocal.setItem("responsive-layout", layout);
}
}
});
export function useEpThemeStoreHook() {
return useEpThemeStore(store);
}

View File

@@ -8,21 +8,20 @@ export const useMultiTagsStore = defineStore({
id: "pure-multiTags",
state: () => ({
// 存储标签页信息(路由信息)
multiTags: storageLocal.getItem("responsive-sets").multiTagsCache
multiTags: storageLocal.getItem("responsive-configure").multiTagsCache
? storageLocal.getItem("responsive-tags")
: [
{
path: "/welcome",
parentPath: "/",
meta: {
title: "message.hshome",
icon: "el-icon-s-home",
i18n: true,
showLink: true
title: "menus.hshome",
icon: "home-filled",
i18n: true
}
}
],
multiTagsCache: storageLocal.getItem("responsive-sets").multiTagsCache
multiTagsCache: storageLocal.getItem("responsive-configure").multiTagsCache
}),
getters: {
getMultiTagsCache() {
@@ -55,31 +54,30 @@ export const useMultiTagsStore = defineStore({
case "push":
{
const tagVal = value as multiType;
const tagPath = tagVal?.path;
// 判断tag是否已存在
const tagHasExits = this.multiTags.some(tag => {
return tag.path === tagVal?.path;
return tag.path === tagPath;
});
// 判断tag中的query键值是否相等
const tagQueryHasExits = this.multiTags.some(tag => {
return isEqual(tag.query, tagVal.query);
return isEqual(tag.query, tagVal?.query);
});
if (tagHasExits && tagQueryHasExits) return;
const meta = tagVal?.meta;
const dynamicLevel = meta?.dynamicLevel ?? -1;
const dynamicLevel = tagVal?.meta?.dynamicLevel ?? -1;
if (dynamicLevel > 0) {
// dynamicLevel动态路由可打开的数量
const realPath = meta?.realPath ?? "";
// 获取到已经打开的动态路由数, 判断是否大于dynamicLevel
if (
this.multiTags.filter(e => e.meta?.realPath ?? "" === realPath)
.length >= dynamicLevel
this.multiTags.filter(e => e?.path === tagPath).length >=
dynamicLevel
) {
// 关闭第一个
const index = this.multiTags.findIndex(
item => item.meta?.realPath === realPath
item => item?.path === tagPath
);
index !== -1 && this.multiTags.splice(index, 1);
}

View File

@@ -25,8 +25,8 @@ export type multiType = {
path: string;
parentPath: string;
name: string;
query: object;
meta: any;
query?: object;
};
export type setType = {

View File

@@ -58,10 +58,9 @@ export const useUserStore = defineStore({
path: "/welcome",
parentPath: "/",
meta: {
title: "message.hshome",
icon: "el-icon-s-home",
i18n: true,
showLink: true
title: "menus.hshome",
icon: "home-filled",
i18n: true
}
}
]);

29
src/style/dark.scss Normal file
View File

@@ -0,0 +1,29 @@
/* 暗黑模式 */
[data-theme="dark"] {
filter: invert(0.9) hue-rotate(180deg);
#mse,
img,
.icon-svg,
.login-container {
filter: invert(1) hue-rotate(180deg);
}
// element plus
.el-radio-button__original-radio:checked + .el-radio-button__inner,
.el-image-viewer__close,
.el-image-viewer__actions__inner,
.el-image-viewer__next,
.el-image-viewer__prev {
color: #000 !important;
}
.el-overlay {
background-color: rgba(0, 0, 0, 0.05) !important;
}
.el-drawer {
box-shadow: 0 8px 10px -5px rgb(0 0 0 / 1%), 0 16px 24px 2px rgb(0 0 0 / 2%),
0 6px 30px 5px rgb(0 0 0 / 1%);
}
}

View File

@@ -35,3 +35,30 @@
.is-dark {
z-index: 99999 !important;
}
/* 动态改变cssvar 用于主题切换 https://github.com/element-plus/element-plus/issues/4856#issuecomment-1000174357 */
.el-button--primary {
--el-button-bg-color: var(--el-color-primary) !important;
--el-button-border-color: var(--el-color-primary) !important;
--el-button-hover-bg-color: var(--el-color-primary-light-2) !important;
--el-button-hover-border-color: var(--el-color-primary-light-2) !important;
--el-button-active-bg-color: var(--el-color-primary-active) !important;
--el-button-active-border-color: var(--el-color-primary-active) !important;
}
/* nprogress适配ep的primary */
#nprogress {
& .bar {
background-color: var(--el-color-primary) !important;
}
& .peg {
box-shadow: 0 0 10px var(--el-color-primary),
0 0 5px var(--el-color-primary) !important;
}
& .spinner-icon {
border-top-color: var(--el-color-primary);
border-left-color: var(--el-color-primary);
}
}

View File

@@ -2,6 +2,7 @@
@import "./transition.scss";
@import "./element-plus.scss";
@import "./sidebar.scss";
@import "./dark.scss";
body {
width: 100%;

View File

@@ -6,7 +6,7 @@
z-index: -1;
}
.container {
.login-container {
width: 100vw;
height: 100vh;
display: grid;
@@ -85,7 +85,7 @@
align-items: center;
}
.icon i {
.icon svg {
color: #d9d9d9;
transition: 0.5s;
}
@@ -107,7 +107,7 @@
padding: 0;
}
.input-group.focus .icon i {
.input-group.focus .icon svg {
color: #5392f0;
}
@@ -183,7 +183,7 @@ a:hover {
}
@media screen and (max-width: 1080px) {
.container {
.login-container {
grid-gap: 9rem;
}
}
@@ -217,7 +217,7 @@ a:hover {
display: none;
}
.container {
.login-container {
grid-template-columns: 1fr;
}

View File

@@ -306,6 +306,10 @@
height: 48px;
line-height: 48px;
background: $menuBg;
svg {
position: static !important;
}
}
.is-active > .el-sub-menu__title,
@@ -568,6 +572,7 @@
body[layout="vertical"] {
$sideBarWidth: 210px;
@include merge-style($sideBarWidth);
.el-menu--collapse {
width: 54px;
}
@@ -599,16 +604,6 @@ body[layout="vertical"] {
}
}
.el-sub-menu {
overflow: hidden;
& > .el-sub-menu__title {
.el-sub-menu__icon-arrow {
display: none;
}
}
}
/* 菜单折叠 */
.el-menu--collapse {
.el-sub-menu {

View File

@@ -1,21 +0,0 @@
interface ProxyAlgorithm {
increaseIndexes<T>(val: Array<T>): Array<T>;
}
class algorithmProxy implements ProxyAlgorithm {
constructor() {}
// 数组每一项添加索引字段
public increaseIndexes<T>(val: Array<T>): Array<T> {
return Object.keys(val)
.map(v => {
return {
...val[v],
key: v
};
})
.filter(v => v.meta && v.meta.showLink);
}
}
export const algorithm = new algorithmProxy();

View File

@@ -18,8 +18,8 @@ http.request('get', '/xxx?message=' + msg);
import { http } from "/@/utils/http";
// params传参
http.request('get', '/xxx', { params: param });
http.request('post', '/xxx', { params: param });
// data传参
http.request('get', '/xxx', { data: param });
http.request('post', '/xxx', { data: param });
```

View File

@@ -17,15 +17,20 @@ export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
type: Object,
default: Storage.getData(undefined, "layout") ?? {
layout: config.Layout ?? "vertical",
theme: config.Theme ?? "default"
theme: config.Theme ?? "default",
darkMode: config.DarkMode ?? false,
sidebarStatus: config.SidebarStatus ?? true,
epThemeColor: config.EpThemeColor ?? "409EFF"
}
},
sets: {
configure: {
type: Object,
default: Storage.getData(undefined, "sets") ?? {
default: Storage.getData(undefined, "configure") ?? {
grey: config.Grey ?? false,
weak: config.Weak ?? false,
hideTabs: config.HideTabs ?? false,
showLogo: config.ShowLogo ?? true,
showModel: config.ShowModel ?? "smart",
multiTagsCache: config.MultiTagsCache ?? false
}
}
@@ -40,10 +45,9 @@ export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
path: "/welcome",
parentPath: "/",
meta: {
title: "message.hshome",
title: "menus.hshome",
i18n: true,
icon: "HomeFilled",
showLink: true
icon: "home-filled"
}
}
]

93
src/views/guide/index.vue Normal file
View File

@@ -0,0 +1,93 @@
<script lang="ts">
export default {
name: "reGuide"
};
</script>
<script setup lang="ts">
import Driver from "driver.js";
import "driver.js/dist/driver.min.css";
// 步骤
const steps = [
{
element: "#header-notice",
popover: {
title: "消息通知",
description: "你可以在这里查看管理员发送的消息",
position: "left"
}
},
{
element: "#header-screenfull",
popover: {
title: "全屏",
description: "你可以在这里进行全屏切换",
position: "left"
}
},
{
element: "#header-translation",
popover: {
title: "国际化",
description: "你可以在这里进行语言切换",
position: "left"
}
},
{
element: ".el-icon-setting",
popover: {
title: "项目配置",
description: "你可以在这里查看项目配置",
position: "left"
}
},
{
element: ".tags-view",
popover: {
title: "多标签页",
description: "这里是你访问过的页面的历史",
position: "buttom"
}
}
];
const driver = new Driver({
className: "scoped-class",
animate: true,
opacity: 0.75,
padding: 0,
allowClose: true,
overlayClickNext: false,
doneBtnText: "完成",
closeBtnText: "关闭",
nextBtnText: "下一步",
prevBtnText: "上一步"
});
const guide = () => {
driver.defineSteps(steps);
driver.start();
};
</script>
<template>
<div>
<p>引导页对于一些第一次进入项目的人很有用你可以简单介绍下项目的功能</p>
<el-button
type="primary"
style="margin-top: 10px"
@click.prevent.stop="guide"
>
打开引导页
</el-button>
</div>
</template>
<style>
div#driver-highlighted-element-stage,
div#driver-page-overlay {
background: transparent !important;
outline: 5000px solid rgba(0, 0, 0, 0.75);
}
</style>

View File

@@ -5,14 +5,14 @@ import { initRouter } from "/@/router/utils";
import { storageSession } from "/@/utils/storage";
import { addClass, removeClass } from "/@/utils/operate";
import bg from "/@/assets/login/bg.png";
import avatar from "/@/assets/login/avatar.svg";
import illustration0 from "/@/assets/login/illustration0.svg";
import illustration1 from "/@/assets/login/illustration1.svg";
import illustration2 from "/@/assets/login/illustration2.svg";
import illustration3 from "/@/assets/login/illustration3.svg";
import illustration4 from "/@/assets/login/illustration4.svg";
import illustration5 from "/@/assets/login/illustration5.svg";
import illustration6 from "/@/assets/login/illustration6.svg";
import avatar from "/@/assets/login/avatar.svg?component";
import illustration0 from "/@/assets/login/illustration0.svg?component";
import illustration1 from "/@/assets/login/illustration1.svg?component";
import illustration2 from "/@/assets/login/illustration2.svg?component";
import illustration3 from "/@/assets/login/illustration3.svg?component";
import illustration4 from "/@/assets/login/illustration4.svg?component";
import illustration5 from "/@/assets/login/illustration5.svg?component";
import illustration6 from "/@/assets/login/illustration6.svg?component";
const router = useRouter();
@@ -71,7 +71,7 @@ function onPwdBlur() {
<template>
<img :src="bg" class="wave" />
<div class="container">
<div class="login-container">
<div class="img">
<component :is="currentWeek"></component>
</div>
@@ -110,7 +110,7 @@ function onPwdBlur() {
}"
>
<div class="icon">
<i class="fa fa-user"></i>
<IconifyIconOffline icon="fa-user" width="14" height="14" />
</div>
<div>
<h5>用户名</h5>
@@ -139,7 +139,7 @@ function onPwdBlur() {
}"
>
<div class="icon">
<i class="fa fa-lock"></i>
<IconifyIconOffline icon="fa-lock" width="14" height="14" />
</div>
<div>
<h5>密码</h5>

View File

@@ -1,7 +1,7 @@
<template>
<div>
<p>{{ $t("message.hsmenu1") }}</p>
<p style="text-indent: 2em">{{ $t("message.hsmenu1-1") }}</p>
<p>{{ $t("menus.hsmenu1") }}</p>
<p style="text-indent: 2em">{{ $t("menus.hsmenu1-1") }}</p>
<el-input v-model="input" />
</div>
</template>

View File

@@ -1,8 +1,8 @@
<template>
<div>
<p>{{ $t("message.hsmenu1") }}</p>
<p style="text-indent: 2em">{{ $t("message.hsmenu1-2") }}</p>
<p style="text-indent: 4em">{{ $t("message.hsmenu1-2-1") }}</p>
<p>{{ $t("menus.hsmenu1") }}</p>
<p style="text-indent: 2em">{{ $t("menus.hsmenu1-2") }}</p>
<p style="text-indent: 4em">{{ $t("menus.hsmenu1-2-1") }}</p>
<el-input v-model="input" />
</div>
</template>

View File

@@ -1,8 +1,8 @@
<template>
<div>
<p>{{ $t("message.hsmenu1") }}</p>
<p style="text-indent: 2em">{{ $t("message.hsmenu1-2") }}</p>
<p style="text-indent: 4em">{{ $t("message.hsmenu1-2-2") }}</p>
<p>{{ $t("menus.hsmenu1") }}</p>
<p style="text-indent: 2em">{{ $t("menus.hsmenu1-2") }}</p>
<p style="text-indent: 4em">{{ $t("menus.hsmenu1-2-2") }}</p>
<el-input v-model="input" />
</div>
</template>

View File

@@ -1,7 +1,7 @@
<template>
<div>
<p>{{ $t("message.hsmenu1") }}</p>
<p style="text-indent: 2em">{{ $t("message.hsmenu1-3") }}</p>
<p>{{ $t("menus.hsmenu1") }}</p>
<p style="text-indent: 2em">{{ $t("menus.hsmenu1-3") }}</p>
<el-input v-model="input" />
</div>
</template>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<p>{{ $t("message.hsmenu2") }}</p>
<p>{{ $t("menus.hsmenu2") }}</p>
<el-input v-model="input" />
</div>
</template>

View File

@@ -101,13 +101,13 @@ const checkboxChangeEvent: VxeTableEvents.CheckboxChange = ({ records }) => {
<template #default="{ row }">
<vxe-button
type="text"
icon="el-icon-edit"
icon="fa fa-pencil-square-o"
@click="editConfig(row)"
>编辑</vxe-button
>
<vxe-button
type="text"
icon="el-icon-delete"
icon="fa fa-trash-o"
@click="delConfig(row)"
>删除</vxe-button
>
@@ -140,7 +140,7 @@ const checkboxChangeEvent: VxeTableEvents.CheckboxChange = ({ records }) => {
<span class="select-count"
>已选中{{ configData.selectRecords.length }}</span
>
<vxe-button size="small">{{ $t("message.hsdelete") }}</vxe-button>
<vxe-button size="small">{{ $t("buttons.hsdelete") }}</vxe-button>
</span>
</template>
</vxe-pager>
@@ -179,10 +179,4 @@ const checkboxChangeEvent: VxeTableEvents.CheckboxChange = ({ records }) => {
:deep(.el-divider--horizontal) {
margin: 13px 0;
}
:deep(.el-icon-close) {
&:hover {
color: red;
}
}
</style>

View File

@@ -209,28 +209,28 @@ function handleClose() {
<template #buttons>
<vxe-input
v-model="dictData.filterName"
:placeholder="$t('message.hssearch')"
:placeholder="$t('buttons.hssearch')"
@keyup="searchEvent"
></vxe-input>
</template>
<template #tools>
<vxe-button
icon="el-icon-circle-plus-outline"
icon="fa fa-plus-square-o"
status="primary"
@click="onAdd"
>{{ $t("message.hsadd") }}</vxe-button
>{{ $t("buttons.hsadd") }}</vxe-button
>
<vxe-button
icon="el-icon-folder-opened"
icon="fa fa-folder-open-o"
status="primary"
@click="$refs.xTree.setAllTreeExpand(true)"
>{{ $t("message.hsexpendAll") }}</vxe-button
>{{ $t("buttons.hsexpendAll") }}</vxe-button
>
<vxe-button
icon="el-icon-folder"
icon="fa fa-folder-o"
status="primary"
@click="$refs.xTree.clearTreeExpand()"
>{{ $t("message.hscollapseAll") }}</vxe-button
>{{ $t("buttons.hscollapseAll") }}</vxe-button
>
</template>
</vxe-toolbar>
@@ -266,23 +266,26 @@ function handleClose() {
</vxe-table-column>
<vxe-table-column title="操作" width="330" fixed="right">
<template #default="{ row }">
<vxe-button type="text" icon="el-icon-edit" @click="onEdit(row)"
<vxe-button
type="text"
icon="fa fa-pencil-square-o"
@click="onEdit(row)"
>编辑</vxe-button
>
<vxe-button
type="text"
icon="el-icon-circle-plus-outline"
icon="fa fa-plus-square-o"
@click="onAddChild(row)"
>新增子类型</vxe-button
>
<vxe-button
v-show="row.model"
type="text"
icon="el-icon-setting"
icon="fa fa-cog"
@click="onDeploy(row)"
>字典配置</vxe-button
>
<vxe-button type="text" icon="el-icon-delete" @click="confirmEvent"
<vxe-button type="text" icon="fa fa-trash-o" @click="confirmEvent"
>删除</vxe-button
>
</template>

View File

@@ -6,6 +6,7 @@ export default {
<script setup lang="ts">
import { reactive } from "vue";
import { $t } from "/@/plugins/i18n";
import { VxeGridProps } from "vxe-table";
const gridOptions = reactive({
@@ -29,19 +30,19 @@ const gridOptions = reactive({
buttons: [
{
code: "insert_actived",
name: "message.hsadd",
name: $t("buttons.hsadd"),
status: "perfect",
icon: "fa fa-plus"
},
{
code: "mark_cancel",
name: "message.hsmark",
name: $t("buttons.hsmark"),
status: "perfect",
icon: "fa fa-trash-o"
},
{
code: "save",
name: "message.hssave",
name: $t("buttons.hssave"),
status: "perfect",
icon: "fa fa-save"
}

View File

@@ -17,8 +17,7 @@ function toDetail(index: number) {
title: { zh: `No.${index} - 详情信息`, en: `No.${index} - DetailInfo` },
showLink: false,
i18n: false,
dynamicLevel: 3,
realPath: "/tabs/detail"
dynamicLevel: 3
}
});
router.push({ name: "tabDetail", query: { id: String(index) } });
@@ -31,12 +30,7 @@ function toDetail(index: number) {
title="标签页复用超出限制自动关闭(使用场景: 动态路由)"
name="tag"
>
<el-button
v-for="index in 6"
:key="index"
size="medium"
@click="toDetail(index)"
>
<el-button v-for="index in 6" :key="index" @click="toDetail(index)">
打开{{ index }}详情页
</el-button>
</el-collapse-item>

View File

@@ -63,9 +63,7 @@ const openDepot = (): void => {
>
<el-card>
<template #header>
<div>
<span>GitHub信息</span>
</div>
<span style="font-size: 16px; font-weight: 500">GitHub信息</span>
</template>
<el-skeleton animated :rows="7" :loading="loading">
<template #default>
@@ -97,9 +95,9 @@ const openDepot = (): void => {
>
<el-card>
<template #header>
<div>
<span>GitHub滚动信息</span>
</div>
<span style="font-size: 16px; font-weight: 500"
>GitHub滚动信息</span
>
</template>
<el-skeleton animated :rows="7" :loading="loading">
<template #default>
@@ -131,9 +129,9 @@ const openDepot = (): void => {
>
<el-card>
<template #header>
<div>
<span>GitHub饼图信息</span>
</div>
<span style="font-size: 16px; font-weight: 500"
>GitHub饼图信息</span
>
</template>
<el-skeleton animated :rows="7" :loading="loading">
<template #default>
@@ -165,9 +163,9 @@ const openDepot = (): void => {
>
<el-card>
<template #header>
<div>
<span>GitHub折线图信息</span>
</div>
<span style="font-size: 16px; font-weight: 500"
>GitHub折线图信息</span
>
</template>
<el-skeleton animated :rows="7" :loading="loading">
<template #default>
@@ -199,9 +197,9 @@ const openDepot = (): void => {
>
<el-card>
<template #header>
<div>
<span>GitHub柱状图信息</span>
</div>
<span style="font-size: 16px; font-weight: 500"
>GitHub柱状图信息</span
>
</template>
<el-skeleton animated :rows="7" :loading="loading">
<template #default>

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