Compare commits

..

22 Commits
2.8.0 ... v3.0

Author SHA1 Message Date
xiaoxian521
3e8dfb2bcb release: update 3.0 2022-02-14 23:19:32 +08:00
xiaoxian521
6fb0f7ef4d fix: mixNav route 2022-02-14 23:12:04 +08:00
xiaoxian521
08983fdbc1 fix: 固定vite@2.7.13,2.8+打包有问题 2022-02-14 22:51:36 +08:00
啝裳
ef05b2b614 feat: add mix nav (#187)
feat: add mix nav
2022-02-14 22:09:39 +08:00
xiaoxian521
a2dde02994 docs: update 2022-02-10 13:13:38 +08:00
rich1e
7544cf058e fix: epThemeColor error
Closes #183
2022-02-10 12:54:16 +08:00
xiaoxian521
43193fd2b6 docs: update 2022-02-10 12:01:36 +08:00
xiaoxian521
25c55c5d1d chore: update eslint@8.8.0 2022-02-07 15:59:04 +08:00
xiaoxian521
217258b972 chore: update element-plus@2.0.0 2022-02-07 13:43:54 +08:00
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
93 changed files with 3254 additions and 3017 deletions

View File

@@ -37,7 +37,7 @@ module.exports = {
"eslint:recommended",
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/prettier/@typescript-eslint"
"@vue/eslint-config-typescript"
],
parser: "vue-eslint-parser",
parserOptions: {
@@ -50,6 +50,10 @@ module.exports = {
}
},
rules: {
"vue/no-v-html": "off",
"vue/require-default-prop": "off",
"vue/require-explicit-emits": "off",
"vue/multi-word-component-names": "off",
"@typescript-eslint/no-explicit-any": "off", // any
"no-debugger": "off",
"@typescript-eslint/explicit-module-boundary-types": "off", // setup()

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"],

View File

@@ -1,7 +1,6 @@
module.exports = {
bracketSpacing: true,
jsxBracketSameLine: true,
singleQuote: false,
arrowParens: 'avoid',
trailingComma: 'none'
arrowParens: "avoid",
trailingComma: "none"
};

View File

@@ -1,11 +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"
]
}

17
.vscode/settings.json vendored
View File

@@ -1,15 +1,4 @@
{
/** 便
* ESLint
* Prettier - Code formatter
* stylelint
* vscode-icons
* i18n Ally
* Iconify IntelliSense
* TypeScript Vue Plugin (Volar)
* Vue Language Features (Volar)
*/
"terminal.integrated.rendererType": "dom",
"editor.formatOnType": true,
"editor.formatOnSave": true,
"javascript.updateImportsOnFileMove.enabled": "always",
@@ -27,26 +16,20 @@
"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.keystyle": "nested",

View File

@@ -1,3 +1,35 @@
# 3.0 (2022-2-14)
### 🎫 Feat
- Added mix navigation
### 🐞 Bug fixes
- Fix tab page bug
# 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

View File

@@ -1,3 +1,35 @@
# 3.0 (2022-2-14)
### 🎫 Feat
- Added mix navigation
### 🐞 Bug fixes
- Fix tab page bug
# 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

View File

@@ -1,3 +1,35 @@
# 3.0 (2022-2-14)
### 🎫 Feat
- 添加混合导航
### 🐞 Bug fixes
- 修复标签页 bug
# 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

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
@@ -122,21 +124,15 @@ If you think this project is helpful to you, you can help the author buy a cup o
<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
In principle, no fees and copyrights are charged, so you can use it with confidence
In principle, no fees and copyrights are charged, and you can use it with confidence, but if you need secondary open source, please contact the author for permission!
[MIT © xiaoxian521-2020](./LICENSE)
## Backers
Thank you very much for your support, I believe the project will get better and better! ! ! :heart:
Thank you very much for your support, I believe the project will get better and better :heart:
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen |
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: |
@@ -144,6 +140,6 @@ Thank you very much for your support, I believe the project will get better and
## Contributors
This project exists thanks to all the people who contribute!!! :heart:
This project exists thanks to all the people who contribute :heart:
<a href="https://github.com/xiaoxian521/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=xiaoxian521/vue-pure-admin" /></a>

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)
## 预览
@@ -122,21 +124,21 @@ pnpm build
<img src="http://yiming_chang.gitee.io/manages/pay.jpg" width="150px" height="150px" />
## 微信交流群
## QQ 交流群
为了项目更好的发展,你可选择捐赠 10 元后添加下图微信拉你进群,添加后请自觉发捐赠截图
群里严禁`黄``赌``毒``vpn`等违法行为!
<img src="http://yiming_chang.gitee.io/manages/kf.jpg" width="150px" height="195px" />
<img src="http://yiming_chang.gitee.io/manages/qq.jpg" width="150px" height="225px" />
## 许可证
原则上不收取任何费用及版权,可以放心使用
原则上不收取任何费用及版权,可以放心使用,不过如需二次开源(比如用此平台二次开发并开源)请联系作者获取许可!
[MIT © xiaoxian521-2020](./LICENSE)
## 捐赠者
非常感谢你们的支持,相信项目会越来越好:heart:
非常感谢你们的支持,相信项目会越来越好 :heart:
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen |
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: |
@@ -144,6 +146,6 @@ pnpm build
## 贡献者
这个项目的存在感谢所有做出贡献的人:heart:
这个项目的存在感谢所有做出贡献的人 :heart:
<a href="https://github.com/xiaoxian521/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=xiaoxian521/vue-pure-admin" /></a>

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

@@ -7,10 +7,9 @@ const systemRouter = {
name: "system",
redirect: "/system/user/index",
meta: {
icon: "Setting",
icon: "setting",
title: "menus.hssysManagement",
i18n: true,
showLink: true,
rank: 6
},
children: [
@@ -19,8 +18,7 @@ const systemRouter = {
name: "user",
meta: {
title: "menus.hsBaseinfo",
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -29,7 +27,6 @@ const systemRouter = {
meta: {
title: "menus.hsDict",
i18n: true,
showLink: true,
keepAlive: true
}
}
@@ -42,9 +39,8 @@ const permissionRouter = {
redirect: "/permission/page/index",
meta: {
title: "menus.permission",
icon: "Lollipop",
icon: "lollipop",
i18n: true,
showLink: true,
rank: 3
},
children: [
@@ -53,8 +49,7 @@ const permissionRouter = {
name: "permissionPage",
meta: {
title: "menus.permissionPage",
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -63,7 +58,6 @@ const permissionRouter = {
meta: {
title: "menus.permissionButton",
i18n: true,
showLink: true,
authority: []
}
}
@@ -78,7 +72,6 @@ const tabsRouter = {
icon: "IF-team-icontabs",
title: "menus.hstabs",
i18n: true,
showLink: true,
rank: 8
},
children: [
@@ -87,7 +80,6 @@ const tabsRouter = {
name: "reTabs",
meta: {
title: "menus.hstabs",
showLink: true,
i18n: true
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "vue-pure-admin",
"version": "2.8.0",
"version": "3.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"
},
@@ -30,42 +31,37 @@
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@ctrl/tinycolor": "^3.4.0",
"@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",
"@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.6.2",
"@vueuse/motion": "^2.0.0-beta.9",
"@vueuse/shared": "^7.6.2",
"animate.css": "^4.1.1",
"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.3.0-beta.1",
"echarts": "^5.3.0",
"element-plus": "^2.0.2",
"element-resize-detector": "^1.2.3",
"font-awesome": "^4.7.0",
"js-cookie": "^3.0.1",
"lodash-es": "^4.17.21",
"mitt": "^3.0.0",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"path": "^0.12.7",
"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",
"rgb-hex": "^4.0.0",
"v-contextmenu": "3.0.0",
"vue": "^3.2.24",
"vue-i18n": "^9.2.0-beta.3",
"vue": "^3.2.31",
"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.1.18",
"wangeditor": "^4.7.9",
@@ -75,47 +71,59 @@
"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",
"@types/node": "14.14.14",
"@types/nprogress": "0.2.0",
"@types/qs": "^6.9.7",
"@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",
"@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",
"@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2",
"@vitejs/plugin-legacy": "^1.7.0",
"@vitejs/plugin-vue": "^2.2.0",
"@vitejs/plugin-vue-jsx": "^1.3.4",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"@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",
"eslint-plugin-vue": "7.17.0",
"husky": "7.0.2",
"eslint": "^8.8.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.4.1",
"husky": "^7.0.4",
"lint-staged": "11.1.2",
"postcss": "8.2.6",
"postcss": "^8.4.6",
"postcss-html": "^1.3.0",
"postcss-import": "14.0.0",
"prettier": "2.3.2",
"postcss-scss": "^4.0.3",
"prettier": "^2.5.1",
"pretty-quick": "3.1.1",
"rimraf": "3.0.2",
"sass": "^1.45.0",
"sass-loader": "^12.3.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",
"rollup-plugin-visualizer": "^5.5.4",
"sass": "^1.49.7",
"sass-loader": "^12.4.0",
"stylelint": "^14.3.0",
"stylelint-config-html": "^1.0.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-recommended": "^6.0.0",
"stylelint-config-standard": "^24.0.0",
"stylelint-order": "^5.0.0",
"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.7.0",
"vite-svg-loader": "2.2.0",
"vue-eslint-parser": "^8.2.0",
"windicss": "^3.4.3"
},
"repository": "git@github.com:xiaoxian521/vue-pure-admin.git",
"author": "xiaoxian521",

3360
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.8.0",
"Version": "3.0",
"Title": "PureAdmin",
"FixedHeader": true,
"HiddenSideBar": false,

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>

View File

@@ -13,21 +13,27 @@ const lists = ref([
<el-descriptions class="margin-top" direction="vertical" :column="3" border>
<el-descriptions-item>
<template #label>
<el-icon><user /></el-icon>
<el-icon>
<IconifyIconOffline icon="user" />
</el-icon>
用户名
</template>
xiaoxian
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<el-icon><iphone /></el-icon>
<el-icon>
<IconifyIconOffline icon="iphone" />
</el-icon>
手机号
</template>
123456789
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<el-icon><location /></el-icon>
<el-icon>
<IconifyIconOffline icon="location" />
</el-icon>
居住地
</template>
上海
@@ -36,7 +42,9 @@ const lists = ref([
<el-descriptions class="margin-top" direction="vertical" :column="2" border>
<el-descriptions-item>
<template #label>
<el-icon><tickets /></el-icon>
<el-icon>
<IconifyIconOffline icon="tickets" />
</el-icon>
标签
</template>
<el-tag
@@ -51,7 +59,9 @@ const lists = ref([
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<el-icon><office-building /></el-icon>
<el-icon>
<IconifyIconOffline icon="office-building" />
</el-icon>
联系地址
</template>
上海市徐汇区
@@ -60,7 +70,9 @@ const lists = ref([
<el-descriptions class="margin-top" direction="vertical" :column="1" border>
<el-descriptions-item>
<template #label>
<el-icon><notebook /></el-icon>
<el-icon>
<IconifyIconOffline icon="notebook" />
</el-icon>
留言
</template>
好好学习天天向上

View File

@@ -169,7 +169,8 @@ export default defineComponent({
style={{
color: props.color,
fontSize: props.fontSize
}}>
}}
>
{state.displayValue}
</span>
</>

View File

@@ -43,7 +43,8 @@ export default defineComponent({
<div
class="scroll-num"
// @ts-ignore
style={{ "--i": props.i, "--delay": props.delay }}>
style={{ "--i": props.i, "--delay": props.delay }}
>
<ul ref="ul" style={{ fontSize: "32px" }}>
<li>0</li>
<li>1</li>

View File

@@ -133,7 +133,8 @@ export default defineComponent({
<>
<div
class={useAttrs({ excludeListeners: true, excludeKeys: ["class"] })}
style={this.getWrapperStyle}>
style={this.getWrapperStyle}
>
<img
ref="imgElRef"
src={this.props.src}

View File

@@ -9,7 +9,7 @@
background: #fff;
font-size: 66px;
color: #fff;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
box-shadow: 0 0 6px rgb(0 0 0 / 50%);
text-align: center;
font-family: "Helvetica Neue";
}
@@ -58,7 +58,7 @@
.m-flipper.down.go .front::before {
transform-origin: 50% 100%;
animation: frontFlipDown 0.6s ease-in-out both;
box-shadow: 0 -2px 6px rgba(255, 255, 255, 0.3);
box-shadow: 0 -2px 6px rgb(255 255 255 / 30%);
backface-visibility: hidden;
}
@@ -85,7 +85,7 @@
.m-flipper.up.go .front::after {
transform-origin: 50% 0;
animation: frontFlipUp 0.6s ease-in-out both;
box-shadow: 0 2px 6px rgba(255, 255, 255, 0.3);
box-shadow: 0 2px 6px rgb(255 255 255 / 30%);
backface-visibility: hidden;
}

View File

@@ -1,98 +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) {
// fontawesome4
const fa4Reg = /^fa-/;
// fontawesome5+
const fa5Reg = /^FA-/;
// iconfont
const iFReg = /^IF-/;
// remixicon
const riReg = /^RI-/;
// typeof icon === "function" 属于SVG
if (fa5Reg.test(icon)) {
const text = icon.split(fa5Reg)[1];
return findIcon(
text.slice(0, text.indexOf(" ") == -1 ? text.length : text.indexOf(" ")),
"FA",
text.slice(text.indexOf(" ") + 1, text.length)
);
} else if (fa4Reg.test(icon)) {
return findIcon(icon.split(fa4Reg)[1], "fa");
} 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 === "fa") {
return defineComponent({
name: "faIcon",
data() {
return { icon: `fa ${icon}` };
},
template: `<i :class="icon" />`
});
} 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

@@ -299,10 +299,12 @@ export default defineComponent({
cursor: unref(rateDisabled) ? "auto" : "pointer",
textAlign: "center"
}}
key={key}>
key={key}
>
<div
ref={`hsdiv${props.HsKey}${key}`}
class={`hs-item ${[unref(classes)[key] + key]}`}>
class={`hs-item ${[unref(classes)[key] + key]}`}
>
<span>{item}</span>
</div>
</td>

View File

@@ -111,20 +111,24 @@ export default defineComponent({
class="vue-splitter-container clearfix"
style={(unref(cursor), unref(userSelect))}
onMouseup={() => onMouseUp()}
onMousemove={() => onMouseMove(event)}>
onMousemove={() => onMouseMove(event)}
>
<div
class={unref(leftClass)}
style={{ [unref(type)]: unref(percent) + "%" }}>
style={{ [unref(type)]: unref(percent) + "%" }}
>
{ctx.slots.paneL()}
</div>
<resizer
style={`${unref([resizeType])}:${unref(percent)}%`}
split={props.splitSet?.split}
onMousedown={() => onMouseDown()}
onClick={() => onClick()}></resizer>
onClick={() => onClick()}
></resizer>
<div
class={unref(rightClass)}
style={{ [unref(type)]: 100 - unref(percent) + "%" }}>
style={{ [unref(type)]: 100 - unref(percent) + "%" }}
>
{ctx.slots.paneR()}
</div>
<div v-show={unref(active)} class="vue-splitter-container-mask"></div>

View File

@@ -1,21 +1,18 @@
.splitter-pane-resizer {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
background: #000;
position: absolute;
opacity: 0.2;
z-index: 1;
-moz-background-clip: padding;
-webkit-background-clip: padding;
background-clip: padding;
background-clip: padding-box;
}
.splitter-pane-resizer.horizontal {
height: 11px;
margin: -5px 0;
border-top: 5px solid rgba(255, 255, 255, 0);
border-bottom: 5px solid rgba(255, 255, 255, 0);
border-top: 5px solid rgb(255 255 255 / 0%);
border-bottom: 5px solid rgb(255 255 255 / 0%);
cursor: row-resize;
width: 100%;
}
@@ -24,7 +21,7 @@
width: 11px;
height: 100%;
margin-left: -5px;
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
border-left: 5px solid rgb(255 255 255 / 0%);
border-right: 5px solid rgb(255 255 255 / 0%);
cursor: col-resize;
}

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({

View File

@@ -1,70 +1,43 @@
<script setup lang="ts">
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { emitter } from "/@/utils/mitt";
import { useNav } from "../hooks/nav";
import { useRoute } from "vue-router";
import Notice from "./notice/index.vue";
import mixNav from "./sidebar/mixNav.vue";
import avatars from "/@/assets/avatars.jpg";
import { transformI18n } from "/@/plugins/i18n";
import Hamburger from "./sidebar/hamBurger.vue";
import { useRouter, useRoute } from "vue-router";
import { storageSession } from "/@/utils/storage";
import { watch, getCurrentInstance } from "vue";
import Breadcrumb from "./sidebar/breadCrumb.vue";
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 globalization from "/@/assets/svg/globalization.svg?component";
const route = useRoute();
const { locale } = useI18n();
const instance =
getCurrentInstance().appContext.config.globalProperties.$storage;
const pureApp = useAppStoreHook();
const router = useRouter();
const route = useRoute();
let usename = storageSession.getItem("info")?.username;
const { locale } = useI18n();
const getDropdownItemStyle = computed(() => {
return t => {
return {
background: locale.value === t ? "#1b2a47" : "",
color: locale.value === t ? "#f4f4f5" : "#000"
};
};
});
const {
logout,
onPanel,
changeTitle,
toggleSideBar,
pureApp,
usename,
getDropdownItemStyle
} = useNav();
watch(
() => locale.value,
() => {
//@ts-ignore
document.title = transformI18n(
//@ts-ignore
unref(route.meta.title),
unref(route.meta.i18n)
); // 动态title
changeTitle(route.meta);
}
);
// 退出登录
const logout = (): void => {
storageSession.removeItem("info");
router.push("/login");
};
function onPanel() {
emitter.emit("openPanel");
}
function toggleSideBar() {
pureApp.toggleSideBar();
}
// 简体中文
function translationCh() {
instance.locale = { locale: "zh" };
locale.value = "zh";
}
// English
function translationEn() {
instance.locale = { locale: "en" };
locale.value = "en";
@@ -74,14 +47,17 @@ function translationEn() {
<template>
<div class="navbar">
<Hamburger
v-if="pureApp.layout !== 'mix'"
:is-active="pureApp.sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<Breadcrumb class="breadcrumb-container" />
<Breadcrumb v-if="pureApp.layout !== 'mix'" class="breadcrumb-container" />
<div class="vertical-header-right">
<mixNav v-if="pureApp.layout === 'mix'" />
<div v-if="pureApp.layout === 'vertical'" class="vertical-header-right">
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 全屏 -->
@@ -92,17 +68,19 @@ function translationEn() {
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle('zh')"
:style="getDropdownItemStyle(locale, '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="getDropdownItemStyle('en')"
:style="getDropdownItemStyle(locale, '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>
@@ -117,8 +95,10 @@ function translationEn() {
<template #dropdown>
<el-dropdown-menu class="logout">
<el-dropdown-item @click="logout">
<i class="ri-logout-circle-r-line"></i
>{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
<IconifyIconOffline
icon="logout-circle-r-line"
style="margin: 5px"
/>{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
@@ -128,7 +108,7 @@ function translationEn() {
:title="$t('buttons.hssystemSet')"
@click="onPanel"
>
<Setting />
<IconifyIconOffline icon="setting" />
</el-icon>
</div>
</div>
@@ -149,10 +129,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 {
@@ -233,14 +209,8 @@ function translationEn() {
}
.translation {
.el-dropdown-menu__item {
padding: 5px 40px !important;
}
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
::v-deep(.el-dropdown-menu__item) {
padding: 5px 40px;
}
.check-zh {
@@ -257,16 +227,10 @@ function translationEn() {
.logout {
max-width: 120px;
.el-dropdown-menu__item {
::v-deep(.el-dropdown-menu__item) {
min-width: 100%;
display: inline-flex;
flex-wrap: wrap;
}
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
}
}
</style>

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>
@@ -125,7 +125,7 @@ emitter.on("openPanel", () => {
&:hover {
cursor: pointer;
color: red;
color: var(--el-color-primary);
}
}
}

View File

@@ -5,18 +5,14 @@ const { isFullscreen, toggle } = useFullscreen();
<template>
<div class="screen-full" @click="toggle">
<i
<FontIcon
:title="
isFullscreen
? $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

@@ -16,9 +16,7 @@ import { useRouter } from "vue-router";
import panel from "../panel/index.vue";
import { emitter } from "/@/utils/mitt";
import { templateRef } from "@vueuse/core";
import dayIcon from "/@/assets/svg/day.svg";
import { debounce } from "/@/utils/debounce";
import darkIcon from "/@/assets/svg/dark.svg";
import { themeColorsType } from "../../types";
import { useAppStoreHook } from "/@/store/modules/app";
import { shadeBgColor } from "../../theme/element-plus";
@@ -28,6 +26,9 @@ 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;
@@ -60,6 +61,7 @@ let themeColors = ref<Array<themeColorsType>>([
const verticalRef = templateRef<HTMLElement | null>("verticalRef", null);
const horizontalRef = templateRef<HTMLElement | null>("horizontalRef", null);
const mixRef = templateRef<HTMLElement | null>("mixRef", null);
let layoutTheme =
ref(storageLocal.getItem("responsive-layout")) ||
@@ -100,7 +102,7 @@ const getThemeColorStyle = computed(() => {
};
});
function changeStorageConfigure(key, val) {
function storageConfigureChange<T>(key: string, val: T): void {
const storageConfigure = instance.configure;
storageConfigure[key] = val;
instance.configure = storageConfigure;
@@ -116,7 +118,7 @@ function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
// 灰色模式设置
const greyChange = (value): void => {
toggleClass(settings.greyVal, "html-grey", document.querySelector("html"));
changeStorageConfigure("grey", value);
storageConfigureChange("grey", value);
};
// 色弱模式设置
@@ -126,58 +128,55 @@ const weekChange = (value): void => {
"html-weakness",
document.querySelector("html")
);
changeStorageConfigure("weak", value);
storageConfigureChange("weak", value);
};
const tagsChange = () => {
let showVal = settings.tabsVal;
changeStorageConfigure("hideTabs", showVal);
storageConfigureChange("hideTabs", showVal);
emitter.emit("tagViewsChange", showVal);
};
const multiTagsCacheChange = () => {
let multiTagsCache = settings.multiTagsCache;
changeStorageConfigure("multiTagsCache", multiTagsCache);
storageConfigureChange("multiTagsCache", multiTagsCache);
useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache);
};
// 清空缓存并返回登录页
function onReset() {
toggleClass(getConfig().Grey, "html-grey", document.querySelector("html"));
toggleClass(
getConfig().Weak,
"html-weakness",
document.querySelector("html")
);
router.push("/login");
const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig();
useAppStoreHook().setLayout(Layout);
useEpThemeStoreHook().setEpThemeColor(EpThemeColor);
useMultiTagsStoreHook().multiTagsCacheChange(MultiTagsCache);
toggleClass(Grey, "html-grey", document.querySelector("html"));
toggleClass(Weak, "html-weakness", document.querySelector("html"));
useMultiTagsStoreHook().handleTags("equal", [
{
path: "/welcome",
parentPath: "/",
meta: {
title: "menus.hshome",
icon: "HomeFilled",
i18n: true,
showLink: true
icon: "home-filled",
i18n: true
}
}
]);
useMultiTagsStoreHook().multiTagsCacheChange(getConfig().MultiTagsCache);
useEpThemeStoreHook().setEpThemeColor(getConfig().EpThemeColor);
storageLocal.clear();
storageSession.clear();
router.push("/login");
}
function onChange(label) {
changeStorageConfigure("showModel", label);
storageConfigureChange("showModel", label);
emitter.emit("tagViewsShowModel", label);
}
// 侧边栏Logo
function logoChange() {
unref(logoVal)
? changeStorageConfigure("showLogo", true)
: changeStorageConfigure("showLogo", false);
? storageConfigureChange("showLogo", true)
: storageConfigureChange("showLogo", false);
emitter.emit("logoChange", unref(logoVal));
}
@@ -192,10 +191,17 @@ watch(instance, ({ layout }) => {
case "vertical":
toggleClass(true, isSelect, unref(verticalRef));
debounce(setFalse([horizontalRef]), 50);
debounce(setFalse([mixRef]), 50);
break;
case "horizontal":
toggleClass(true, isSelect, unref(horizontalRef));
debounce(setFalse([verticalRef]), 50);
debounce(setFalse([mixRef]), 50);
break;
case "mix":
toggleClass(true, isSelect, unref(mixRef));
debounce(setFalse([verticalRef]), 50);
debounce(setFalse([horizontalRef]), 50);
break;
}
});
@@ -315,7 +321,7 @@ nextTick(() => {
<el-divider>导航栏模式</el-divider>
<ul class="pure-theme">
<el-tooltip class="item" content="左侧菜单模式" placement="bottom">
<el-tooltip class="item" content="左侧模式" placement="bottom">
<li
:class="layoutTheme.layout === 'vertical' ? $style.isSelect : ''"
ref="verticalRef"
@@ -326,7 +332,7 @@ nextTick(() => {
</li>
</el-tooltip>
<el-tooltip class="item" content="顶部菜单模式" placement="bottom">
<el-tooltip class="item" content="顶部模式" placement="bottom">
<li
:class="layoutTheme.layout === 'horizontal' ? $style.isSelect : ''"
ref="horizontalRef"
@@ -336,6 +342,17 @@ nextTick(() => {
<div></div>
</li>
</el-tooltip>
<el-tooltip class="item" content="混合模式" placement="bottom">
<li
:class="layoutTheme.layout === 'mix' ? $style.isSelect : ''"
ref="mixRef"
@click="setLayoutModel('mix')"
>
<div></div>
<div></div>
</li>
</el-tooltip>
</ul>
<el-divider v-show="!dataTheme">主题色</el-divider>
@@ -351,7 +368,7 @@ nextTick(() => {
:size="17"
:color="getThemeColor(item.themeColor)"
>
<Check />
<IconifyIconOffline icon="check" />
</el-icon>
</li>
</ul>
@@ -436,7 +453,12 @@ nextTick(() => {
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>
@@ -444,7 +466,7 @@ nextTick(() => {
<style scoped module>
.isSelect {
border: 2px solid #0960bd;
border: 2px solid var(--el-color-primary);
}
</style>
@@ -476,15 +498,14 @@ nextTick(() => {
.pure-theme {
margin-top: 25px;
width: 100%;
height: 100px;
height: 50px;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
li {
margin: 10px;
width: 36%;
height: 70px;
width: 18%;
height: 45px;
background: #f0f2f5;
position: relative;
overflow: hidden;
@@ -522,6 +543,27 @@ nextTick(() => {
}
}
}
&:nth-child(3) {
div {
&:nth-child(1) {
width: 100%;
height: 30%;
background: #1b2a47;
box-shadow: 0 0 1px #888;
}
&:nth-child(2) {
width: 30%;
height: 70%;
bottom: 0;
left: 0;
background: #fff;
box-shadow: 0 0 1px #888;
position: absolute;
}
}
}
}
}

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

@@ -1,144 +1,80 @@
<script setup lang="ts">
import {
computed,
unref,
watch,
nextTick,
onMounted,
getCurrentInstance
} from "vue";
import { useI18n } from "vue-i18n";
import { emitter } from "/@/utils/mitt";
import { useNav } from "../../hooks/nav";
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 { watch, nextTick, onMounted, getCurrentInstance } from "vue";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import globalization from "/@/assets/svg/globalization.svg?component";
const route = useRoute();
const { locale } = useI18n();
const routers = useRouter().options.routes;
const menuRef = templateRef<ElRef | null>("menu", null);
const instance =
getCurrentInstance().appContext.config.globalProperties.$storage;
const title =
getCurrentInstance().appContext.config.globalProperties.$config?.Title;
const menuRef = templateRef<ElRef | null>("menu", null);
const route = useRoute();
const router = useRouter();
const routers = useRouter().options.routes;
let usename = storageSession.getItem("info")?.username;
const { locale, t } = useI18n();
const {
logout,
backHome,
onPanel,
changeTitle,
handleResize,
menuSelect,
usename,
getDropdownItemStyle
} = useNav();
const getDropdownItemStyle = computed(() => {
return t => {
return {
background: locale.value === t ? "#1b2a47" : "",
color: locale.value === t ? "#f4f4f5" : "#000"
};
};
onMounted(() => {
nextTick(() => {
handleResize(menuRef.value);
});
});
watch(
() => locale.value,
() => {
//@ts-ignore
// 动态title
document.title = t(unref(route.meta.title));
changeTitle(route.meta);
}
);
// 退出登录
const logout = (): void => {
storageSession.removeItem("info");
router.push("/login");
};
function onPanel() {
emitter.emit("openPanel");
}
const activeMenu = computed((): string => {
const { meta, path } = route;
if (meta.activeMenu) {
// @ts-ignore
return meta.activeMenu;
}
return path;
});
const menuSelect = (indexPath: string): void => {
let parentPath = "";
let parentPathIndex = indexPath.lastIndexOf("/");
if (parentPathIndex > 0) {
parentPath = indexPath.slice(0, parentPathIndex);
}
// 找到当前路由的信息
function findCurrentRoute(routes) {
return routes.map(item => {
if (item.path === indexPath) {
// 切换左侧菜单 通知标签页
emitter.emit("changLayoutRoute", {
indexPath,
parentPath
});
} else {
if (item.children) findCurrentRoute(item.children);
}
});
}
findCurrentRoute(algorithm.increaseIndexes(routers));
};
function backHome() {
router.push("/welcome");
}
function handleResize() {
// @ts-ignore
menuRef.value.handleResize();
}
// 简体中文
function translationCh() {
instance.locale = { locale: "zh" };
locale.value = "zh";
handleResize();
handleResize(menuRef.value);
}
// English
function translationEn() {
instance.locale = { locale: "en" };
locale.value = "en";
handleResize();
handleResize(menuRef.value);
}
onMounted(() => {
nextTick(() => {
handleResize();
});
});
</script>
<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
ref="menu"
:default-active="activeMenu"
unique-opened
router
class="horizontal-header-menu"
mode="horizontal"
@select="menuSelect"
:default-active="route.path"
router
@select="indexPath => menuSelect(indexPath, routers)"
>
<sidebar-item
v-for="route in usePermissionStoreHook().wholeMenus"
@@ -158,17 +94,17 @@ onMounted(() => {
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle('zh')"
:style="getDropdownItemStyle(locale, '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="getDropdownItemStyle('en')"
:style="getDropdownItemStyle(locale, '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>
@@ -183,8 +119,11 @@ onMounted(() => {
<template #dropdown>
<el-dropdown-menu class="logout">
<el-dropdown-item @click="logout">
<i class="ri-logout-circle-r-line"></i
>{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
<IconifyIconOffline
icon="logout-circle-r-line"
style="margin: 5px"
/>
{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
@@ -194,7 +133,7 @@ onMounted(() => {
:title="$t('buttons.hssystemSet')"
@click="onPanel"
>
<Setting />
<IconifyIconOffline icon="setting" />
</el-icon>
</div>
</div>
@@ -202,14 +141,8 @@ onMounted(() => {
<style lang="scss" scoped>
.translation {
.el-dropdown-menu__item {
padding: 5px 40px !important;
}
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
::v-deep(.el-dropdown-menu__item) {
padding: 5px 40px;
}
.check-zh {
@@ -226,16 +159,10 @@ onMounted(() => {
.logout {
max-width: 120px;
.el-dropdown-menu__item {
::v-deep(.el-dropdown-menu__item) {
min-width: 100%;
display: inline-flex;
flex-wrap: wrap;
}
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
}
}
</style>

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

@@ -0,0 +1,239 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import Notice from "../notice/index.vue";
import { useNav } from "../../hooks/nav";
import { templateRef } from "@vueuse/core";
import avatars from "/@/assets/avatars.jpg";
import { transformI18n } from "/@/plugins/i18n";
import screenfull from "../screenfull/index.vue";
import { useRoute, useRouter } from "vue-router";
import { deviceDetection } from "/@/utils/deviceDetection";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
import { getParentPaths, findRouteByPath } from "/@/router/utils";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import globalization from "/@/assets/svg/globalization.svg?component";
import { ref, watch, nextTick, onMounted, getCurrentInstance } from "vue";
const route = useRoute();
const { locale } = useI18n();
const routers = useRouter().options.routes;
const menuRef = templateRef<ElRef | null>("menu", null);
const instance =
getCurrentInstance().appContext.config.globalProperties.$storage;
const {
logout,
onPanel,
changeTitle,
toggleSideBar,
handleResize,
menuSelect,
resolvePath,
pureApp,
usename,
getDropdownItemStyle
} = useNav();
let defaultActive = ref(null);
function getDefaultActive(routePath) {
const wholeMenus = usePermissionStoreHook().wholeMenus;
// 当前路由的父级路径
const parentRoutes = getParentPaths(routePath, wholeMenus)[0];
defaultActive.value = findRouteByPath(
parentRoutes,
wholeMenus
)?.children[0]?.path;
}
onMounted(() => {
getDefaultActive(route.path);
nextTick(() => {
handleResize(menuRef.value);
});
});
watch(
() => locale.value,
() => {
changeTitle(route.meta);
}
);
watch(
() => route.path,
() => {
getDefaultActive(route.path);
}
);
function translationCh() {
instance.locale = { locale: "zh" };
locale.value = "zh";
handleResize(menuRef.value);
}
function translationEn() {
instance.locale = { locale: "en" };
locale.value = "en";
handleResize(menuRef.value);
}
</script>
<template>
<div class="horizontal-header">
<div
:class="classes.container"
:title="pureApp.sidebar.opened ? '点击折叠' : '点击展开'"
@click="toggleSideBar"
>
<svg
:fill="useEpThemeStoreHook().fill"
:class="[
'hamburger',
pureApp.sidebar.opened ? 'is-active-hamburger' : ''
]"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
>
<path
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
/>
</svg>
</div>
<el-menu
ref="menu"
class="horizontal-header-menu"
mode="horizontal"
:default-active="defaultActive"
router
@select="indexPath => menuSelect(indexPath, routers)"
>
<el-menu-item
v-for="route in usePermissionStoreHook().wholeMenus"
:key="route.path"
:index="resolvePath(route) || route.redirect"
>
<template #title>
<el-icon v-show="route.meta.icon" :class="route.meta.icon">
<component
:is="useRenderIcon(route.meta && route.meta.icon)"
></component>
</el-icon>
<span>{{ transformI18n(route.meta.title, route.meta.i18n) }}</span>
<FontIcon
v-if="route.meta.extraIcon"
width="30px"
height="30px"
style="position: absolute; right: 10px"
:icon="route.meta.extraIcon.name"
:svg="route.meta.extraIcon.svg ? true : false"
></FontIcon>
</template>
</el-menu-item>
</el-menu>
<div class="horizontal-header-right">
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 全屏 -->
<screenfull id="header-screenfull" v-show="!deviceDetection()" />
<!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click">
<globalization />
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')"
@click="translationCh"
><el-icon class="check-zh" v-show="locale === 'zh'"
><IconifyIconOffline icon="check" /></el-icon
>简体中文</el-dropdown-item
>
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')"
@click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'"
><IconifyIconOffline icon="check" /></el-icon
>English</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 退出登陆 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link">
<img :src="avatars" />
<p>{{ usename }}</p>
</span>
<template #dropdown>
<el-dropdown-menu class="logout">
<el-dropdown-item @click="logout">
<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('buttons.hssystemSet')"
@click="onPanel"
>
<IconifyIconOffline icon="setting" />
</el-icon>
</div>
</div>
</template>
<style module="classes" scoped>
.container {
padding: 0 15px;
}
</style>
<style lang="scss" scoped>
.hamburger {
width: 20px;
height: 20px;
&:hover {
cursor: pointer;
}
}
.is-active-hamburger {
transform: rotate(180deg);
}
.translation {
::v-deep(.el-dropdown-menu__item) {
padding: 5px 40px;
}
.check-zh {
position: absolute;
left: 20px;
}
.check-en {
position: absolute;
left: 20px;
}
}
.logout {
max-width: 120px;
::v-deep(.el-dropdown-menu__item) {
min-width: 100%;
display: inline-flex;
flex-wrap: wrap;
}
}
</style>

View File

@@ -1,22 +1,14 @@
<script setup lang="ts">
import {
ref,
PropType,
nextTick,
computed,
CSSProperties,
getCurrentInstance
} from "vue";
import { ref, PropType, nextTick, computed, CSSProperties } from "vue";
import path from "path";
import { useNav } from "../../hooks/nav";
import { childrenType } from "../../types";
import { transformI18n } from "/@/plugins/i18n";
import { findIconReg } from "/@/components/ReIcon";
import Icon from "/@/components/ReIcon/src/Icon.vue";
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();
const { pureApp } = useNav();
const menuMode = ["vertical", "mix"].includes(pureApp.layout);
const props = defineProps({
item: {
@@ -32,6 +24,19 @@ 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",
@@ -93,7 +98,6 @@ function hoverMenu(key) {
: Object.assign(key, {
showTooltip: false
});
hoverMenuMap.set(key, true);
});
}
@@ -120,8 +124,8 @@ function hasOneShowingChild(
function resolvePath(routePath) {
const httpReg = /^http(s?):\/\//;
if (httpReg.test(routePath)) {
return props.basePath + "/" + routePath;
if (httpReg.test(routePath) || httpReg.test(props.basePath)) {
return routePath || props.basePath;
} else {
return path.resolve(props.basePath, routePath);
}
@@ -143,13 +147,25 @@ function resolvePath(routePath) {
<el-icon v-show="props.item.meta.icon">
<component
:is="
findIconReg(
useRenderIcon(
onlyOneChild.meta.icon ||
(props.item.meta && props.item.meta.icon)
)
"
></component>
</el-icon>
<div
v-if="
!pureApp.sidebar.opened &&
pureApp.layout === 'mix' &&
props.item?.pathList?.length === 2
"
:style="getDivStyle"
>
<span :style="getMenuTextStyle">
{{ transformI18n(onlyOneChild.meta.title, onlyOneChild.meta.i18n) }}
</span>
</div>
<template #title>
<div :style="getDivStyle">
<span v-if="!menuMode">{{
@@ -176,11 +192,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>
@@ -195,7 +214,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">{{
@@ -220,11 +239,14 @@ function resolvePath(routePath) {
</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

@@ -1,61 +1,56 @@
<script setup lang="ts">
import Logo from "./logo.vue";
import { emitter } from "/@/utils/mitt";
import { useNav } from "../../hooks/nav";
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";
import { useAppStoreHook } from "/@/store/modules/app";
import { ref, computed, watch, onBeforeMount } from "vue";
import { findRouteByPath, getParentPaths } from "/@/router/utils";
import { usePermissionStoreHook } from "/@/store/modules/permission";
const route = useRoute();
const pureApp = useAppStoreHook();
const router = useRouter().options.routes;
const routers = useRouter().options.routes;
const showLogo = ref(
storageLocal.getItem("responsive-configure")?.showLogo ?? true
);
const isCollapse = computed(() => {
return !pureApp.getSidebarStatus;
});
const activeMenu = computed((): string => {
const { meta, path } = route;
if (meta.activeMenu) {
// @ts-ignore
return meta.activeMenu;
}
return path;
const { pureApp, isCollapse, menuSelect } = useNav();
let subMenuData = ref([]);
const menuData = computed(() => {
return pureApp.layout === "mix"
? subMenuData.value
: usePermissionStoreHook().wholeMenus;
});
const menuSelect = (indexPath: string): void => {
let parentPath = "";
let parentPathIndex = indexPath.lastIndexOf("/");
if (parentPathIndex > 0) {
parentPath = indexPath.slice(0, parentPathIndex);
}
// 找到当前路由的信息
// eslint-disable-next-line no-inner-declarations
function findCurrentRoute(routes) {
return routes.map(item => {
if (item.path === indexPath) {
// 切换左侧菜单 通知标签页
emitter.emit("changLayoutRoute", {
indexPath,
parentPath
});
} else {
if (item.children) findCurrentRoute(item.children);
}
});
}
findCurrentRoute(algorithm.increaseIndexes(router));
};
function getSubMenuData(path) {
// path的上级路由组成的数组
const parentPathArr = getParentPaths(
path,
usePermissionStoreHook().wholeMenus
);
// 当前路由的父级路由信息
const parenetRoute = findRouteByPath(
parentPathArr[0] || path,
usePermissionStoreHook().wholeMenus
);
if (!parenetRoute?.children) return;
subMenuData.value = parenetRoute?.children;
}
getSubMenuData(route.path);
onBeforeMount(() => {
emitter.on("logoChange", key => {
showLogo.value = key;
});
});
watch(
() => route.path,
() => getSubMenuData(route.path)
);
</script>
<template>
@@ -63,21 +58,21 @@ onBeforeMount(() => {
<Logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:default-active="route.path"
:collapse="isCollapse"
unique-opened
router
:collapse-transition="false"
mode="vertical"
class="outer-most"
@select="menuSelect"
@select="indexPath => menuSelect(indexPath, routers)"
>
<sidebar-item
v-for="route in usePermissionStoreHook().wholeMenus"
:key="route.path"
:item="route"
v-for="routes in menuData"
:key="routes.path"
:item="routes"
class="outer-most"
:base-path="route.path"
:base-path="routes.path"
/>
</el-menu>
</el-scrollbar>

View File

@@ -18,36 +18,6 @@
}
}
@-webkit-keyframes rotate {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
}
}
@-moz-keyframes rotate {
from {
-moz-transform: rotate(0deg);
}
to {
-moz-transform: rotate(360deg);
}
}
@-o-keyframes rotate {
from {
-o-transform: rotate(0deg);
}
to {
-o-transform: rotate(360deg);
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
@@ -80,7 +50,7 @@
.scroll-item {
border-radius: 3px 3px 0 0;
padding: 0 6px 0 6px;
padding: 0 6px;
box-shadow: 0 0 1px #888;
position: relative;
margin-right: 4px;
@@ -92,7 +62,7 @@
.el-icon-close {
font-size: 10px;
color: #1890ff;
color: var(--el-color-primary);
cursor: pointer;
position: absolute;
top: 50%;
@@ -123,7 +93,7 @@
a {
text-decoration: none;
color: #666;
padding: 0 4px 0 4px;
padding: 0 4px;
}
.scroll-container {
@@ -190,7 +160,8 @@
align-items: center;
&:hover {
background: #eee;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
svg {
@@ -236,7 +207,7 @@
}
.scroll-item.is-active {
background-color: #eaf4fe;
background-color: var(--el-color-primary-light-9);
position: relative;
color: #fff;
@@ -249,17 +220,27 @@
}
a {
color: #1890ff;
color: var(--el-color-primary);
}
}
.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 {
@@ -267,15 +248,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;
@@ -284,10 +259,10 @@
/* 卡片模式下鼠标移入显示蓝色边框 */
.card-in {
color: #1890ff;
color: var(--el-color-primary);
a {
color: #1890ff;
color: var(--el-color-primary);
}
}
@@ -308,7 +283,7 @@
position: absolute;
left: 0;
bottom: 0;
background: #1890ff;
background: var(--el-color-primary);
}
/* 灵动模式下鼠标移入显示蓝色进度条 */
@@ -318,7 +293,7 @@
position: absolute;
left: 0;
bottom: 0;
background: #1890ff;
background: var(--el-color-primary);
animation: scheduleInWidth 400ms ease-in;
}
@@ -329,14 +304,11 @@
position: absolute;
left: 0;
bottom: 0;
background: #1890ff;
background: var(--el-color-primary);
animation: scheduleOutWidth 400ms ease-in;
}
/* 刷新按钮动画效果 */
.refresh-button {
-webkit-animation: rotate 600ms linear infinite;
-moz-animation: rotate 600ms linear infinite;
-o-animation: rotate 600ms linear infinite;
animation: rotate 600ms linear infinite;
}

View File

@@ -3,6 +3,7 @@ import {
ref,
watch,
unref,
reactive,
nextTick,
computed,
ComputedRef,
@@ -11,15 +12,15 @@ import {
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 { $t as t } from "/@/plugins/i18n";
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";
@@ -30,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();
@@ -40,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;
});
@@ -106,7 +106,11 @@ const iconIsActive = computed(() => {
const dynamicTagView = () => {
const index = multiTags.value.findIndex(item => {
return item.path === route.path;
if (item?.query) {
return isEqual(route?.query, item?.query);
} else {
return item.path === route.path;
}
});
moveToView(index);
};
@@ -128,15 +132,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;
@@ -186,7 +190,7 @@ const handleScroll = (offset: number): void => {
}
};
const tagsViews = ref<Array<tagsViewsType>>([
const tagsViews = reactive<Array<tagsViewsType>>([
{
icon: refresh,
text: t("buttons.hsreload"),
@@ -314,8 +318,7 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
meta: {
title: "menus.hshome",
i18n: true,
icon: "el-icon-s-home",
showLink: true
icon: "home-filled"
}
},
obj
@@ -424,6 +427,11 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
});
}
function handleCommand(command: object) {
const { key, item } = command;
onClickDrop(key, item);
}
// 触发右键中菜单的点击事件
function selectTag(key, item) {
onClickDrop(key, item, currentSelect.value);
@@ -435,13 +443,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;
});
}
@@ -463,35 +471,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);
@@ -505,10 +512,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
@@ -517,7 +524,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);
@@ -552,30 +559,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]);
}
}
@@ -629,7 +638,9 @@ const getContextMenuStyle = computed((): CSSProperties => {
<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="getTabStyle">
<div
@@ -644,8 +655,8 @@ const getContextMenuStyle = computed((): CSSProperties => {
: ''
]"
@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"
@@ -659,7 +670,7 @@ const getContextMenuStyle = computed((): CSSProperties => {
class="el-icon-close"
@click.stop="deleteMenu(item)"
>
<CloseBold />
<IconifyIconOffline icon="close-bold" />
</el-icon>
<div
:ref="'schedule' + index"
@@ -669,7 +680,12 @@ const getContextMenuStyle = computed((): CSSProperties => {
</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
@@ -698,24 +714,32 @@ const getContextMenuStyle = computed((): CSSProperties => {
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-dropdown
trigger="click"
placement="bottom-end"
@command="handleCommand"
>
<el-icon>
<ArrowDown />
<IconifyIconOffline icon="arrow-down" />
</el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="(item, key) in tagsViews"
:key="key"
:command="{ key, item }"
:divided="item.divided"
:disabled="item.disabled"
@click="onClickDrop(key, item)"
>
<component :is="item.icon" :key="key" />
<component
:is="item.icon"
:key="key"
style="margin-right: 6px"
/>
{{ $t(item.text) }}
</el-dropdown-item>
</el-dropdown-menu>

110
src/layout/hooks/nav.ts Normal file
View File

@@ -0,0 +1,110 @@
import { computed } from "vue";
import { router } from "/@/router";
import { emitter } from "/@/utils/mitt";
import { routeMetaType } from "../types";
import { transformI18n } from "/@/plugins/i18n";
import { storageSession } from "/@/utils/storage";
import { useAppStoreHook } from "/@/store/modules/app";
import { Title } from "../../../public/serverConfig.json";
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
export function useNav() {
const pureApp = useAppStoreHook();
// 用户名
const usename: string = storageSession.getItem("info")?.username;
// 设置国际化选中后的样式
const getDropdownItemStyle = computed(() => {
return (locale, t) => {
return {
background: locale === t ? useEpThemeStoreHook().epThemeColor : "",
color: locale === t ? "#f4f4f5" : "#000"
};
};
});
const isCollapse = computed(() => {
return !pureApp.getSidebarStatus;
});
// 动态title
function changeTitle(meta: routeMetaType) {
if (Title)
document.title = `${transformI18n(meta.title, meta.i18n)} | ${Title}`;
else document.title = transformI18n(meta.title, meta.i18n);
}
// 退出登录
function logout() {
storageSession.removeItem("info");
router.push("/login");
}
function backHome() {
router.push("/welcome");
}
function onPanel() {
emitter.emit("openPanel");
}
function toggleSideBar() {
pureApp.toggleSideBar();
}
function handleResize(menuRef) {
menuRef.handleResize();
}
function resolvePath(route) {
const httpReg = /^http(s?):\/\//;
const routeChildPath = route.children[0]?.path;
if (httpReg.test(routeChildPath)) {
return route.path + "/" + routeChildPath;
} else {
return routeChildPath;
}
}
function menuSelect(indexPath: string, routers): void {
let parentPath = "";
const parentPathIndex = indexPath.lastIndexOf("/");
if (parentPathIndex > 0) {
parentPath = indexPath.slice(0, parentPathIndex);
}
// 找到当前路由的信息
function findCurrentRoute(indexPath: string, routes) {
return routes.map(item => {
if (item.path === indexPath) {
if (item.redirect) {
findCurrentRoute(item.redirect, item.children);
} else {
// 切换左侧菜单 通知标签页
emitter.emit("changLayoutRoute", {
indexPath,
parentPath
});
}
} else {
if (item.children) findCurrentRoute(indexPath, item.children);
}
});
}
findCurrentRoute(indexPath, routers);
}
return {
logout,
backHome,
onPanel,
changeTitle,
toggleSideBar,
menuSelect,
handleResize,
resolvePath,
isCollapse,
pureApp,
usename,
getDropdownItemStyle
};
}

View File

@@ -11,14 +11,15 @@ 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 { 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";
import appMain from "./components/appMain.vue";
@@ -170,7 +171,8 @@ const layoutHeader = defineComponent({
},
{
default: () => [
!pureSetting.hiddenSideBar && layout.value.includes("vertical")
!pureSetting.hiddenSideBar &&
(layout.value.includes("vertical") || layout.value.includes("mix"))
? h(navbar)
: h("div"),
!pureSetting.hiddenSideBar && layout.value.includes("horizontal")
@@ -212,7 +214,10 @@ const layoutHeader = defineComponent({
@click="useAppStoreHook().toggleSideBar()"
/>
<Vertical
v-show="!pureSetting.hiddenSideBar && layout.includes('vertical')"
v-show="
!pureSetting.hiddenSideBar &&
(layout.includes('vertical') || layout.includes('mix'))
"
/>
<div
:class="[

View File

@@ -19,7 +19,7 @@ $subMenuBg: #0f0303 !default;
/* 有无子集的激活菜单背景 */
$subMenuActiveBg: #4091f7 !default;
$navTextColor: #fff !default;
$menuText: rgba(254, 254, 254, 0.65) !default;
$menuText: rgb(254 254 254 / 65%) !default;
/* logo背景颜色 */
$sidebarLogo: #002140 !default;

View File

@@ -5,7 +5,7 @@ $menuHover: #e13c39;
$subMenuBg: #000;
$subMenuActiveBg: #e13c39;
$navTextColor: red;
$menuText: rgba(254, 254, 254, 0.651);
$menuText: rgb(254 254 254 / 65.1%);
$sidebarLogo: #42090c;
$menuTitleHover: #fff;
$menuActiveBefore: #e13c39;

View File

@@ -1,6 +1,6 @@
/* 动态改变element-plus主题色 */
import rgbHex from "rgb-hex";
import color from "css-color-function";
import { convert } from "css-color-function";
import { TinyColor } from "@ctrl/tinycolor";
import epCss from "element-plus/dist/index.css";
@@ -48,7 +48,7 @@ export const createColors = (primary: string) => {
};
Object.keys(formula).forEach(key => {
const value = formula[key].replace(/primary/, primary);
colors[key] = "#" + rgbHex(color.convert(value));
colors[key] = "#" + rgbHex(convert(value));
});
return colors;
};

View File

@@ -5,7 +5,7 @@ $menuHover: #e85f33;
$subMenuBg: #0f0603;
$subMenuActiveBg: #e85f33;
$navTextColor: #fff;
$menuText: rgba(254, 254, 254, 0.65);
$menuText: rgb(254 254 254 / 65%);
$sidebarLogo: #441708;
$menuTitleHover: #fff;
$menuActiveBefore: #e85f33;

View File

@@ -5,7 +5,7 @@ $menuHover: #f6da4d;
$subMenuBg: #0f0603;
$subMenuActiveBg: #f6da4d;
$navTextColor: #fff;
$menuText: rgba(254, 254, 254, 0.65);
$menuText: rgb(254 254 254 / 65%);
$sidebarLogo: #443b05;
$menuTitleHover: #fff;
$menuActiveBefore: #f6da4d;

View File

@@ -1,3 +1,4 @@
import { Component } from "vue";
export const routerArrays: Array<RouteConfigs> = [
{
path: "/welcome",
@@ -5,24 +6,25 @@ export const routerArrays: Array<RouteConfigs> = [
meta: {
title: "menus.hshome",
i18n: true,
icon: "HomeFilled",
showLink: true
icon: "home-filled"
}
}
];
export type routeMetaType = {
title?: string;
i18n?: boolean;
icon?: string;
showLink?: boolean;
savedPosition?: boolean;
authority?: Array<string>;
};
export type RouteConfigs = {
path?: string;
parentPath?: string;
query?: object;
meta?: {
title?: string;
i18n?: boolean;
icon?: string;
showLink?: boolean;
savedPosition?: boolean;
authority?: Array<string>;
};
meta?: routeMetaType;
children?: RouteConfigs[];
name?: string;
};
@@ -32,7 +34,7 @@ export type multiTagsType = {
};
export type tagsViewsType = {
icon: string;
icon: Component;
text: string;
divided: boolean;
disabled: boolean;
@@ -71,6 +73,8 @@ export type childrenType = {
};
};
showTooltip?: boolean;
parentId?: number;
pathList?: number[];
};
export type themeColorsType = {

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,58 +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,
Guide,
User,
Iphone,
Location,
Tickets,
OfficeBuilding,
Notebook
} from "@element-plus/icons-vue";
// Icon
export const iconComponents = [
Check,
Menu,
HomeFilled,
SetUp,
Edit,
Setting,
Lollipop,
Link,
Position,
Histogram,
RefreshRight,
ArrowDown,
Close,
CloseBold,
Bell,
Guide,
User,
Iphone,
Location,
Tickets,
OfficeBuilding,
Notebook
];
export function useElementPlus(app: App) {
// 注册组件
components.forEach((component: Component) => {
@@ -158,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

@@ -11,7 +11,10 @@ import { storageLocal } from "/@/utils/storage";
* @param isI18n 如果true,获取对应的消息,否则返回本身
* @returns message
*/
export function transformI18n(message: string | object = "", isI18n = false) {
export function transformI18n(
message: string | unknown | object = "",
isI18n: boolean | unknown = false
) {
if (!message) {
return "";
}

View File

@@ -6,6 +6,7 @@ import { split, findIndex } from "lodash-es";
import { transformI18n } from "/@/plugins/i18n";
import remainingRouter from "./modules/remaining";
import { storageSession } from "/@/utils/storage";
import { Title } from "../../public/serverConfig.json";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import { Router, RouteMeta, createRouter, RouteRecordName } from "vue-router";
@@ -54,12 +55,13 @@ router.beforeEach((to: toRouteType, _from, next) => {
const externalLink = to?.redirectedFrom?.fullPath;
if (!externalLink)
to.matched.some(item => {
item.meta.title
? (document.title = transformI18n(
item.meta.title as string,
item.meta?.i18n as boolean
))
: "";
if (!item.meta.title) return "";
if (Title)
document.title = `${transformI18n(
item.meta.title,
item.meta?.i18n
)} | ${Title}`;
else document.title = transformI18n(item.meta.title, item.meta?.i18n);
});
if (name) {
if (_from?.name) {

View File

@@ -7,10 +7,9 @@ const componentsRouter = {
component: Layout,
redirect: "/components/video",
meta: {
icon: "Menu",
icon: "menu",
title: $t("menus.hscomponents"),
i18n: true,
showLink: true,
rank: 4
},
children: [
@@ -20,7 +19,6 @@ const componentsRouter = {
component: () => import("/@/views/components/video/index.vue"),
meta: {
title: $t("menus.hsvideo"),
showLink: true,
i18n: true
}
},
@@ -30,7 +28,6 @@ const componentsRouter = {
component: () => import("/@/views/components/map/index.vue"),
meta: {
title: $t("menus.hsmap"),
showLink: true,
keepAlive: true,
i18n: true,
transition: {
@@ -44,7 +41,6 @@ const componentsRouter = {
component: () => import("/@/views/components/draggable/index.vue"),
meta: {
title: $t("menus.hsdraggable"),
showLink: true,
i18n: true,
transition: {
enterTransition: "animate__zoomIn",
@@ -59,7 +55,6 @@ const componentsRouter = {
component: () => import("/@/views/components/split-pane/index.vue"),
meta: {
title: $t("menus.hssplitPane"),
showLink: true,
i18n: true,
extraIcon: {
svg: true,
@@ -73,8 +68,7 @@ const componentsRouter = {
component: () => import("/@/views/components/button/index.vue"),
meta: {
title: $t("menus.hsbutton"),
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -83,8 +77,7 @@ const componentsRouter = {
component: () => import("/@/views/components/cropping/index.vue"),
meta: {
title: $t("menus.hscropping"),
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -93,8 +86,7 @@ const componentsRouter = {
component: () => import("/@/views/components/count-to/index.vue"),
meta: {
title: $t("menus.hscountTo"),
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -103,8 +95,7 @@ const componentsRouter = {
component: () => import("/@/views/components/selector/index.vue"),
meta: {
title: $t("menus.hsselector"),
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -113,8 +104,7 @@ const componentsRouter = {
component: () => import("/@/views/components/seamless-scroll/index.vue"),
meta: {
title: $t("menus.hsseamless"),
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -123,8 +113,7 @@ const componentsRouter = {
component: () => import("/@/views/components/contextmenu/index.vue"),
meta: {
title: $t("menus.hscontextmenu"),
i18n: true,
showLink: true
i18n: true
}
}
]

View File

@@ -7,10 +7,9 @@ const editorRouter = {
component: Layout,
redirect: "/editor/index",
meta: {
icon: "Edit",
icon: "edit",
title: $t("menus.hseditor"),
i18n: true,
showLink: true,
rank: 2
},
children: [
@@ -20,7 +19,6 @@ const editorRouter = {
component: () => import("/@/views/editor/index.vue"),
meta: {
title: $t("menus.hseditor"),
showLink: true,
i18n: true,
keepAlive: true,
extraIcon: {

View File

@@ -7,9 +7,8 @@ const errorRouter = {
component: Layout,
redirect: "/error/401",
meta: {
icon: "Position",
icon: "position",
title: $t("menus.hserror"),
showLink: true,
i18n: true,
rank: 7
},
@@ -20,8 +19,7 @@ const errorRouter = {
component: () => import("/@/views/error/401.vue"),
meta: {
title: $t("menus.hsfourZeroOne"),
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -30,8 +28,7 @@ const errorRouter = {
component: () => import("/@/views/error/404.vue"),
meta: {
title: $t("menus.hsfourZeroFour"),
i18n: true,
showLink: true
i18n: true
}
}
]

View File

@@ -6,9 +6,8 @@ const externalLink = {
name: "external",
component: Layout,
meta: {
icon: "Link",
icon: "link",
title: $t("menus.externalLink"),
showLink: true,
i18n: true,
rank: 190
},
@@ -17,7 +16,6 @@ const externalLink = {
path: "https://github.com/xiaoxian521/vue-pure-admin",
meta: {
title: $t("menus.externalLink"),
showLink: true,
i18n: true,
rank: 191
}

View File

@@ -7,9 +7,8 @@ const flowChartRouter = {
component: Layout,
redirect: "/flowChart/index",
meta: {
icon: "SetUp",
icon: "set-up",
title: $t("menus.hsflowChart"),
showLink: true,
i18n: true,
rank: 1
},
@@ -20,8 +19,7 @@ const flowChartRouter = {
component: () => import("/@/views/flow-chart/index.vue"),
meta: {
title: $t("menus.hsflowChart"),
i18n: true,
showLink: true
i18n: true
}
}
]

View File

@@ -7,10 +7,9 @@ const guideRouter = {
component: Layout,
redirect: "/guide/index",
meta: {
icon: "Guide",
icon: "guide",
title: $t("menus.hsguide"),
i18n: true,
showLink: true,
rank: 10
},
children: [
@@ -20,7 +19,6 @@ const guideRouter = {
component: () => import("/@/views/guide/index.vue"),
meta: {
title: $t("menus.hsguide"),
showLink: true,
i18n: true
}
}

View File

@@ -7,9 +7,8 @@ const homeRouter = {
component: Layout,
redirect: "/welcome",
meta: {
icon: "HomeFilled",
icon: "home-filled",
title: $t("menus.hshome"),
showLink: true,
i18n: true,
rank: 0
},
@@ -20,8 +19,7 @@ const homeRouter = {
component: () => import("/@/views/welcome.vue"),
meta: {
title: $t("menus.hshome"),
i18n: true,
showLink: true
i18n: true
}
}
]

View File

@@ -16,6 +16,7 @@ import {
formatTwoStageRoutes,
formatFlatteningRoutes
} from "../utils";
import { buildHierarchyTree } from "/@/utils/tree";
// 原始静态路由(未做任何处理)
const routes = [
@@ -32,7 +33,7 @@ const routes = [
// 导出处理后的静态路由(三级及以上的路由全部拍成二级)
export const constantRoutes: Array<RouteRecordRaw> = formatTwoStageRoutes(
formatFlatteningRoutes(ascending(routes))
formatFlatteningRoutes(buildHierarchyTree(ascending(routes)))
);
// 用于渲染菜单,保持原始层级

View File

@@ -7,10 +7,9 @@ const menuTreeRouter = {
component: Layout,
redirect: "/menuTree/index",
meta: {
icon: "RI-node-tree",
icon: "node-tree",
title: $t("menus.hsMenuTree"),
i18n: true,
showLink: true,
rank: 9
},
children: [
@@ -20,7 +19,6 @@ const menuTreeRouter = {
component: () => import("/@/views/menu-tree/index.vue"),
meta: {
title: $t("menus.hsMenuTree"),
showLink: true,
i18n: true
}
}

View File

@@ -8,8 +8,7 @@ const nestedRouter = {
name: "Nested",
meta: {
title: $t("menus.hsmenus"),
icon: "Histogram",
showLink: true,
icon: "histogram",
i18n: true,
rank: 5
},
@@ -20,7 +19,6 @@ const nestedRouter = {
name: "Menu1",
meta: {
title: $t("menus.hsmenu1"),
showLink: true,
i18n: true,
keepAlive: true
},
@@ -32,7 +30,6 @@ const nestedRouter = {
name: "Menu1-1",
meta: {
title: $t("menus.hsmenu1-1"),
showLink: true,
i18n: true,
keepAlive: true
}
@@ -44,7 +41,6 @@ const nestedRouter = {
redirect: "/nested/menu1/menu1-2/menu1-2-1",
meta: {
title: $t("menus.hsmenu1-2"),
showLink: true,
i18n: true,
keepAlive: true
},
@@ -56,7 +52,6 @@ const nestedRouter = {
name: "Menu1-2-1",
meta: {
title: $t("menus.hsmenu1-2-1"),
showLink: true,
i18n: true,
keepAlive: true
}
@@ -68,7 +63,6 @@ const nestedRouter = {
name: "Menu1-2-2",
meta: {
title: $t("menus.hsmenu1-2-2"),
showLink: true,
keepAlive: true,
i18n: true,
extraIcon: {
@@ -85,7 +79,6 @@ const nestedRouter = {
name: "Menu1-3",
meta: {
title: $t("menus.hsmenu1-3"),
showLink: true,
i18n: true,
keepAlive: true
}
@@ -98,7 +91,6 @@ const nestedRouter = {
component: () => import("/@/views/nested/menu2/index.vue"),
meta: {
title: $t("menus.hsmenu2"),
showLink: true,
i18n: true,
keepAlive: true
}

View File

@@ -18,7 +18,7 @@ const remainingRouter = [
name: "redirect",
component: Layout,
meta: {
icon: "HomeFilled",
icon: "home-filled",
title: $t("menus.hshome"),
i18n: true,
showLink: false,

View File

@@ -19,37 +19,37 @@ 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,10 +207,10 @@ 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) => {
@@ -225,10 +225,10 @@ const addAsyncRoutes = (arrRoutes: Array<RouteRecordRaw>) => {
}
});
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(",");
@@ -249,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;
@@ -268,7 +268,7 @@ const hasPermissions = (value: Array<string>): boolean => {
} else {
return false;
}
};
}
export {
ascending,

View File

@@ -8,16 +8,29 @@ export const useEpThemeStore = defineStore({
state: () => ({
epThemeColor:
storageLocal.getItem("responsive-layout")?.epThemeColor ??
getConfig().EpThemeColor
getConfig().EpThemeColor,
epTheme:
storageLocal.getItem("responsive-layout")?.theme ?? getConfig().Theme
}),
getters: {
getEpThemeColor() {
return this.epThemeColor;
},
// 用于mix导航模式下hamburger-svg的fill属性
fill() {
if (this.epTheme === "light") {
return "#409eff";
} else if (this.epTheme === "yellow") {
return "#d25f00";
} else {
return "#fff";
}
}
},
actions: {
setEpThemeColor(newColor) {
const layout = storageLocal.getItem("responsive-layout");
this.epTheme = layout?.theme;
this.epThemeColor = newColor;
layout.epThemeColor = newColor;
storageLocal.setItem("responsive-layout", layout);

View File

@@ -16,9 +16,8 @@ export const useMultiTagsStore = defineStore({
parentPath: "/",
meta: {
title: "menus.hshome",
icon: "HomeFilled",
i18n: true,
showLink: true
icon: "home-filled",
i18n: true
}
}
],

View File

@@ -59,9 +59,8 @@ export const useUserStore = defineStore({
parentPath: "/",
meta: {
title: "menus.hshome",
icon: "HomeFilled",
i18n: true,
showLink: true
icon: "home-filled",
i18n: true
}
}
]);

View File

@@ -19,7 +19,7 @@
}
.el-overlay {
background-color: rgba(0, 0, 0, 0.05) !important;
background-color: rgb(0 0 0 / 5%) !important;
}
.el-drawer {

View File

@@ -25,7 +25,7 @@
}
.el-dropdown-menu {
padding: 2px 0 2px 0 !important;
padding: 2px 0 !important;
}
.el-range-separator {

View File

@@ -11,7 +11,7 @@ body {
padding: 0;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
text-rendering: optimizelegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Arial, sans-serif;
}
@@ -74,19 +74,11 @@ ul {
/* 灰色模式 */
.html-grey {
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
-moz-filter: grayscale(100%);
-ms-filter: grayscale(100%);
-o-filter: grayscale(100%);
}
/* 色弱模式 */
.html-weakness {
filter: invert(80%);
-webkit-filter: invert(80%);
-moz-filter: invert(80%);
-ms-filter: invert(80%);
-o-filter: invert(80%);
}
.pc-spacing {

View File

@@ -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;
}

View File

@@ -306,6 +306,10 @@
height: 48px;
line-height: 48px;
background: $menuBg;
svg {
position: static !important;
}
}
.is-active > .el-sub-menu__title,
@@ -498,9 +502,7 @@
background-color: $menuActiveBefore;
content: "";
clear: both;
-webkit-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
-webkit-transform: translateY(0);
transform: translateY(0);
}
@@ -513,9 +515,7 @@
background-color: $menuActiveBefore;
content: "";
clear: both;
-webkit-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
-webkit-transform: translateY(0);
transform: translateY(0);
}
@@ -526,7 +526,6 @@
position: absolute;
height: 0;
width: 3px;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
top: 50%;
}
@@ -600,16 +599,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 {
@@ -649,3 +638,64 @@ body[layout="horizontal"] {
transition: none !important;
}
}
body[layout="mix"] {
$sideBarWidth: 210px;
@include merge-style($sideBarWidth);
.el-menu {
--el-menu-hover-bg-color: transparent !important;
}
.hideSidebar {
.fixed-header {
width: calc(100% - 54px);
transition: width 0.28s;
}
.sidebar-container {
width: 54px !important;
}
.main-container {
margin-left: 54px;
}
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0 !important;
}
}
/* 菜单折叠 */
.el-menu--collapse {
.el-sub-menu {
& > .el-sub-menu__title {
& > span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
/* 无子菜单 */
.el-menu-item [class^="el-icon"] {
right: 5px;
}
.el-sub-menu__title [class^="el-icon"] {
right: 2px;
}
.submenu-title-noDropdown {
background: transparent !important;
}
}
}
}

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

@@ -20,7 +20,7 @@ export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
theme: config.Theme ?? "default",
darkMode: config.DarkMode ?? false,
sidebarStatus: config.SidebarStatus ?? true,
epThemeColor: config.EpThemeColor ?? "409EFF"
epThemeColor: config.EpThemeColor ?? "#409EFF"
}
},
configure: {
@@ -47,8 +47,7 @@ export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
meta: {
title: "menus.hshome",
i18n: true,
icon: "HomeFilled",
showLink: true
icon: "home-filled"
}
}
]

View File

@@ -46,3 +46,22 @@ export function deleteChildren(menuTree, pathList = []) {
}
return menuTree;
}
// 创建层级关系
export function buildHierarchyTree(menuTree, pathList = []) {
if (!Array.isArray(menuTree)) {
console.warn("menuTree must be an array");
return;
}
if (!menuTree || menuTree.length === 0) return;
for (const [key, node] of menuTree.entries()) {
node.id = key;
node.parentId = pathList.length ? pathList[pathList.length - 1] : null;
node.pathList = [...pathList, node.id];
const hasChildren = node.children && node.children.length > 0;
if (hasChildren) {
buildHierarchyTree(node.children, node.pathList);
}
}
return menuTree;
}

View File

@@ -140,6 +140,7 @@ onMounted(() => {
.item-cut {
font-size: 1.5em;
height: 77px;
line-height: 77px;
text-align: center;
border: 1px solid #e5e4e9;
cursor: move;

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();
@@ -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,18 +1,20 @@
module.exports = {
root: true,
plugins: ["stylelint-order"],
customSyntax: "postcss-html",
extends: ["stylelint-config-standard", "stylelint-config-prettier"],
rules: {
"selector-class-pattern": null,
"selector-pseudo-class-no-unknown": [
true,
{
ignorePseudoClasses: ["deep"]
ignorePseudoClasses: ["global"]
}
],
"selector-pseudo-element-no-unknown": [
true,
{
ignorePseudoElements: ["v-deep", ":deep"]
ignorePseudoElements: ["v-deep"]
}
],
"at-rule-no-unknown": [
@@ -65,5 +67,26 @@ module.exports = {
{ severity: "warning" }
]
},
ignoreFiles: ["**/*.js", "**/*.jsx", "**/*.tsx", "**/*.ts", "**/*.json"]
ignoreFiles: ["**/*.js", "**/*.jsx", "**/*.tsx", "**/*.ts", "**/*.json"],
overrides: [
{
files: ["*.vue", "**/*.vue", "*.html", "**/*.html"],
extends: ["stylelint-config-recommended", "stylelint-config-html"],
rules: {
"keyframes-name-pattern": null,
"selector-pseudo-class-no-unknown": [
true,
{
ignorePseudoClasses: ["deep", "global"]
}
],
"selector-pseudo-element-no-unknown": [
true,
{
ignorePseudoElements: ["v-deep", "v-global", "v-slotted"]
}
]
}
}
]
};

19
types/global.d.ts vendored
View File

@@ -6,16 +6,16 @@ import type {
PropType as VuePropType
} from "vue";
// GlobalComponents for Volar
declare module "vue" {
export interface GlobalComponents {
IconifyIconOffline: typeof import("../src/components/ReIcon")["IconifyIconOffline"];
IconifyIconOnline: typeof import("../src/components/ReIcon")["IconifyIconOnline"];
FontIcon: typeof import("../src/components/ReIcon")["FontIcon"];
}
}
declare global {
const __APP_INFO__: {
pkg: {
name: string;
version: string;
dependencies: Recordable<string>;
devDependencies: Recordable<string>;
};
lastBuildTime: string;
};
interface Window {
// Global vue app instance
__APP__: App<Element>;
@@ -23,7 +23,6 @@ declare global {
mozCancelAnimationFrame: (handle: number) => void;
oCancelAnimationFrame: (handle: number) => void;
msCancelAnimationFrame: (handle: number) => void;
webkitRequestAnimationFrame: (callback: FrameRequestCallback) => number;
mozRequestAnimationFrame: (callback: FrameRequestCallback) => number;
oRequestAnimationFrame: (callback: FrameRequestCallback) => number;

View File

@@ -1,14 +1,7 @@
import { resolve } from "path";
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 { warpperEnv, regExps } from "./build";
import { viteMockServe } from "vite-plugin-mock";
import styleImport from "vite-plugin-style-import";
import ElementPlus from "unplugin-element-plus/vite";
import { getPluginsList } from "./build/plugins";
import { UserConfigExport, ConfigEnv, loadEnv } from "vite";
import themePreprocessorPlugin from "@zougt/vite-plugin-theme-preprocessor";
// 当前执行node命令时文件夹的地址工作目录
const root: string = process.cwd();
@@ -34,7 +27,6 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => {
VITE_PROXY_DOMAIN,
VITE_PROXY_DOMAIN_REAL
} = warpperEnv(loadEnv(mode, root));
const prodMock = true;
return {
base: VITE_PUBLIC_PATH,
root,
@@ -78,105 +70,16 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => {
}
: null
},
plugins: [
vue(),
// jsx、tsx语法支持
vueJsx(),
// 自定义主题
themePreprocessorPlugin({
scss: {
multipleScopeVars: [
{
scopeName: "layout-theme-default",
path: pathResolve("src/layout/theme/default-vars.scss")
},
{
scopeName: "layout-theme-light",
path: pathResolve("src/layout/theme/light-vars.scss")
},
{
scopeName: "layout-theme-dusk",
path: pathResolve("src/layout/theme/dusk-vars.scss")
},
{
scopeName: "layout-theme-volcano",
path: pathResolve("src/layout/theme/volcano-vars.scss")
},
{
scopeName: "layout-theme-yellow",
path: pathResolve("src/layout/theme/yellow-vars.scss")
},
{
scopeName: "layout-theme-mingQing",
path: pathResolve("src/layout/theme/mingQing-vars.scss")
},
{
scopeName: "layout-theme-auroraGreen",
path: pathResolve("src/layout/theme/auroraGreen-vars.scss")
},
{
scopeName: "layout-theme-pink",
path: pathResolve("src/layout/theme/pink-vars.scss")
},
{
scopeName: "layout-theme-saucePurple",
path: pathResolve("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
],
plugins: getPluginsList(command, VITE_LEGACY),
optimizeDeps: {
include: [
"element-plus/lib/locale/lang/zh-cn",
"pinia",
"vue-i18n",
"lodash-es",
"@vueuse/core",
"@iconify/vue",
"element-plus/lib/locale/lang/en",
"element-plus/lib/locale/lang/zh-cn",
"vxe-table/lib/locale/lang/zh-CN",
"vxe-table/lib/locale/lang/en-US"
],

50
windi.config.ts Normal file
View File

@@ -0,0 +1,50 @@
// https://cn.windicss.org/ 中文文档
import { defineConfig } from "windicss/helpers";
import colors from "windicss/colors";
import typography from "windicss/plugin/typography";
export default defineConfig({
darkMode: "class",
attributify: true,
plugins: [typography()],
theme: {
extend: {
zIndex: {
"-1": "-1"
},
screens: {
sm: "576px",
md: "768px",
lg: "992px",
xl: "1200px",
"2xl": "1600px"
},
typography: {
DEFAULT: {
css: {
maxWidth: "65ch",
color: "inherit",
a: {
color: "inherit",
opacity: 0.75,
fontWeight: "500",
textDecoration: "underline",
"&:hover": {
opacity: 1,
color: colors.teal[600]
}
},
b: { color: "inherit" },
strong: { color: "inherit" },
em: { color: "inherit" },
h1: { color: "inherit" },
h2: { color: "inherit" },
h3: { color: "inherit" },
h4: { color: "inherit" },
code: { color: "inherit" }
}
}
}
}
}
});