Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a06de54113 | ||
|
|
5b94930463 | ||
|
|
5885706988 | ||
|
|
997711b264 | ||
|
|
7beb3e63fe | ||
|
|
ae57e42612 | ||
|
|
99140bbd1e | ||
|
|
89f4817cfe | ||
|
|
9802a0c51b | ||
|
|
d2d324e3f0 | ||
|
|
bee2315566 | ||
|
|
6d38b7a6aa | ||
|
|
0f6e4ab4e0 | ||
|
|
ee1a6ffeb6 | ||
|
|
b36850f79f | ||
|
|
bef0d9234e | ||
|
|
0bba4b7d64 | ||
|
|
a7576f6971 | ||
|
|
51d08e4b82 | ||
|
|
494ce8f41b | ||
|
|
8b3f642cf2 | ||
|
|
2e5667f652 | ||
|
|
8d539d4c21 | ||
|
|
c9026a45cc | ||
|
|
81c4184cc4 | ||
|
|
244f657be2 | ||
|
|
74a6b3ffdc | ||
|
|
8e7a79cf94 | ||
|
|
849b46533d | ||
|
|
7f5aeed4d1 | ||
|
|
c749149c2f | ||
|
|
0a7d22248f | ||
|
|
a35dc9d7b6 | ||
|
|
a6e819e169 | ||
|
|
3701161022 | ||
|
|
e3898df731 | ||
|
|
32172220f7 | ||
|
|
fcdfe6d0c0 | ||
|
|
67bc933d5c | ||
|
|
feb1532549 | ||
|
|
1722ee7a9b | ||
|
|
710e119397 | ||
|
|
f21d9f38e5 | ||
|
|
727c0fe3c0 | ||
|
|
e3fda52801 | ||
|
|
37762e45fd | ||
|
|
d43316f7c9 | ||
|
|
6971ba6c53 | ||
|
|
a175cf9fe0 | ||
|
|
9927e6f217 | ||
|
|
bc300eab18 | ||
|
|
3ca4729421 | ||
|
|
dfaa76cc29 | ||
|
|
7103b04283 | ||
|
|
d2ddb49314 | ||
|
|
a4a042bfd7 | ||
|
|
a9a8115d46 | ||
|
|
3676014eb6 | ||
|
|
52910602ff | ||
|
|
a2d1cf0e5f | ||
|
|
8e886e83e7 |
12
.eslintrc.js
@@ -61,6 +61,18 @@ module.exports = {
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"vue/html-self-closing": [
|
||||
"error",
|
||||
{
|
||||
html: {
|
||||
void: "always",
|
||||
normal: "always",
|
||||
component: "always"
|
||||
},
|
||||
svg: "always",
|
||||
math: "always"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
|
||||
34
.github/workflows/preview.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: preview
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "16"
|
||||
registry-url: https://registry.npmjs.com/
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: run deploy.sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm install && pnpm deploy
|
||||
1
.vscode/extensions.json
vendored
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"johnsoncodehk.vscode-typescript-vue-plugin",
|
||||
"voorjaar.windicss-intellisense",
|
||||
"vscode-icons-team.vscode-icons",
|
||||
"davidanson.vscode-markdownlint",
|
||||
|
||||
12
.vscode/settings.json
vendored
@@ -1,13 +1,9 @@
|
||||
{
|
||||
"editor.formatOnType": true,
|
||||
"editor.formatOnSave": true,
|
||||
"javascript.updateImportsOnFileMove.enabled": "always",
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
},
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnPaste": true,
|
||||
"files.autoSave": "afterDelay",
|
||||
@@ -30,14 +26,12 @@
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"i18n-ally.localesPaths": ["src/plugins/i18n"],
|
||||
"i18n-ally.localesPaths": "locales",
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"i18n-ally.namespace": true,
|
||||
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
|
||||
"i18n-ally.enabledParsers": ["ts"],
|
||||
"i18n-ally.enabledParsers": ["yaml", "js"],
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.displayLanguage": "zh-CN",
|
||||
"i18n-ally.enabledFrameworks": ["vue", "react"]
|
||||
"i18n-ally.enabledFrameworks": ["vue"]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,38 @@
|
||||
# 3.2.0 (2022-3-22)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Icon selection component
|
||||
- Menu search function
|
||||
- Added results page
|
||||
- Extended `element-plus` timeline component
|
||||
- Extended `element-plus` tree component to support connecting lines
|
||||
- Add tree selector, support single and multiple selection
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Optimized the error page UI
|
||||
- Optimize the internationalization function
|
||||
- Optimized routing `rank` sorting, compatible with the case where the value of the `rank` field in the routing `meta` is `null`
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed the situation where the menu expands and folds will freeze on some computers
|
||||
|
||||
# 3.1.0 (2022-3-3)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- iframe supports dynamic loading
|
||||
- Watermark example
|
||||
- Print examples (pictures, tables, echarts)
|
||||
- Add running and packaging information, use `lodash-unified` to replace `lodash-es`, `lodash-unified` supports `ESM` and is compatible with `CJS`
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed jumping to another menu page alone in one menu page, the routing page jumped but the tab page was not displayed
|
||||
- Fixed the route that returns dynamic level 3 and above in the background, and the menu does not correspond to the page
|
||||
|
||||
# 3.0 (2022-2-14)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
35
CHANGELOG.md
@@ -1,3 +1,38 @@
|
||||
# 3.2.0 (2022-3-22)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Icon selection component
|
||||
- Menu search function
|
||||
- Added results page
|
||||
- Extended `element-plus` timeline component
|
||||
- Extended `element-plus` tree component to support connecting lines
|
||||
- Add tree selector, support single and multiple selection
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Optimized the error page UI
|
||||
- Optimize the internationalization function
|
||||
- Optimized routing `rank` sorting, compatible with the case where the value of the `rank` field in the routing `meta` is `null`
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed the situation where the menu expands and folds will freeze on some computers
|
||||
|
||||
# 3.1.0 (2022-3-3)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- iframe supports dynamic loading
|
||||
- Watermark example
|
||||
- Print examples (pictures, tables, echarts)
|
||||
- Add running and packaging information, use `lodash-unified` to replace `lodash-es`, `lodash-unified` supports `ESM` and is compatible with `CJS`
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed jumping to another menu page alone in one menu page, the routing page jumped but the tab page was not displayed
|
||||
- Fixed the route that returns dynamic level 3 and above in the background, and the menu does not correspond to the page
|
||||
|
||||
# 3.0 (2022-2-14)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
@@ -1,3 +1,38 @@
|
||||
# 3.2.0 (2022-3-22)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- 图标选择组件
|
||||
- 菜单搜索功能
|
||||
- 添加结果页面
|
||||
- 扩展`element-plus`时间线组件
|
||||
- 扩展`element-plus`树组件,支持连接线
|
||||
- 添加树形选择器,支持单选和多选
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- 优化错误页面 UI
|
||||
- 优化国际化功能
|
||||
- 优化路由`rank`排序,兼容路由`meta`中`rank`字段值为`null`的情况
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- 修复菜单展开折叠在部分电脑出现卡顿的情况
|
||||
|
||||
# 3.1.0 (2022-3-3)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- iframe 支持动态加载
|
||||
- 水印示例
|
||||
- 打印示例(图片、表格、echarts)
|
||||
- 添加运行、打包信息, 使用`lodash-unified`替换`lodash-es`,`lodash-unified`支持`ESM`同时兼容`CJS`
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- 修复在一个菜单页面内单独跳转到另一个菜单页面,路由页面跳转了但是标签页不显示的情况
|
||||
- 修复后台返回动态三级及以上的路由,出现菜单与页面不对应的情况
|
||||
|
||||
# 3.0 (2022-2-14)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
@@ -25,7 +25,7 @@ vue-pure-admin is a free and open source middle and back-end template. Using the
|
||||
|
||||
## Preview
|
||||
|
||||
- [vue-pure-admin](http://yiming_chang.gitee.io/manages)
|
||||
- [vue-pure-admin](https://vue-pure-admin.vercel.app)
|
||||
|
||||
<p align="center">
|
||||
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4857fc7eb7d4c0f8deeefc644c1f7dd~tplv-k3u1fbpfcp-watermark.awebp?">
|
||||
@@ -46,7 +46,7 @@ Open the project in Gitpod (free online dev environment for GitHub) and start co
|
||||
```bash
|
||||
git clone https://github.com/xiaoxian521/vue-pure-admin.git
|
||||
or
|
||||
git clone https://github.com.cnpmjs.org/xiaoxian521/vue-pure-admin.git
|
||||
git clone https://gitee.com/yiming_chang/vue-pure-admin.git
|
||||
```
|
||||
|
||||
- Installation dependencies
|
||||
@@ -120,9 +120,9 @@ Support modern browsers, not IE
|
||||
|
||||
## Donate
|
||||
|
||||
If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support
|
||||
If you think this project is helpful to you, you can help the author buy a glass of juice 🍹 Show your support
|
||||
|
||||
<img src="http://yiming_chang.gitee.io/manages/pay.jpg" width="150px" height="150px" />
|
||||
<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f69bf13c5b854ed5b699807cafa0e3ce~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?" width="150px" height="150px" />
|
||||
|
||||
## License
|
||||
|
||||
@@ -134,9 +134,9 @@ In principle, no fees and copyrights are charged, and you can use it with confid
|
||||
|
||||
Thank you very much for your support, I believe the project will get better and better :heart:
|
||||
|
||||
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| <a href="https://github.com/xueyuheng"><img src="https://avatars.githubusercontent.com/u/48202935?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/taolei1990"><img src="https://avatars.githubusercontent.com/u/23173640?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/hang-kim"><img src="https://avatars.githubusercontent.com/u/52914259?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/madwolfcrazy"><img src="https://avatars.githubusercontent.com/u/223671?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/limuen"><img src="https://avatars.githubusercontent.com/u/31790606?v=4" width="60px" height="60px" /></a> |
|
||||
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen | BenLakes |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| <a href="https://github.com/xueyuheng"><img src="https://avatars.githubusercontent.com/u/48202935?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/taolei1990"><img src="https://avatars.githubusercontent.com/u/23173640?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/hang-kim"><img src="https://avatars.githubusercontent.com/u/52914259?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/madwolfcrazy"><img src="https://avatars.githubusercontent.com/u/223671?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/limuen"><img src="https://avatars.githubusercontent.com/u/31790606?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/BenLakes"><img src="https://avatars.githubusercontent.com/u/15206046?v=4" width="60px" height="60px" /></a> |
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
16
README.md
@@ -25,7 +25,7 @@ vue-pure-admin 是一个免费开源的中后台模版。使用了最新的`vue3
|
||||
|
||||
## 预览
|
||||
|
||||
- [vue-pure-admin](http://yiming_chang.gitee.io/manages)
|
||||
- [vue-pure-admin](https://vue-pure-admin.vercel.app)
|
||||
|
||||
<p align="center">
|
||||
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4857fc7eb7d4c0f8deeefc644c1f7dd~tplv-k3u1fbpfcp-watermark.awebp?">
|
||||
@@ -46,7 +46,7 @@ vue-pure-admin 是一个免费开源的中后台模版。使用了最新的`vue3
|
||||
```bash
|
||||
git clone https://github.com/xiaoxian521/vue-pure-admin.git
|
||||
or
|
||||
git clone https://github.com.cnpmjs.org/xiaoxian521/vue-pure-admin.git
|
||||
git clone https://gitee.com/yiming_chang/vue-pure-admin.git
|
||||
```
|
||||
|
||||
- 安装依赖
|
||||
@@ -120,15 +120,15 @@ pnpm build
|
||||
|
||||
## 捐赠
|
||||
|
||||
如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持
|
||||
如果你觉得这个项目对您有帮助,可以帮作者买一杯果汁 🍹 表示支持
|
||||
|
||||
<img src="http://yiming_chang.gitee.io/manages/pay.jpg" width="150px" height="150px" />
|
||||
<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f69bf13c5b854ed5b699807cafa0e3ce~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?" width="150px" height="150px" />
|
||||
|
||||
## QQ 交流群
|
||||
|
||||
群里严禁`黄`、`赌`、`毒`、`vpn`等违法行为!
|
||||
|
||||
<img src="http://yiming_chang.gitee.io/manages/qq.jpg" width="150px" height="225px" />
|
||||
<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f0697596aec84661b724f6eebdf8db17~tplv-k3u1fbpfcp-watermark.awebp?" width="150px" height="225px" />
|
||||
|
||||
## 许可证
|
||||
|
||||
@@ -140,9 +140,9 @@ pnpm build
|
||||
|
||||
非常感谢你们的支持,相信项目会越来越好 :heart:
|
||||
|
||||
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| <a href="https://github.com/xueyuheng"><img src="https://avatars.githubusercontent.com/u/48202935?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/taolei1990"><img src="https://avatars.githubusercontent.com/u/23173640?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/hang-kim"><img src="https://avatars.githubusercontent.com/u/52914259?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/madwolfcrazy"><img src="https://avatars.githubusercontent.com/u/223671?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/limuen"><img src="https://avatars.githubusercontent.com/u/31790606?v=4" width="60px" height="60px" /></a> |
|
||||
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen | BenLakes | mollerzhu |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| <a href="https://github.com/xueyuheng"><img src="https://avatars.githubusercontent.com/u/48202935?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/taolei1990"><img src="https://avatars.githubusercontent.com/u/23173640?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/hang-kim"><img src="https://avatars.githubusercontent.com/u/52914259?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/madwolfcrazy"><img src="https://avatars.githubusercontent.com/u/223671?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/limuen"><img src="https://avatars.githubusercontent.com/u/31790606?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/BenLakes"><img src="https://avatars.githubusercontent.com/u/15206046?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/mollerzhu"><img src="https://avatars.githubusercontent.com/u/49627902?v=4" width="60px" height="60px" /></a> |
|
||||
|
||||
## 贡献者
|
||||
|
||||
|
||||
85
build/info.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { readdir, stat } from "fs";
|
||||
import type { Plugin } from "vite";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import { sum } from "lodash-unified";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import { green, blue, bold } from "picocolors";
|
||||
dayjs.extend(duration);
|
||||
|
||||
const staticPath = "dist";
|
||||
const fileListTotal: number[] = [];
|
||||
|
||||
const recursiveDirectory = (folder: string, callback: Function): void => {
|
||||
readdir(folder, (err, files: string[]) => {
|
||||
if (err) throw err;
|
||||
let count = 0;
|
||||
const checkEnd = () => {
|
||||
++count == files.length && callback();
|
||||
};
|
||||
files.forEach((item: string) => {
|
||||
stat(folder + "/" + item, async (err, stats) => {
|
||||
if (err) throw err;
|
||||
if (stats.isFile()) {
|
||||
fileListTotal.push(stats.size);
|
||||
checkEnd();
|
||||
} else if (stats.isDirectory()) {
|
||||
recursiveDirectory(`${staticPath}/${item}/`, checkEnd);
|
||||
}
|
||||
});
|
||||
});
|
||||
files.length === 0 && callback();
|
||||
});
|
||||
};
|
||||
|
||||
const formatBytes = (a: number, b?: number): string => {
|
||||
if (0 == a) return "0 Bytes";
|
||||
const c = 1024,
|
||||
d = b || 2,
|
||||
e = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
|
||||
f = Math.floor(Math.log(a) / Math.log(c));
|
||||
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + " " + e[f];
|
||||
};
|
||||
|
||||
export function viteBuildInfo(): Plugin {
|
||||
let config: { command: string };
|
||||
let startTime: Dayjs;
|
||||
let endTime: Dayjs;
|
||||
return {
|
||||
name: "vite:buildInfo",
|
||||
configResolved(resolvedConfig: { command: string }) {
|
||||
config = resolvedConfig;
|
||||
},
|
||||
buildStart() {
|
||||
console.log(
|
||||
bold(
|
||||
green(
|
||||
`👏欢迎使用${blue(
|
||||
"[vue-pure-admin]"
|
||||
)},如果您感觉不错,记得点击后面链接给个star哦💖 https://github.com/xiaoxian521/vue-pure-admin`
|
||||
)
|
||||
)
|
||||
);
|
||||
if (config.command === "build") {
|
||||
startTime = dayjs(new Date());
|
||||
}
|
||||
},
|
||||
closeBundle() {
|
||||
if (config.command === "build") {
|
||||
endTime = dayjs(new Date());
|
||||
recursiveDirectory(staticPath, () => {
|
||||
console.log(
|
||||
bold(
|
||||
green(
|
||||
`恭喜打包完成🎉(总用时${dayjs
|
||||
.duration(endTime.diff(startTime))
|
||||
.format("mm分ss秒")},打包后的大小为${formatBytes(
|
||||
sum(fileListTotal)
|
||||
)})`
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { resolve } from "path";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { viteBuildInfo } from "./info";
|
||||
import svgLoader from "vite-svg-loader";
|
||||
import legacy from "@vitejs/plugin-legacy";
|
||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||
@@ -6,21 +8,29 @@ 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 VueI18n from "@intlify/vite-plugin-vue-i18n";
|
||||
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";
|
||||
import themePreprocessorPlugin from "@pureadmin/theme";
|
||||
|
||||
export function getPluginsList(command, VITE_LEGACY) {
|
||||
const prodMock = true;
|
||||
const lifecycle = process.env.npm_lifecycle_event;
|
||||
return [
|
||||
vue(),
|
||||
// https://github.com/intlify/bundle-tools/tree/main/packages/vite-plugin-vue-i18n
|
||||
VueI18n({
|
||||
runtimeOnly: true,
|
||||
compositionOnly: true,
|
||||
include: [resolve("locales/**")]
|
||||
}),
|
||||
// jsx、tsx语法支持
|
||||
vueJsx(),
|
||||
WindiCSS(),
|
||||
// 线上环境删除console
|
||||
removeConsole(),
|
||||
viteBuildInfo(),
|
||||
// 修改layout文件夹下的文件时自动重载浏览器 解决 https://github.com/xiaoxian521/vue-pure-admin/issues/170
|
||||
liveReload(["src/layout/**/*", "src/router/**/*"]),
|
||||
// 自定义主题
|
||||
|
||||
30
deploy.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Replace packaging path
|
||||
sed -i "s#VITE_PUBLIC_PATH = /#VITE_PUBLIC_PATH = /vue-pure-admin/#g" $(pwd)/.env.production
|
||||
|
||||
# Make sure the script throws the error encountered
|
||||
set -e
|
||||
|
||||
pnpm build
|
||||
cd dist
|
||||
touch README.md .nojekyll
|
||||
|
||||
# deploy to github
|
||||
if [ -z "$GITHUB_TOKEN" ]; then
|
||||
msg='deploy'
|
||||
githubUrl=git@github.com:xiaoxian521/vue-pure-admin.git
|
||||
else
|
||||
msg='ci: Automatic deployment from github actions'
|
||||
githubUrl=https://xiaoxian521:${GITHUB_TOKEN}@github.com/xiaoxian521/vue-pure-admin.git
|
||||
git config --global user.name "xiaoxian521"
|
||||
git config --global user.email "1923740402@qq.com"
|
||||
fi
|
||||
git init
|
||||
git add -A
|
||||
git commit -m "${msg}"
|
||||
# Push to github gh-pages branch
|
||||
git push -f $githubUrl master:gh-pages
|
||||
|
||||
cd -
|
||||
rm -rf dist
|
||||
14
index.html
@@ -14,20 +14,16 @@
|
||||
<body>
|
||||
<div id="app">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
body,
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
font-family: "Reggae One", cursive;
|
||||
}
|
||||
|
||||
.loader,
|
||||
@@ -53,6 +49,8 @@
|
||||
transform: translateZ(0);
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
top: 0;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
.loader:before,
|
||||
@@ -96,7 +94,7 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="loader">Loading...</div>
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
74
locales/en.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
buttons:
|
||||
hsLoginOut: LoginOut
|
||||
hsfullscreen: FullScreen
|
||||
hsexitfullscreen: ExitFullscreen
|
||||
hsrefreshRoute: RefreshRoute
|
||||
hslogin: Login
|
||||
hsadd: Add
|
||||
hsmark: Mark/Cancel
|
||||
hssave: Save
|
||||
hssearch: Search
|
||||
hsexpendAll: Expand All
|
||||
hscollapseAll: Collapse All
|
||||
hssystemSet: Open ProjectConfig
|
||||
hsdelete: Delete
|
||||
hsreload: Reload
|
||||
hscloseCurrentTab: Close CurrentTab
|
||||
hscloseLeftTabs: Close LeftTabs
|
||||
hscloseRightTabs: Close RightTabs
|
||||
hscloseOtherTabs: Close OtherTabs
|
||||
hscloseAllTabs: Close AllTabs
|
||||
menus:
|
||||
hshome: Home
|
||||
hslogin: Login
|
||||
hssysManagement: System Manage
|
||||
hsBaseinfo: Base Info
|
||||
hsDict: Dict Manage
|
||||
hseditor: Editor
|
||||
hserror: Error Page
|
||||
hsfourZeroFour: "404"
|
||||
hsfourZeroOne: "403"
|
||||
hsFive: "500"
|
||||
hscomponents: Components
|
||||
hsvideo: Video Components
|
||||
hsmap: Map Components
|
||||
hsdraggable: Draggable Components
|
||||
hssplitPane: Split Pane
|
||||
hsbutton: Button Components
|
||||
hscropping: Picture Cropping
|
||||
hscountTo: Digital Animation
|
||||
hsselector: Selector Components
|
||||
hsflowChart: Flow Chart
|
||||
hsseamless: Seamless Scroll
|
||||
hscontextmenu: Context Menu
|
||||
hsmenus: MultiLevel Menu
|
||||
hsmenu1: Menu1
|
||||
hsmenu1-1: Menu1-1
|
||||
hsmenu1-2: Menu1-2
|
||||
hsmenu1-2-1: Menu1-2-1
|
||||
hsmenu1-2-2: Menu1-2-2
|
||||
hsmenu1-3: Menu1-3
|
||||
hsmenu2: Menu2
|
||||
permission: Permission Manage
|
||||
permissionPage: Page Permission
|
||||
permissionButton: Button Permission
|
||||
hstabs: Tabs Operate
|
||||
hsguide: Guide
|
||||
hsAble: Able
|
||||
hsMenuTree: Menu Tree
|
||||
hsWatermark: Water Mark
|
||||
hsPrint: Print
|
||||
hsExternalPage: External Page
|
||||
hsPureDocument: Pure Doc(Embedded)
|
||||
externalLink: Pure Doc(External)
|
||||
hsEpDocument: Element Plus Doc(Embedded)
|
||||
hsAbout: About
|
||||
hsResult: Result Page
|
||||
hsSuccess: Success Page
|
||||
hsFail: Fail Page
|
||||
hsIconSelect: Icon Select
|
||||
hsTimeline: Time Line
|
||||
hsLineTree: LineTree
|
||||
hsAntTabs: Imitate Antdv Tabs
|
||||
hsAntAnchor: Imitate Antdv Anchor
|
||||
hsAntTreeSelect: Imitate Antdv TreeSelector
|
||||
74
locales/zh-CN.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
buttons:
|
||||
hsLoginOut: 退出系统
|
||||
hsfullscreen: 全屏
|
||||
hsexitfullscreen: 退出全屏
|
||||
hsrefreshRoute: 刷新路由
|
||||
hslogin: 登陆
|
||||
hsadd: 新增
|
||||
hsmark: 标记/取消
|
||||
hssave: 保存
|
||||
hssearch: 搜索
|
||||
hsexpendAll: 全部展开
|
||||
hscollapseAll: 全部折叠
|
||||
hssystemSet: 打开项目配置
|
||||
hsdelete: 删除
|
||||
hsreload: 重新加载
|
||||
hscloseCurrentTab: 关闭当前标签页
|
||||
hscloseLeftTabs: 关闭左侧标签页
|
||||
hscloseRightTabs: 关闭右侧标签页
|
||||
hscloseOtherTabs: 关闭其他标签页
|
||||
hscloseAllTabs: 关闭全部标签页
|
||||
menus:
|
||||
hshome: 首页
|
||||
hslogin: 登陆
|
||||
hssysManagement: 系统管理
|
||||
hsBaseinfo: 基础信息
|
||||
hsDict: 字典管理
|
||||
hseditor: 编辑器
|
||||
hserror: 错误页面
|
||||
hsfourZeroFour: "404"
|
||||
hsfourZeroOne: "403"
|
||||
hsFive: "500"
|
||||
hscomponents: 组件
|
||||
hsvideo: 视频组件
|
||||
hsmap: 地图组件
|
||||
hsdraggable: 拖拽组件
|
||||
hssplitPane: 切割面板
|
||||
hsbutton: 按钮组件
|
||||
hscropping: 图片裁剪
|
||||
hscountTo: 数字动画
|
||||
hsselector: 选择器组件
|
||||
hsflowChart: 流程图
|
||||
hsseamless: 无缝滚动
|
||||
hscontextmenu: 右键菜单
|
||||
hsmenus: 多级菜单
|
||||
hsmenu1: 菜单1
|
||||
hsmenu1-1: 菜单1-1
|
||||
hsmenu1-2: 菜单1-2
|
||||
hsmenu1-2-1: 菜单1-2-1
|
||||
hsmenu1-2-2: 菜单1-2-2
|
||||
hsmenu1-3: 菜单1-3
|
||||
hsmenu2: 菜单2
|
||||
permission: 权限管理
|
||||
permissionPage: 页面权限
|
||||
permissionButton: 按钮权限
|
||||
hstabs: 标签页操作
|
||||
hsguide: 引导页
|
||||
hsAble: 功能
|
||||
hsMenuTree: 菜单树结构
|
||||
hsWatermark: 水印
|
||||
hsPrint: 打印
|
||||
hsExternalPage: 外部页面
|
||||
hsPureDocument: 平台文档(内嵌)
|
||||
externalLink: 平台文档(外链)
|
||||
hsEpDocument: Element Plus文档(内嵌)
|
||||
hsAbout: 关于
|
||||
hsResult: 结果页面
|
||||
hsSuccess: 成功页面
|
||||
hsFail: 失败页面
|
||||
hsIconSelect: 图标选择器
|
||||
hsTimeline: 时间线
|
||||
hsLineTree: 树形连接线
|
||||
hsAntTabs: 仿antdv标签页
|
||||
hsAntAnchor: 仿antdv锚点
|
||||
hsAntTreeSelect: 仿antdv树型选择器
|
||||
@@ -4,7 +4,6 @@ import { MockMethod } from "vite-plugin-mock";
|
||||
// http://mockjs.com/examples.html#Object
|
||||
const systemRouter = {
|
||||
path: "/system",
|
||||
name: "system",
|
||||
redirect: "/system/user/index",
|
||||
meta: {
|
||||
icon: "setting",
|
||||
@@ -35,13 +34,12 @@ const systemRouter = {
|
||||
|
||||
const permissionRouter = {
|
||||
path: "/permission",
|
||||
name: "permission",
|
||||
redirect: "/permission/page/index",
|
||||
meta: {
|
||||
title: "menus.permission",
|
||||
icon: "lollipop",
|
||||
i18n: true,
|
||||
rank: 3
|
||||
rank: 7
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@@ -64,15 +62,53 @@ const permissionRouter = {
|
||||
]
|
||||
};
|
||||
|
||||
const frameRouter = {
|
||||
path: "/iframe",
|
||||
redirect: "/iframe/pure",
|
||||
meta: {
|
||||
icon: "monitor",
|
||||
title: "menus.hsExternalPage",
|
||||
i18n: true,
|
||||
rank: 10
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/iframe/pure",
|
||||
name: "reFramePure",
|
||||
meta: {
|
||||
i18n: true,
|
||||
title: "menus.hsPureDocument",
|
||||
frameSrc: "https://pure-admin-doc.vercel.app"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/external",
|
||||
name: "https://pure-admin-doc.vercel.app",
|
||||
meta: {
|
||||
title: "menus.externalLink",
|
||||
i18n: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/iframe/ep",
|
||||
name: "reFrameEp",
|
||||
meta: {
|
||||
i18n: true,
|
||||
title: "menus.hsEpDocument",
|
||||
frameSrc: "https://element-plus.gitee.io/zh-CN/"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const tabsRouter = {
|
||||
path: "/tabs",
|
||||
name: "reTabs",
|
||||
redirect: "/tabs/index",
|
||||
meta: {
|
||||
icon: "IF-team-icontabs",
|
||||
title: "menus.hstabs",
|
||||
i18n: true,
|
||||
rank: 8
|
||||
rank: 12
|
||||
},
|
||||
children: [
|
||||
{
|
||||
@@ -113,6 +149,7 @@ export default [
|
||||
code: 0,
|
||||
info: [
|
||||
tabsRouter,
|
||||
frameRouter,
|
||||
systemRouter,
|
||||
setDifAuthority("v-admin", permissionRouter)
|
||||
]
|
||||
|
||||
67
package.json
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"name": "vue-pure-admin",
|
||||
"version": "3.0",
|
||||
"version": "3.2.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">= 16",
|
||||
"pnpm": ">= 6"
|
||||
},
|
||||
"scripts": {
|
||||
"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",
|
||||
"deploy": "bash deploy.sh",
|
||||
"preview": "vite preview",
|
||||
"preview:build": "pnpm build && vite preview",
|
||||
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install",
|
||||
@@ -33,40 +30,43 @@
|
||||
"@ctrl/tinycolor": "^3.4.0",
|
||||
"@logicflow/core": "0.7.1",
|
||||
"@logicflow/extension": "0.7.1",
|
||||
"@vueuse/core": "^7.6.2",
|
||||
"@pureadmin/components": "^1.0.2",
|
||||
"@vueuse/core": "^8.0.0",
|
||||
"@vueuse/motion": "^2.0.0-beta.9",
|
||||
"@vueuse/shared": "^7.6.2",
|
||||
"@vueuse/shared": "^8.0.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^0.25.0",
|
||||
"cropperjs": "^1.5.11",
|
||||
"axios": "^0.26.1",
|
||||
"cropperjs": "^1.5.12",
|
||||
"css-color-function": "^1.3.3",
|
||||
"dayjs": "^1.10.7",
|
||||
"dayjs": "^1.11.0",
|
||||
"driver.js": "^0.9.8",
|
||||
"echarts": "^5.3.0",
|
||||
"element-plus": "^2.0.2",
|
||||
"element-plus": "^2.1.4",
|
||||
"element-resize-detector": "^1.2.3",
|
||||
"js-cookie": "^3.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lodash-unified": "^1.0.2",
|
||||
"mitt": "^3.0.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^2.0.11",
|
||||
"pinia": "^2.0.12",
|
||||
"qs": "^6.10.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"responsive-storage": "^1.0.11",
|
||||
"rgb-hex": "^4.0.0",
|
||||
"v-contextmenu": "3.0.0",
|
||||
"vue": "^3.2.31",
|
||||
"vue-i18n": "^9.2.0-beta.30",
|
||||
"vue-i18n": "^9.2.0-beta.32",
|
||||
"vue-json-pretty": "^2.0.2",
|
||||
"vue-router": "^4.0.12",
|
||||
"vue-router": "^4.0.14",
|
||||
"vue-types": "^4.1.1",
|
||||
"vuedraggable": "4.1.0",
|
||||
"vxe-table": "^4.1.18",
|
||||
"wangeditor": "^4.7.9",
|
||||
"xe-utils": "^3.5.2",
|
||||
"xgplayer": "2.28.0"
|
||||
"vxe-table": "^4.2.0",
|
||||
"wangeditor": "^4.7.12",
|
||||
"xe-utils": "^3.5.4",
|
||||
"xgplayer": "^2.31.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "13.1.0",
|
||||
@@ -75,28 +75,33 @@
|
||||
"@iconify-icons/fa": "^1.1.1",
|
||||
"@iconify-icons/fa-solid": "^1.1.2",
|
||||
"@iconify-icons/ri": "^1.1.1",
|
||||
"@iconify/vue": "^3.1.3",
|
||||
"@iconify/vue": "^3.1.4",
|
||||
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
|
||||
"@pureadmin/theme": "^0.0.1",
|
||||
"@types/element-resize-detector": "1.1.3",
|
||||
"@types/js-cookie": "^3.0.1",
|
||||
"@types/lodash": "^4.14.180",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/mockjs": "1.0.3",
|
||||
"@types/node": "14.14.14",
|
||||
"@types/nprogress": "0.2.0",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@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",
|
||||
"@vitejs/plugin-legacy": "^1.7.1",
|
||||
"@vitejs/plugin-vue": "^2.2.4",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.8",
|
||||
"@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": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^8.4.1",
|
||||
"font-awesome": "^4.7.0",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "11.1.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss": "^8.4.6",
|
||||
"postcss-html": "^1.3.0",
|
||||
"postcss-import": "14.0.0",
|
||||
@@ -104,26 +109,26 @@
|
||||
"prettier": "^2.5.1",
|
||||
"pretty-quick": "3.1.1",
|
||||
"rimraf": "3.0.2",
|
||||
"rollup-plugin-visualizer": "^5.5.4",
|
||||
"sass": "^1.49.7",
|
||||
"sass-loader": "^12.4.0",
|
||||
"rollup": "^2.70.1",
|
||||
"rollup-plugin-visualizer": "^5.6.0",
|
||||
"sass": "^1.49.9",
|
||||
"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",
|
||||
"typescript": "^4.6.2",
|
||||
"unplugin-element-plus": "^0.3.1",
|
||||
"vite": "^2.9.0-beta.4",
|
||||
"vite-plugin-live-reload": "^2.1.0",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-remove-console": "^0.0.6",
|
||||
"vite-plugin-style-import": "1.4.1",
|
||||
"vite-plugin-windicss": "^1.7.0",
|
||||
"vite-plugin-windicss": "^1.8.3",
|
||||
"vite-svg-loader": "2.2.0",
|
||||
"vue-eslint-parser": "^8.2.0",
|
||||
"windicss": "^3.4.3"
|
||||
"windicss": "^3.5.1"
|
||||
},
|
||||
"repository": "git@github.com:xiaoxian521/vue-pure-admin.git",
|
||||
"author": "xiaoxian521",
|
||||
|
||||
1903
pnpm-lock.yaml
generated
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Version": "3.0",
|
||||
"Version": "3.2.0",
|
||||
"Title": "PureAdmin",
|
||||
"FixedHeader": true,
|
||||
"HiddenSideBar": false,
|
||||
|
||||
|
Before Width: | Height: | Size: 680 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
1
src/assets/status/403.svg
Normal file
|
After Width: | Height: | Size: 12 KiB |
1
src/assets/status/404.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
src/assets/status/500.svg
Normal file
|
After Width: | Height: | Size: 15 KiB |
1
src/assets/svg/enter_outlined.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--ant-design" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 1024 1024"><path fill="currentColor" d="M864 170h-60c-4.4 0-8 3.6-8 8v518H310v-73c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 0 0 0 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-75h498c35.3 0 64-28.7 64-64V178c0-4.4-3.6-8-8-8z"></path></svg>
|
||||
|
After Width: | Height: | Size: 448 B |
1
src/assets/svg/mdi_keyboard_esc.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--mdi" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path fill="currentColor" d="M1 7h6v2H3v2h4v2H3v2h4v2H1V7m10 0h4v2h-4v2h2a2 2 0 0 1 2 2v2c0 1.11-.89 2-2 2H9v-2h4v-2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2m8 0h2a2 2 0 0 1 2 2v1h-2V9h-2v6h2v-1h2v1c0 1.11-.89 2-2 2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 477 B |
@@ -92,5 +92,5 @@ tryOnUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="'bar' + props.index" style="width: 100%; height: 35vh"></div>
|
||||
<div :class="'bar' + props.index" style="width: 100%; height: 35vh" />
|
||||
</template>
|
||||
|
||||
@@ -78,9 +78,9 @@ let classOption = reactive({
|
||||
>
|
||||
<ul class="item">
|
||||
<li v-for="(item, index) in listData" :key="index">
|
||||
<span v-text="item.date"></span>
|
||||
<span v-text="item.name"></span>
|
||||
<span v-text="item.star"></span>
|
||||
<span v-text="item.date" />
|
||||
<span v-text="item.name" />
|
||||
<span v-text="item.star" />
|
||||
</li>
|
||||
</ul>
|
||||
</SeamlessScroll>
|
||||
|
||||
@@ -80,5 +80,5 @@ tryOnUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="'line' + props.index" style="width: 100%; height: 35vh"></div>
|
||||
<div :class="'line' + props.index" style="width: 100%; height: 35vh" />
|
||||
</template>
|
||||
|
||||
@@ -83,5 +83,5 @@ tryOnUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="'pie' + props.index" style="width: 100%; height: 35vh"></div>
|
||||
<div :class="'pie' + props.index" style="width: 100%; height: 35vh" />
|
||||
</template>
|
||||
|
||||
39
src/components/ReFlicker/index.css
Normal file
@@ -0,0 +1,39 @@
|
||||
.point {
|
||||
width: var(--point-width);
|
||||
height: var(--point-height);
|
||||
background: var(--point-background);
|
||||
position: relative;
|
||||
border-radius: var(--point-border-radius);
|
||||
}
|
||||
|
||||
.point-flicker:after {
|
||||
background: var(--point-background);
|
||||
}
|
||||
|
||||
.point-flicker:before,
|
||||
.point-flicker:after {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
border-radius: var(--point-border-radius);
|
||||
animation: flicker 1.2s ease-out infinite;
|
||||
}
|
||||
|
||||
@keyframes flicker {
|
||||
0% {
|
||||
transform: scale(0.5);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
30% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(var(--point-scale));
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
44
src/components/ReFlicker/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import "./index.css";
|
||||
import { h, defineComponent, Component } from "vue";
|
||||
|
||||
export interface attrsType {
|
||||
width?: string;
|
||||
height?: string;
|
||||
borderRadius?: number | string;
|
||||
background?: string;
|
||||
scale?: number | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 圆点、方形闪烁动画组件
|
||||
* @param width 可选 string 宽
|
||||
* @param height 可选 string 高
|
||||
* @param borderRadius 可选 number | string 传0为方形、传50%或者不传为圆形
|
||||
* @param background 可选 string 闪烁颜色
|
||||
* @param scale 可选 number | string 闪烁范围,默认2,值越大闪烁范围越大
|
||||
* @returns Component
|
||||
*/
|
||||
export function useRenderFlicker(attrs?: attrsType): Component {
|
||||
return defineComponent({
|
||||
name: "Flicker",
|
||||
render() {
|
||||
return h(
|
||||
"div",
|
||||
{
|
||||
class: "point point-flicker",
|
||||
style: {
|
||||
"--point-width": attrs?.width ?? "12px",
|
||||
"--point-height": attrs?.height ?? "12px",
|
||||
"--point-background":
|
||||
attrs?.background ?? "var(--el-color-primary)",
|
||||
"--point-border-radius": attrs?.borderRadius ?? "50%",
|
||||
"--point-scale": attrs?.scale ?? "2"
|
||||
}
|
||||
},
|
||||
{
|
||||
default: () => []
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -107,7 +107,7 @@ onMounted(() => {
|
||||
}"
|
||||
@click="onControl(item, key)"
|
||||
>
|
||||
<span :class="'iconfont ' + item.icon"></span>
|
||||
<span :class="'iconfont ' + item.icon" />
|
||||
<p>{{ item.text }}</p>
|
||||
</button>
|
||||
</li>
|
||||
@@ -126,7 +126,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-size: 25px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.control-container p {
|
||||
|
||||
@@ -13,5 +13,5 @@ const props = defineProps({
|
||||
:deep="3"
|
||||
:showLength="true"
|
||||
:data="props.graphData"
|
||||
></vue-json-pretty>
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -44,7 +44,7 @@ const nodeDragNode = item => {
|
||||
<div
|
||||
v-if="item.type === 'user' || item.type === 'time'"
|
||||
class="shape"
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
<span class="node-label">{{ item.text }}</span>
|
||||
</div>
|
||||
|
||||
1961
src/components/ReIcon/data.ts
Normal file
@@ -1,13 +1,16 @@
|
||||
import iconifyIconOffline from "./src/iconifyIconOffline";
|
||||
import iconifyIconOnline from "./src/iconifyIconOnline";
|
||||
import fontIcon from "./src/iconfont";
|
||||
import iconSelect from "./src/select.vue";
|
||||
|
||||
export const IconifyIconOffline = iconifyIconOffline;
|
||||
export const IconifyIconOnline = iconifyIconOnline;
|
||||
export const FontIcon = fontIcon;
|
||||
export const IconSelect = iconSelect;
|
||||
|
||||
export default {
|
||||
IconifyIconOffline,
|
||||
IconifyIconOnline,
|
||||
FontIcon
|
||||
FontIcon,
|
||||
IconSelect
|
||||
};
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { iconType } from "./types";
|
||||
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 {
|
||||
/**
|
||||
* 支持fontawesome4、5+、iconfont、remixicon、element-plus的icons、自定义svg
|
||||
* @param icon 必传 string 图标
|
||||
* @param attrs 可选 iconType 属性
|
||||
* @returns Component
|
||||
*/
|
||||
export function useRenderIcon(icon: string, attrs?: iconType): Component {
|
||||
// iconfont
|
||||
const ifReg = /^IF-/;
|
||||
// typeof icon === "function" 属于SVG
|
||||
@@ -19,7 +25,8 @@ export function useRenderIcon(icon: string): Component {
|
||||
render() {
|
||||
return h(FontIcon, {
|
||||
icon: iconName,
|
||||
iconType
|
||||
iconType,
|
||||
...attrs
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -31,7 +38,8 @@ export function useRenderIcon(icon: string): Component {
|
||||
name: "Icon",
|
||||
render() {
|
||||
return h(IconifyIconOffline, {
|
||||
icon: icon
|
||||
icon: icon,
|
||||
...attrs
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -24,6 +24,10 @@ 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";
|
||||
import Rank from "@iconify-icons/ep/rank";
|
||||
import videoPlay from "@iconify-icons/ep/video-play";
|
||||
import Monitor from "@iconify-icons/ep/monitor";
|
||||
import Search from "@iconify-icons/ep/search";
|
||||
addIcon("check", Check);
|
||||
addIcon("menu", Menu);
|
||||
addIcon("home-filled", HomeFilled);
|
||||
@@ -46,16 +50,36 @@ addIcon("location", Location);
|
||||
addIcon("tickets", Tickets);
|
||||
addIcon("office-building", OfficeBuilding);
|
||||
addIcon("notebook", Notebook);
|
||||
addIcon("video-play", videoPlay);
|
||||
addIcon("rank", Rank);
|
||||
addIcon("monitor", Monitor);
|
||||
addIcon("search", Search);
|
||||
|
||||
// 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";
|
||||
import ubuntuFill from "@iconify-icons/ri/ubuntu-fill";
|
||||
import questionLine from "@iconify-icons/ri/question-line";
|
||||
import checkboxCircleLine from "@iconify-icons/ri/checkbox-circle-line";
|
||||
import informationLine from "@iconify-icons/ri/information-line";
|
||||
import closeCircleLine from "@iconify-icons/ri/close-circle-line";
|
||||
import arrowUpLine from "@iconify-icons/ri/arrow-up-line";
|
||||
import arrowDownLine from "@iconify-icons/ri/arrow-down-line";
|
||||
import bookmark2Line from "@iconify-icons/ri/bookmark-2-line";
|
||||
addIcon("arrow-right-s-line", arrowRightSLine);
|
||||
addIcon("arrow-left-s-line", arrowLeftSLine);
|
||||
addIcon("logout-circle-r-line", logoutCircleRLine);
|
||||
addIcon("node-tree", nodeTree);
|
||||
addIcon("ubuntu-fill", ubuntuFill);
|
||||
addIcon("question-line", questionLine);
|
||||
addIcon("checkbox-circle-line", checkboxCircleLine);
|
||||
addIcon("information-line", informationLine);
|
||||
addIcon("close-circle-line", closeCircleLine);
|
||||
addIcon("arrow-up-line", arrowUpLine);
|
||||
addIcon("arrow-down-line", arrowDownLine);
|
||||
addIcon("bookmark-2-line", bookmark2Line);
|
||||
|
||||
// Font Awesome 4
|
||||
import faUser from "@iconify-icons/fa/user";
|
||||
|
||||
184
src/components/ReIcon/src/select.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<script setup lang="ts">
|
||||
import { cloneDeep } from "lodash-unified";
|
||||
import { ref, computed, CSSProperties } from "vue";
|
||||
import { IconJson } from "/@/components/ReIcon/data";
|
||||
type ParameterCSSProperties = (item?: string) => CSSProperties | undefined;
|
||||
|
||||
let inputValue = ref("ep:add-location");
|
||||
let iconList = ref(IconJson);
|
||||
let icon = ref("add-location");
|
||||
let currentActiveType = ref("ep:");
|
||||
// 深拷贝图标数据,前端做搜索
|
||||
let copyIconList = cloneDeep(iconList.value);
|
||||
|
||||
let pageSize = ref(96);
|
||||
let currentPage = ref(1);
|
||||
|
||||
// 搜索条件
|
||||
let filterValue = ref("");
|
||||
|
||||
let tabsList = [
|
||||
{
|
||||
label: "Element Plus",
|
||||
name: "ep:"
|
||||
},
|
||||
{
|
||||
label: "Font Awesome 4",
|
||||
name: "fa:"
|
||||
},
|
||||
{
|
||||
label: "Font Awesome 5 Solid",
|
||||
name: "fa-solid:"
|
||||
}
|
||||
];
|
||||
|
||||
let pageList = computed(() => {
|
||||
if (currentPage.value === 1) {
|
||||
return copyIconList[currentActiveType.value]
|
||||
.slice(currentPage.value - 1, pageSize.value)
|
||||
.filter(v => v.includes(filterValue.value));
|
||||
} else {
|
||||
return copyIconList[currentActiveType.value]
|
||||
.slice(
|
||||
pageSize.value * (currentPage.value - 1),
|
||||
pageSize.value * (currentPage.value - 1) + pageSize.value
|
||||
)
|
||||
.filter(v => v.includes(filterValue.value));
|
||||
}
|
||||
});
|
||||
|
||||
const iconItemStyle = computed((): ParameterCSSProperties => {
|
||||
return item => {
|
||||
if (inputValue.value === currentActiveType.value + item) {
|
||||
return {
|
||||
borderColor: "var(--el-color-primary)"
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function handleClick({ props }) {
|
||||
currentPage.value = 1;
|
||||
currentActiveType.value = props.name;
|
||||
inputValue.value =
|
||||
currentActiveType.value + iconList.value[currentActiveType.value][0];
|
||||
icon.value = iconList.value[currentActiveType.value][0];
|
||||
}
|
||||
|
||||
function onChangeIcon(item) {
|
||||
inputValue.value = currentActiveType.value + item;
|
||||
icon.value = item;
|
||||
}
|
||||
|
||||
function onCurrentChange(page) {
|
||||
currentPage.value = page;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="selector w-350px">
|
||||
<el-input v-model="inputValue" disabled>
|
||||
<template #append>
|
||||
<el-popover :width="350" trigger="click" popper-class="pure-popper">
|
||||
<template #reference>
|
||||
<div
|
||||
class="w-40px h-32px cursor-pointer flex justify-center items-center"
|
||||
>
|
||||
<IconifyIconOnline :icon="icon" :type="currentActiveType" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-input
|
||||
class="p-2"
|
||||
v-model="filterValue"
|
||||
placeholder="搜索图标"
|
||||
clearable
|
||||
/>
|
||||
<el-divider border-style="dashed" />
|
||||
|
||||
<el-tabs v-model="currentActiveType" @tab-click="handleClick">
|
||||
<el-tab-pane
|
||||
v-for="(pane, index) in tabsList"
|
||||
:key="index"
|
||||
:label="pane.label"
|
||||
:name="pane.name"
|
||||
>
|
||||
<el-divider class="tab-divider" border-style="dashed" />
|
||||
<el-scrollbar height="220px">
|
||||
<ul class="flex flex-wrap px-2 ml-2">
|
||||
<li
|
||||
v-for="(item, key) in pageList"
|
||||
:key="key"
|
||||
:title="item"
|
||||
class="icon-item p-2 w-1/10 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-solid"
|
||||
:style="iconItemStyle(item)"
|
||||
@click="onChangeIcon(item)"
|
||||
>
|
||||
<IconifyIconOnline :icon="item" :type="currentActiveType" />
|
||||
</li>
|
||||
</ul>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-divider border-style="dashed" />
|
||||
|
||||
<el-pagination
|
||||
small
|
||||
:total="copyIconList[currentActiveType].length"
|
||||
:page-size="pageSize"
|
||||
:current-page="currentPage"
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
class="flex items-center justify-center h-10"
|
||||
@current-change="onCurrentChange"
|
||||
/>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-divider--horizontal {
|
||||
margin: 1px auto !important;
|
||||
}
|
||||
|
||||
.tab-divider.el-divider--horizontal {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.icon-item {
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-next) {
|
||||
font-size: 15px;
|
||||
line-height: 32px;
|
||||
box-shadow: -5px 0 5px -6px #ccc;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-prev) {
|
||||
font-size: 15px;
|
||||
line-height: 32px;
|
||||
box-shadow: 5px 0 5px -6px #ccc;
|
||||
}
|
||||
|
||||
:deep(.el-input-group__append) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__header),
|
||||
:deep(.el-tabs__nav-wrap) {
|
||||
margin: 0;
|
||||
position: static;
|
||||
}
|
||||
</style>
|
||||
18
src/components/ReIcon/src/types.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface iconType {
|
||||
// iconify (https://docs.iconify.design/icon-components/vue/#properties)
|
||||
inline?: boolean;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
horizontalFlip?: boolean;
|
||||
verticalFlip?: boolean;
|
||||
flip?: string;
|
||||
rotate?: number | string;
|
||||
color?: string;
|
||||
horizontalAlign?: boolean;
|
||||
verticalAlign?: boolean;
|
||||
align?: string;
|
||||
onLoad?: Function;
|
||||
|
||||
// all icon
|
||||
style?: object;
|
||||
}
|
||||
@@ -19,10 +19,6 @@ type resultType = {
|
||||
info: Array<undefined>;
|
||||
};
|
||||
|
||||
export interface mapInter {
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
let MarkerCluster;
|
||||
let map: MapConfigureInter;
|
||||
|
||||
@@ -126,7 +122,7 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="mapview" ref="mapview" v-loading="mapSet.loading"></div>
|
||||
<div id="mapview" ref="mapview" v-loading="mapSet.loading" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -498,7 +498,7 @@ defineExpose({
|
||||
:class="leftSwitchClass"
|
||||
@click="leftSwitchClick"
|
||||
>
|
||||
<slot name="left-switch"></slot>
|
||||
<slot name="left-switch" />
|
||||
</div>
|
||||
<div
|
||||
:style="rightSwitch"
|
||||
@@ -506,7 +506,7 @@ defineExpose({
|
||||
:class="rightSwitchClass"
|
||||
@click="rightSwitchClick"
|
||||
>
|
||||
<slot name="right-switch"></slot>
|
||||
<slot name="right-switch" />
|
||||
</div>
|
||||
<div
|
||||
:ref="'realBox' + classOption['key']"
|
||||
@@ -519,9 +519,9 @@ defineExpose({
|
||||
@mousewheel="wheel"
|
||||
>
|
||||
<div :ref="'slotList' + classOption['key']" :style="float">
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</div>
|
||||
<div v-html="copyHtml" :style="float"></div>
|
||||
<div v-html="copyHtml" :style="float" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
49
src/components/ReTreeLine/index.scss
Normal file
@@ -0,0 +1,49 @@
|
||||
$--element-tree-line-color: #dcdfe6 !default;
|
||||
$--element-tree-line-style: dashed !default;
|
||||
$--element-tree-line-width: 1px !default;
|
||||
|
||||
/* 添加 el-tree-node__conten 默认没有的 position */
|
||||
.el-tree .el-tree-node__content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.element-tree-node-label-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.element-tree-node-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
.element-tree-node-line-ver {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
border-left: $--element-tree-line-width $--element-tree-line-style
|
||||
$--element-tree-line-color;
|
||||
&.last-node-line {
|
||||
border-left: $--element-tree-line-width $--element-tree-line-style
|
||||
transparent;
|
||||
}
|
||||
&.last-node-isLeaf-line {
|
||||
height: 50%;
|
||||
}
|
||||
}
|
||||
.element-tree-node-line-hor {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
height: 0;
|
||||
border-bottom: $--element-tree-line-width $--element-tree-line-style
|
||||
$--element-tree-line-color;
|
||||
}
|
||||
.element-tree-node-label-line {
|
||||
flex: 1;
|
||||
border-top: $--element-tree-line-width $--element-tree-line-style
|
||||
$--element-tree-line-color;
|
||||
align-self: center;
|
||||
margin: 0 10px;
|
||||
}
|
||||
155
src/components/ReTreeLine/index.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
// 参考https://www.npmjs.com/package/element-tree-line (主要是替换需要通过函数传参的方式去注册组件,并添加更好的类型支持,并移除this.$scopedSlots,在3.x中,将所有this.$scopedSlots替换为this.$slots)
|
||||
import { isFunction } from "/@/utils/is";
|
||||
import { h, defineComponent } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import "./index.scss";
|
||||
import type {
|
||||
TreeNode,
|
||||
TreeData,
|
||||
TreeNodeData
|
||||
} from "element-plus/es/components/tree-v2/src/types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "el-tree-line",
|
||||
props: {
|
||||
node: {
|
||||
type: Object as PropType<TreeNode>,
|
||||
required: true
|
||||
},
|
||||
data: {
|
||||
type: Array as PropType<TreeNodeData>,
|
||||
default: () => {}
|
||||
},
|
||||
treeData: {
|
||||
type: Array as PropType<TreeData>,
|
||||
default: () => []
|
||||
},
|
||||
indent: {
|
||||
type: Number,
|
||||
default: 16
|
||||
},
|
||||
showLabelLine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
setup(_, context) {
|
||||
const { slots } = context;
|
||||
const getScopedSlot = slotName => {
|
||||
if (!slotName) {
|
||||
return null;
|
||||
}
|
||||
const slotNameSplits = slotName.split("||");
|
||||
let slot = null;
|
||||
for (let index = 0; index < slotNameSplits.length; index++) {
|
||||
const name = slotNameSplits[index];
|
||||
slot = (slots || {})[name];
|
||||
}
|
||||
return slot;
|
||||
};
|
||||
const getSlotValue = (slot, scopedData, defaultNode = null) => {
|
||||
if (isFunction(slot)) {
|
||||
return slot(scopedData) || defaultNode;
|
||||
}
|
||||
return slot || defaultNode;
|
||||
};
|
||||
|
||||
return {
|
||||
getScopedSlot,
|
||||
getSlotValue
|
||||
};
|
||||
},
|
||||
render() {
|
||||
// 自定义整行节点label区域
|
||||
const scopeSlotDefault = this.getScopedSlot("default");
|
||||
// 显示横线时自定义节点label区域
|
||||
const labelSlot = this.getScopedSlot("node-label");
|
||||
// 显示横线时追加在横线右边的内容
|
||||
const afterLabelSlot = this.getScopedSlot("after-node-label");
|
||||
const labelNodes = scopeSlotDefault
|
||||
? this.getSlotValue(scopeSlotDefault, {
|
||||
node: this.node,
|
||||
data: this.data
|
||||
})
|
||||
: [
|
||||
labelSlot
|
||||
? this.getSlotValue(labelSlot, {
|
||||
node: this.node,
|
||||
data: this.data
|
||||
})
|
||||
: h("span", { class: "element-tree-node-label" }, this.node.label),
|
||||
this.showLabelLine
|
||||
? h("span", {
|
||||
class: "element-tree-node-label-line"
|
||||
})
|
||||
: null,
|
||||
this.getSlotValue(afterLabelSlot, {
|
||||
node: this.node,
|
||||
data: this.data
|
||||
})
|
||||
];
|
||||
// 取得每一层的当前节点是不是在当前层级列表的最后一个
|
||||
const lastnodeArr = [];
|
||||
let currentNode = this.node;
|
||||
while (currentNode) {
|
||||
let parentNode = currentNode.parent;
|
||||
// 兼容element-plus的 el-tree-v2 (Virtualized Tree 虚拟树)
|
||||
if (currentNode.level === 1 && !currentNode.parent) {
|
||||
// el-tree-v2的第一层node是没有parent的,必需 treeData 创建一个parent
|
||||
if (!this.treeData || !Array.isArray(this.treeData)) {
|
||||
throw Error(
|
||||
"if you using el-tree-v2 (Virtualized Tree) of element-plus,element-tree-line required data."
|
||||
);
|
||||
}
|
||||
parentNode = {
|
||||
children: Array.isArray(this.treeData)
|
||||
? this.treeData.map(item => {
|
||||
return { ...item, key: item.id };
|
||||
})
|
||||
: [],
|
||||
level: 0,
|
||||
key: "node-0",
|
||||
parent: null
|
||||
};
|
||||
}
|
||||
if (parentNode) {
|
||||
// element-plus的 el-tree-v2 使用的是children和key, 其他使用的是 childNodes和id
|
||||
const index = (parentNode.children || parentNode.childNodes).findIndex(
|
||||
item => (item.key || item.id) === (currentNode.key || currentNode.id)
|
||||
);
|
||||
lastnodeArr.unshift(
|
||||
index === (parentNode.children || parentNode.childNodes).length - 1
|
||||
);
|
||||
}
|
||||
currentNode = parentNode;
|
||||
}
|
||||
const lineNodes = [];
|
||||
for (let i = 0; i < this.node.level; i++) {
|
||||
lineNodes.push(
|
||||
h("span", {
|
||||
class: {
|
||||
"element-tree-node-line-ver": true,
|
||||
"last-node-line": lastnodeArr[i] && this.node.level - 1 !== i,
|
||||
"last-node-isLeaf-line": lastnodeArr[i] && this.node.level - 1 === i
|
||||
},
|
||||
style: { left: this.indent * i + "px" }
|
||||
})
|
||||
);
|
||||
}
|
||||
return h(
|
||||
"span",
|
||||
{
|
||||
class: "element-tree-node-label-wrapper"
|
||||
},
|
||||
[labelNodes].concat(lineNodes).concat([
|
||||
h("span", {
|
||||
class: "element-tree-node-line-hor",
|
||||
style: {
|
||||
width: (this.node.isLeaf ? 24 : 8) + "px",
|
||||
left: (this.node.level - 1) * this.indent + "px"
|
||||
}
|
||||
})
|
||||
])
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
defineComponent,
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
import { RouterView } from "vue-router";
|
||||
import backTop from "/@/assets/svg/back_top.svg?component";
|
||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useNav } from "../hooks/nav";
|
||||
import { useRoute } from "vue-router";
|
||||
import Search from "./search/index.vue";
|
||||
import Notice from "./notice/index.vue";
|
||||
import mixNav from "./sidebar/mixNav.vue";
|
||||
import avatars from "/@/assets/avatars.jpg";
|
||||
@@ -13,7 +14,7 @@ import screenfull from "../components/screenfull/index.vue";
|
||||
import globalization from "/@/assets/svg/globalization.svg?component";
|
||||
|
||||
const route = useRoute();
|
||||
const { locale } = useI18n();
|
||||
const { locale, t } = useI18n();
|
||||
const instance =
|
||||
getCurrentInstance().appContext.config.globalProperties.$storage;
|
||||
const {
|
||||
@@ -58,6 +59,8 @@ function translationEn() {
|
||||
<mixNav v-if="pureApp.layout === 'mix'" />
|
||||
|
||||
<div v-if="pureApp.layout === 'vertical'" class="vertical-header-right">
|
||||
<!-- 菜单搜索 -->
|
||||
<Search />
|
||||
<!-- 通知 -->
|
||||
<Notice id="header-notice" />
|
||||
<!-- 全屏 -->
|
||||
@@ -79,10 +82,11 @@ function translationEn() {
|
||||
<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
|
||||
>
|
||||
<span class="check-en" v-show="locale === 'en'">
|
||||
<IconifyIconOffline icon="check" /> </span
|
||||
>English
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
@@ -98,18 +102,18 @@ function translationEn() {
|
||||
<IconifyIconOffline
|
||||
icon="logout-circle-r-line"
|
||||
style="margin: 5px"
|
||||
/>{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
|
||||
/>{{ t("buttons.hsLoginOut") }}</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-icon
|
||||
<span
|
||||
class="el-icon-setting"
|
||||
:title="$t('buttons.hssystemSet')"
|
||||
:title="t('buttons.hssystemSet')"
|
||||
@click="onPanel"
|
||||
>
|
||||
<IconifyIconOffline icon="setting" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import NoticeList from "./noticeList.vue";
|
||||
import { noticesData } from "./data";
|
||||
import NoticeList from "./noticeList.vue";
|
||||
import { templateRef } from "@vueuse/core";
|
||||
import { Tabs, TabPane } from "@pureadmin/components";
|
||||
|
||||
const dropdownDom = templateRef<ElRef | null>("dropdownDom", null);
|
||||
const activeName = ref(noticesData[0].name);
|
||||
const notices = ref(noticesData);
|
||||
|
||||
@@ -10,38 +13,51 @@ let noticesNum = ref(0);
|
||||
notices.value.forEach(notice => {
|
||||
noticesNum.value += notice.list.length;
|
||||
});
|
||||
|
||||
function tabClick() {
|
||||
// @ts-expect-error
|
||||
dropdownDom.value.handleOpen();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dropdown trigger="click" placement="bottom-end">
|
||||
<el-dropdown ref="dropdownDom" trigger="click" placement="bottom-end">
|
||||
<span class="dropdown-badge">
|
||||
<el-badge :value="noticesNum" :max="99">
|
||||
<el-icon class="header-notice-icon"
|
||||
><IconifyIconOffline icon="bell"
|
||||
/></el-icon>
|
||||
<span class="header-notice-icon">
|
||||
<IconifyIconOffline icon="bell" />
|
||||
</span>
|
||||
</el-badge>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-tabs v-model="activeName" class="dropdown-tabs">
|
||||
<Tabs
|
||||
centered
|
||||
class="dropdown-tabs"
|
||||
v-model:activeName="activeName"
|
||||
@tabClick="tabClick"
|
||||
>
|
||||
<template v-for="item in notices" :key="item.key">
|
||||
<el-tab-pane
|
||||
:label="`${item.name}(${item.list.length})`"
|
||||
:name="item.name"
|
||||
>
|
||||
<TabPane :tab="`${item.name}(${item.list.length})`">
|
||||
<el-scrollbar max-height="330px">
|
||||
<div class="noticeList-container">
|
||||
<NoticeList :list="item.list" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</TabPane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</Tabs>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.ant-tabs-dropdown {
|
||||
z-index: 2900 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dropdown-badge {
|
||||
display: flex;
|
||||
@@ -79,4 +95,8 @@ notices.value.forEach(notice => {
|
||||
padding: 15px 24px 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-nav) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,8 +10,8 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const titleRef = ref(null);
|
||||
const descriptionRef = ref(null);
|
||||
const titleTooltip = ref(false);
|
||||
const descriptionRef = ref(null);
|
||||
const descriptionTooltip = ref(false);
|
||||
|
||||
function hoverTitle() {
|
||||
@@ -50,7 +50,7 @@ function hoverDescription(event, description) {
|
||||
:size="30"
|
||||
:src="props.noticeItem.avatar"
|
||||
class="notice-container-avatar"
|
||||
></el-avatar>
|
||||
/>
|
||||
<div class="notice-container-text">
|
||||
<div class="notice-text-title">
|
||||
<el-tooltip
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import NoticeItem from "./noticeItem.vue";
|
||||
import { ListItem } from "./data";
|
||||
import NoticeItem from "./noticeItem.vue";
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
@@ -17,7 +17,7 @@ const props = defineProps({
|
||||
v-for="(item, index) in props.list"
|
||||
:noticeItem="item"
|
||||
:key="index"
|
||||
></NoticeItem>
|
||||
/>
|
||||
</div>
|
||||
<el-empty v-else description="暂无数据"></el-empty>
|
||||
<el-empty v-else description="暂无数据" />
|
||||
</template>
|
||||
|
||||
@@ -26,7 +26,7 @@ emitter.on("openPanel", () => {
|
||||
<IconifyIconOffline icon="close" />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div style="border-bottom: 1px solid #dcdfe6"></div>
|
||||
<div style="border-bottom: 1px solid #dcdfe6" />
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { useFullscreen } from "@vueuse/core";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
</script>
|
||||
|
||||
@@ -7,9 +10,7 @@ const { isFullscreen, toggle } = useFullscreen();
|
||||
<div class="screen-full" @click="toggle">
|
||||
<FontIcon
|
||||
:title="
|
||||
isFullscreen
|
||||
? $t('buttons.hsexitfullscreen')
|
||||
: $t('buttons.hsfullscreen')
|
||||
isFullscreen ? t('buttons.hsexitfullscreen') : t('buttons.hsfullscreen')
|
||||
"
|
||||
:icon="isFullscreen ? 'team-iconexit-fullscreen' : 'team-iconfullscreen'"
|
||||
/>
|
||||
|
||||
42
src/layout/components/search/components/SearchFooter.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="search-footer">
|
||||
<span class="search-footer-item">
|
||||
<enterOutlined class="icon" />
|
||||
确认
|
||||
</span>
|
||||
<span class="search-footer-item">
|
||||
<IconifyIconOffline icon="arrow-up-line" class="icon" />
|
||||
<IconifyIconOffline icon="arrow-down-line" class="icon" />
|
||||
切换
|
||||
</span>
|
||||
<span class="search-footer-item">
|
||||
<mdiKeyboardEsc class="icon" />
|
||||
关闭
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import enterOutlined from "/@/assets/svg/enter_outlined.svg?component";
|
||||
import mdiKeyboardEsc from "/@/assets/svg/mdi_keyboard_esc.svg?component";
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.search-footer {
|
||||
display: flex;
|
||||
color: #333;
|
||||
|
||||
.search-footer-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 2px;
|
||||
margin-right: 3px;
|
||||
font-size: 20px;
|
||||
box-shadow: inset 0 -2px #cdcde6, inset 0 0 1px 1px #fff,
|
||||
0 1px 2px 1px #1e235a66;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
165
src/layout/components/search/components/SearchModal.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRouter } from "vue-router";
|
||||
import SearchResult from "./SearchResult.vue";
|
||||
import SearchFooter from "./SearchFooter.vue";
|
||||
import { deleteChildren } from "/@/utils/tree";
|
||||
import { transformI18n } from "/@/plugins/i18n";
|
||||
import { useDebounceFn, onKeyStroke } from "@vueuse/core";
|
||||
import { ref, watch, computed, nextTick, shallowRef } from "vue";
|
||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
|
||||
|
||||
interface Props {
|
||||
/** 弹窗显隐 */
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: "update:value", val: boolean): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
const router = useRouter();
|
||||
|
||||
const keyword = ref("");
|
||||
const activePath = ref("");
|
||||
const inputRef = ref<HTMLInputElement | null>(null);
|
||||
const resultOptions = shallowRef([]);
|
||||
const handleSearch = useDebounceFn(search, 300);
|
||||
|
||||
/** 菜单树形结构 */
|
||||
const menusData = computed(() => {
|
||||
return deleteChildren(usePermissionStoreHook().menusTree);
|
||||
});
|
||||
|
||||
const show = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(val: boolean) {
|
||||
emit("update:value", val);
|
||||
}
|
||||
});
|
||||
|
||||
watch(show, async val => {
|
||||
if (val) {
|
||||
/** 自动聚焦 */
|
||||
await nextTick();
|
||||
inputRef.value?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
/** 将菜单树形结构扁平化为一维数组,用于菜单查询 */
|
||||
function flatTree(arr) {
|
||||
const res = [];
|
||||
function deep(arr) {
|
||||
arr.forEach(item => {
|
||||
res.push(item);
|
||||
item.children && deep(item.children);
|
||||
});
|
||||
}
|
||||
deep(arr);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** 查询 */
|
||||
function search() {
|
||||
const flatMenusData = flatTree(menusData.value);
|
||||
resultOptions.value = flatMenusData.filter(
|
||||
menu =>
|
||||
keyword.value &&
|
||||
transformI18n(menu.meta?.title, menu.meta?.i18n)
|
||||
.toLocaleLowerCase()
|
||||
.includes(keyword.value.toLocaleLowerCase().trim())
|
||||
);
|
||||
if (resultOptions.value?.length > 0) {
|
||||
activePath.value = resultOptions.value[0].path;
|
||||
} else {
|
||||
activePath.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
show.value = false;
|
||||
/** 延时处理防止用户看到某些操作 */
|
||||
setTimeout(() => {
|
||||
resultOptions.value = [];
|
||||
keyword.value = "";
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/** key up */
|
||||
function handleUp() {
|
||||
const { length } = resultOptions.value;
|
||||
if (length === 0) return;
|
||||
const index = resultOptions.value.findIndex(
|
||||
item => item.path === activePath.value
|
||||
);
|
||||
if (index === 0) {
|
||||
activePath.value = resultOptions.value[length - 1].path;
|
||||
} else {
|
||||
activePath.value = resultOptions.value[index - 1].path;
|
||||
}
|
||||
}
|
||||
|
||||
/** key down */
|
||||
function handleDown() {
|
||||
const { length } = resultOptions.value;
|
||||
if (length === 0) return;
|
||||
const index = resultOptions.value.findIndex(
|
||||
item => item.path === activePath.value
|
||||
);
|
||||
if (index + 1 === length) {
|
||||
activePath.value = resultOptions.value[0].path;
|
||||
} else {
|
||||
activePath.value = resultOptions.value[index + 1].path;
|
||||
}
|
||||
}
|
||||
|
||||
/** key enter */
|
||||
function handleEnter() {
|
||||
const { length } = resultOptions.value;
|
||||
if (length === 0 || activePath.value === "") return;
|
||||
router.push(activePath.value);
|
||||
handleClose();
|
||||
}
|
||||
|
||||
onKeyStroke("Enter", handleEnter);
|
||||
onKeyStroke("ArrowUp", handleUp);
|
||||
onKeyStroke("ArrowDown", handleDown);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog top="5vh" v-model="show" :before-close="handleClose">
|
||||
<el-input
|
||||
ref="inputRef"
|
||||
v-model="keyword"
|
||||
clearable
|
||||
placeholder="请输入关键词搜索"
|
||||
@input="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="el-input__icon">
|
||||
<IconifyIconOffline icon="search" />
|
||||
</span>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="search-result-container">
|
||||
<el-empty v-if="resultOptions.length === 0" description="暂无搜索结果" />
|
||||
<SearchResult
|
||||
v-else
|
||||
v-model:value="activePath"
|
||||
:options="resultOptions"
|
||||
@click="handleEnter"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<SearchFooter />
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.search-result-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
91
src/layout/components/search/components/SearchResult.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="result">
|
||||
<template v-for="item in options" :key="item.path">
|
||||
<div
|
||||
class="result-item"
|
||||
:style="{
|
||||
background:
|
||||
item?.path === active ? useEpThemeStoreHook().epThemeColor : '',
|
||||
color: item.path === active ? '#fff' : ''
|
||||
}"
|
||||
@click="handleTo"
|
||||
@mouseenter="handleMouse(item)"
|
||||
>
|
||||
<component :is="useRenderIcon(item.meta?.icon ?? 'bookmark-2-line')" />
|
||||
<span class="result-item-title">{{ t(item.meta?.title) }}</span>
|
||||
<enterOutlined />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
|
||||
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
|
||||
import enterOutlined from "/@/assets/svg/enter_outlined.svg?component";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
interface optionsItem {
|
||||
path: string;
|
||||
meta?: {
|
||||
icon?: string;
|
||||
title?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
options: Array<optionsItem>;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: "update:value", val: string): void;
|
||||
(e: "enter"): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const active = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(val: string) {
|
||||
emit("update:value", val);
|
||||
}
|
||||
});
|
||||
|
||||
/** 鼠标移入 */
|
||||
async function handleMouse(item) {
|
||||
active.value = item.path;
|
||||
}
|
||||
|
||||
function handleTo() {
|
||||
emit("enter");
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.result {
|
||||
padding-bottom: 12px;
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 56px;
|
||||
margin-top: 8px;
|
||||
padding: 14px;
|
||||
border-radius: 4px;
|
||||
background: #e5e7eb;
|
||||
cursor: pointer;
|
||||
|
||||
&-title {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
3
src/layout/components/search/components/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import SearchModal from "./SearchModal.vue";
|
||||
|
||||
export { SearchModal };
|
||||
30
src/layout/components/search/index.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="ts" setup>
|
||||
import { SearchModal } from "./components";
|
||||
import useBoolean from "../../hooks/useBoolean";
|
||||
const { bool: show, toggle } = useBoolean();
|
||||
function handleSearch() {
|
||||
toggle();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="search-container" @click="handleSearch">
|
||||
<IconifyIconOffline icon="search" />
|
||||
</div>
|
||||
<SearchModal v-model:value="show" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 48px;
|
||||
width: 40px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: #f6f6f6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
import rgbHex from "rgb-hex";
|
||||
import { find } from "lodash-es";
|
||||
import { find } from "lodash-unified";
|
||||
import { getConfig } from "/@/config";
|
||||
import { useRouter } from "vue-router";
|
||||
import panel from "../panel/index.vue";
|
||||
@@ -24,7 +24,7 @@ import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
|
||||
import { storageLocal, storageSession } from "/@/utils/storage";
|
||||
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
|
||||
import { createNewStyle, writeNewStyle } from "../../theme/element-plus";
|
||||
import { toggleTheme } from "@zougt/vite-plugin-theme-preprocessor/dist/browser-utils";
|
||||
import { toggleTheme } from "@pureadmin/theme/dist/browser-utils";
|
||||
|
||||
import dayIcon from "/@/assets/svg/day.svg?component";
|
||||
import darkIcon from "/@/assets/svg/dark.svg?component";
|
||||
@@ -111,7 +111,7 @@ function storageConfigureChange<T>(key: string, val: T): void {
|
||||
function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
|
||||
const targetEl = target || document.body;
|
||||
let { className } = targetEl;
|
||||
className = className.replace(clsName, "");
|
||||
className = className.replace(clsName, "").trim();
|
||||
targetEl.className = flag ? `${className} ${clsName} ` : className;
|
||||
}
|
||||
|
||||
@@ -316,8 +316,7 @@ nextTick(() => {
|
||||
:active-icon="dayIcon"
|
||||
:inactive-icon="darkIcon"
|
||||
@change="dataThemeChange"
|
||||
>
|
||||
</el-switch>
|
||||
/>
|
||||
|
||||
<el-divider>导航栏模式</el-divider>
|
||||
<ul class="pure-theme">
|
||||
@@ -327,8 +326,8 @@ nextTick(() => {
|
||||
ref="verticalRef"
|
||||
@click="setLayoutModel('vertical')"
|
||||
>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div />
|
||||
<div />
|
||||
</li>
|
||||
</el-tooltip>
|
||||
|
||||
@@ -338,8 +337,8 @@ nextTick(() => {
|
||||
ref="horizontalRef"
|
||||
@click="setLayoutModel('horizontal')"
|
||||
>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div />
|
||||
<div />
|
||||
</li>
|
||||
</el-tooltip>
|
||||
|
||||
@@ -349,8 +348,8 @@ nextTick(() => {
|
||||
ref="mixRef"
|
||||
@click="setLayoutModel('mix')"
|
||||
>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div />
|
||||
<div />
|
||||
</li>
|
||||
</el-tooltip>
|
||||
</ul>
|
||||
@@ -384,8 +383,7 @@ nextTick(() => {
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
@change="greyChange"
|
||||
>
|
||||
</el-switch>
|
||||
/>
|
||||
</li>
|
||||
<li v-show="!dataTheme">
|
||||
<span>色弱模式</span>
|
||||
@@ -396,8 +394,7 @@ nextTick(() => {
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
@change="weekChange"
|
||||
>
|
||||
</el-switch>
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<span>隐藏标签页</span>
|
||||
@@ -408,8 +405,7 @@ nextTick(() => {
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
@change="tagsChange"
|
||||
>
|
||||
</el-switch>
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<span>侧边栏Logo</span>
|
||||
@@ -422,8 +418,7 @@ nextTick(() => {
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
@change="logoChange"
|
||||
>
|
||||
</el-switch>
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<span>标签页持久化</span>
|
||||
@@ -434,8 +429,7 @@ nextTick(() => {
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
@change="multiTagsCacheChange"
|
||||
>
|
||||
</el-switch>
|
||||
/>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import { isEqual } from "lodash-es";
|
||||
import { isEqual } from "lodash-unified";
|
||||
import { transformI18n } from "/@/plugins/i18n";
|
||||
import { getParentPaths, findRouteByPath } from "/@/router/utils";
|
||||
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useNav } from "../../hooks/nav";
|
||||
import Search from "../search/index.vue";
|
||||
import Notice from "../notice/index.vue";
|
||||
import { templateRef } from "@vueuse/core";
|
||||
import SidebarItem from "./sidebarItem.vue";
|
||||
@@ -13,7 +14,7 @@ import { usePermissionStoreHook } from "/@/store/modules/permission";
|
||||
import globalization from "/@/assets/svg/globalization.svg?component";
|
||||
|
||||
const route = useRoute();
|
||||
const { locale } = useI18n();
|
||||
const { locale, t } = useI18n();
|
||||
const routers = useRouter().options.routes;
|
||||
const menuRef = templateRef<ElRef | null>("menu", null);
|
||||
const instance =
|
||||
@@ -45,6 +46,13 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
menuSelect(route.path, routers);
|
||||
}
|
||||
);
|
||||
|
||||
function translationCh() {
|
||||
instance.locale = { locale: "zh" };
|
||||
locale.value = "zh";
|
||||
@@ -61,11 +69,7 @@ function translationEn() {
|
||||
<template>
|
||||
<div class="horizontal-header">
|
||||
<div class="horizontal-header-left" @click="backHome">
|
||||
<FontIcon
|
||||
icon="team-iconlogo"
|
||||
svg
|
||||
style="width: 35px; height: 35px"
|
||||
></FontIcon>
|
||||
<FontIcon icon="team-iconlogo" svg style="width: 35px; height: 35px" />
|
||||
<h4>{{ title }}</h4>
|
||||
</div>
|
||||
<el-menu
|
||||
@@ -84,6 +88,8 @@ function translationEn() {
|
||||
/>
|
||||
</el-menu>
|
||||
<div class="horizontal-header-right">
|
||||
<!-- 菜单搜索 -->
|
||||
<Search />
|
||||
<!-- 通知 -->
|
||||
<Notice id="header-notice" />
|
||||
<!-- 全屏 -->
|
||||
@@ -96,15 +102,17 @@ function translationEn() {
|
||||
<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
|
||||
>
|
||||
<span class="check-zh" v-show="locale === 'zh'">
|
||||
<IconifyIconOffline icon="check" /> </span
|
||||
>简体中文
|
||||
</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
|
||||
>
|
||||
<span class="check-en" v-show="locale === 'en'">
|
||||
<IconifyIconOffline icon="check" /> </span
|
||||
>English</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
@@ -123,18 +131,18 @@ function translationEn() {
|
||||
icon="logout-circle-r-line"
|
||||
style="margin: 5px"
|
||||
/>
|
||||
{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
|
||||
{{ t("buttons.hsLoginOut") }}</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-icon
|
||||
<span
|
||||
class="el-icon-setting"
|
||||
:title="$t('buttons.hssystemSet')"
|
||||
:title="t('buttons.hssystemSet')"
|
||||
@click="onPanel"
|
||||
>
|
||||
<IconifyIconOffline icon="setting" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -18,11 +18,7 @@ const title =
|
||||
class="sidebar-logo-link"
|
||||
to="/"
|
||||
>
|
||||
<FontIcon
|
||||
icon="team-iconlogo"
|
||||
svg
|
||||
style="width: 35px; height: 35px"
|
||||
></FontIcon>
|
||||
<FontIcon icon="team-iconlogo" svg style="width: 35px; height: 35px" />
|
||||
<span class="sidebar-title">{{ title }}</span>
|
||||
</router-link>
|
||||
<router-link
|
||||
@@ -32,11 +28,7 @@ const title =
|
||||
class="sidebar-logo-link"
|
||||
to="/"
|
||||
>
|
||||
<FontIcon
|
||||
icon="team-iconlogo"
|
||||
svg
|
||||
style="width: 35px; height: 35px"
|
||||
></FontIcon>
|
||||
<FontIcon icon="team-iconlogo" svg style="width: 35px; height: 35px" />
|
||||
<span class="sidebar-title">{{ title }}</span>
|
||||
</router-link>
|
||||
</transition>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Search from "../search/index.vue";
|
||||
import Notice from "../notice/index.vue";
|
||||
import { useNav } from "../../hooks/nav";
|
||||
import { templateRef } from "@vueuse/core";
|
||||
@@ -16,7 +17,7 @@ import globalization from "/@/assets/svg/globalization.svg?component";
|
||||
import { ref, watch, nextTick, onMounted, getCurrentInstance } from "vue";
|
||||
|
||||
const route = useRoute();
|
||||
const { locale } = useI18n();
|
||||
const { locale, t } = useI18n();
|
||||
const routers = useRouter().options.routes;
|
||||
const menuRef = templateRef<ElRef | null>("menu", null);
|
||||
const instance =
|
||||
@@ -118,11 +119,9 @@ function translationEn() {
|
||||
: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>
|
||||
<div v-show="route.meta.icon" :class="['el-icon', route.meta.icon]">
|
||||
<component :is="useRenderIcon(route.meta && route.meta.icon)" />
|
||||
</div>
|
||||
<span>{{ transformI18n(route.meta.title, route.meta.i18n) }}</span>
|
||||
<FontIcon
|
||||
v-if="route.meta.extraIcon"
|
||||
@@ -131,11 +130,13 @@ function translationEn() {
|
||||
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">
|
||||
<!-- 菜单搜索 -->
|
||||
<Search />
|
||||
<!-- 通知 -->
|
||||
<Notice id="header-notice" />
|
||||
<!-- 全屏 -->
|
||||
@@ -148,15 +149,15 @@ function translationEn() {
|
||||
<el-dropdown-item
|
||||
:style="getDropdownItemStyle(locale, 'zh')"
|
||||
@click="translationCh"
|
||||
><el-icon class="check-zh" v-show="locale === 'zh'"
|
||||
><IconifyIconOffline icon="check" /></el-icon
|
||||
><span class="check-zh" v-show="locale === 'zh'"
|
||||
><IconifyIconOffline icon="check" /></span
|
||||
>简体中文</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
|
||||
><span class="check-en" v-show="locale === 'en'"
|
||||
><IconifyIconOffline icon="check" /></span
|
||||
>English</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
@@ -175,18 +176,18 @@ function translationEn() {
|
||||
icon="logout-circle-r-line"
|
||||
style="margin: 5px"
|
||||
/>
|
||||
{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
|
||||
{{ t("buttons.hsLoginOut") }}</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-icon
|
||||
<span
|
||||
class="el-icon-setting"
|
||||
:title="$t('buttons.hssystemSet')"
|
||||
:title="t('buttons.hssystemSet')"
|
||||
@click="onPanel"
|
||||
>
|
||||
<IconifyIconOffline icon="setting" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -111,6 +111,10 @@ function hasOneShowingChild(
|
||||
return true;
|
||||
});
|
||||
|
||||
if (showingChildren[0]?.meta?.showParent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (showingChildren.length === 1) {
|
||||
return true;
|
||||
}
|
||||
@@ -144,7 +148,7 @@ function resolvePath(routePath) {
|
||||
:class="{ 'submenu-title-noDropdown': !isNest }"
|
||||
:style="getNoDropdownStyle"
|
||||
>
|
||||
<el-icon v-show="props.item.meta.icon">
|
||||
<div class="el-icon" v-show="props.item.meta.icon">
|
||||
<component
|
||||
:is="
|
||||
useRenderIcon(
|
||||
@@ -152,8 +156,8 @@ function resolvePath(routePath) {
|
||||
(props.item.meta && props.item.meta.icon)
|
||||
)
|
||||
"
|
||||
></component>
|
||||
</el-icon>
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
!pureApp.sidebar.opened &&
|
||||
@@ -199,7 +203,7 @@ function resolvePath(routePath) {
|
||||
:style="getExtraIconStyle"
|
||||
:icon="onlyOneChild.meta.extraIcon.name"
|
||||
:svg="onlyOneChild.meta.extraIcon.svg ? true : false"
|
||||
></FontIcon>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
@@ -212,11 +216,14 @@ function resolvePath(routePath) {
|
||||
popper-append-to-body
|
||||
>
|
||||
<template #title>
|
||||
<el-icon v-show="props.item.meta.icon" :class="props.item.meta.icon">
|
||||
<div
|
||||
v-show="props.item.meta.icon"
|
||||
:class="['el-icon', props.item.meta.icon]"
|
||||
>
|
||||
<component
|
||||
:is="useRenderIcon(props.item.meta && props.item.meta.icon)"
|
||||
></component>
|
||||
</el-icon>
|
||||
/>
|
||||
</div>
|
||||
<span v-if="!menuMode">{{
|
||||
transformI18n(props.item.meta.title, props.item.meta.i18n)
|
||||
}}</span>
|
||||
@@ -246,7 +253,7 @@ function resolvePath(routePath) {
|
||||
style="position: absolute; right: 10px"
|
||||
:icon="props.item.meta.extraIcon.name"
|
||||
:svg="props.item.meta.extraIcon.svg ? true : false"
|
||||
></FontIcon>
|
||||
/>
|
||||
</template>
|
||||
<sidebar-item
|
||||
v-for="child in props.item.children"
|
||||
|
||||
@@ -49,7 +49,10 @@ onBeforeMount(() => {
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => getSubMenuData(route.path)
|
||||
() => {
|
||||
getSubMenuData(route.path);
|
||||
menuSelect(route.path, routers);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
|
||||
@@ -19,12 +19,12 @@ 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 { useI18n } from "vue-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";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { isEqual, isEmpty } from "lodash-unified";
|
||||
import { transformI18n, $t } from "/@/plugins/i18n";
|
||||
import { RouteConfigs, tagsViewsType } from "../../types";
|
||||
import { useSettingStoreHook } from "/@/store/modules/settings";
|
||||
import { handleAliveRoute, delAliveRoutes } from "/@/router/utils";
|
||||
@@ -33,6 +33,7 @@ import { usePermissionStoreHook } from "/@/store/modules/permission";
|
||||
import { toggleClass, removeClass, hasClass } from "/@/utils/operate";
|
||||
import { templateRef, useResizeObserver, useDebounceFn } from "@vueuse/core";
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const translateX = ref<number>(0);
|
||||
@@ -193,42 +194,42 @@ const handleScroll = (offset: number): void => {
|
||||
const tagsViews = reactive<Array<tagsViewsType>>([
|
||||
{
|
||||
icon: refresh,
|
||||
text: t("buttons.hsreload"),
|
||||
text: $t("buttons.hsreload"),
|
||||
divided: false,
|
||||
disabled: false,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
icon: close,
|
||||
text: t("buttons.hscloseCurrentTab"),
|
||||
text: $t("buttons.hscloseCurrentTab"),
|
||||
divided: false,
|
||||
disabled: multiTags.value.length > 1 ? false : true,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
icon: closeLeft,
|
||||
text: t("buttons.hscloseLeftTabs"),
|
||||
text: $t("buttons.hscloseLeftTabs"),
|
||||
divided: true,
|
||||
disabled: multiTags.value.length > 1 ? false : true,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
icon: closeRight,
|
||||
text: t("buttons.hscloseRightTabs"),
|
||||
text: $t("buttons.hscloseRightTabs"),
|
||||
divided: false,
|
||||
disabled: multiTags.value.length > 1 ? false : true,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
icon: closeOther,
|
||||
text: t("buttons.hscloseOtherTabs"),
|
||||
text: $t("buttons.hscloseOtherTabs"),
|
||||
divided: true,
|
||||
disabled: multiTags.value.length > 2 ? false : true,
|
||||
show: true
|
||||
},
|
||||
{
|
||||
icon: closeAll,
|
||||
text: t("buttons.hscloseAllTabs"),
|
||||
text: $t("buttons.hscloseAllTabs"),
|
||||
divided: false,
|
||||
disabled: multiTags.value.length > 1 ? false : true,
|
||||
show: true
|
||||
@@ -428,6 +429,7 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
|
||||
}
|
||||
|
||||
function handleCommand(command: object) {
|
||||
// @ts-expect-error
|
||||
const { key, item } = command;
|
||||
onClickDrop(key, item);
|
||||
}
|
||||
@@ -662,7 +664,7 @@ const getContextMenuStyle = computed((): CSSProperties => {
|
||||
<router-link :to="item.path"
|
||||
>{{ transformI18n(item.meta.title, item.meta.i18n) }}
|
||||
</router-link>
|
||||
<el-icon
|
||||
<span
|
||||
v-if="
|
||||
iconIsActive(item, index) ||
|
||||
(index === activeIndex && index !== 0)
|
||||
@@ -671,12 +673,12 @@ const getContextMenuStyle = computed((): CSSProperties => {
|
||||
@click.stop="deleteMenu(item)"
|
||||
>
|
||||
<IconifyIconOffline icon="close-bold" />
|
||||
</el-icon>
|
||||
</span>
|
||||
<div
|
||||
:ref="'schedule' + index"
|
||||
v-if="showModel !== 'card'"
|
||||
:class="[scheduleIsActive(item)]"
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -701,7 +703,7 @@ const getContextMenuStyle = computed((): CSSProperties => {
|
||||
>
|
||||
<li v-if="item.show" @click="selectTag(key, item)">
|
||||
<component :is="item.icon" :key="key" />
|
||||
{{ $t(item.text) }}
|
||||
{{ t(item.text) }}
|
||||
</li>
|
||||
</div>
|
||||
</ul>
|
||||
@@ -709,13 +711,13 @@ const getContextMenuStyle = computed((): CSSProperties => {
|
||||
<!-- 右侧功能按钮 -->
|
||||
<ul class="right-button">
|
||||
<li>
|
||||
<el-icon
|
||||
:title="$t('buttons.hsrefreshRoute')"
|
||||
<span
|
||||
:title="t('buttons.hsrefreshRoute')"
|
||||
class="el-icon-refresh-right rotate"
|
||||
@click="onFresh"
|
||||
>
|
||||
<IconifyIconOffline icon="refresh-right" />
|
||||
</el-icon>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<el-dropdown
|
||||
@@ -723,9 +725,7 @@ const getContextMenuStyle = computed((): CSSProperties => {
|
||||
placement="bottom-end"
|
||||
@command="handleCommand"
|
||||
>
|
||||
<el-icon>
|
||||
<IconifyIconOffline icon="arrow-down" />
|
||||
</el-icon>
|
||||
<IconifyIconOffline icon="arrow-down" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
@@ -740,14 +740,14 @@ const getContextMenuStyle = computed((): CSSProperties => {
|
||||
:key="key"
|
||||
style="margin-right: 6px"
|
||||
/>
|
||||
{{ $t(item.text) }}
|
||||
{{ t(item.text) }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</li>
|
||||
<li>
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
63
src/layout/frameView.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="frame" v-loading="loading">
|
||||
<iframe :src="frameSrc" class="frame-iframe" ref="frameRef" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useRoute } from "vue-router";
|
||||
import { ref, unref, onMounted, nextTick } from "vue";
|
||||
|
||||
const loading = ref(false);
|
||||
const currentRoute = useRoute();
|
||||
const frameSrc = ref<string>("");
|
||||
const frameRef = ref<HTMLElement | null>(null);
|
||||
|
||||
if (unref(currentRoute.meta)?.frameSrc) {
|
||||
frameSrc.value = unref(currentRoute.meta)?.frameSrc as string;
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function init() {
|
||||
nextTick(() => {
|
||||
const iframe = unref(frameRef);
|
||||
if (!iframe) return;
|
||||
const _frame = iframe as any;
|
||||
if (_frame.attachEvent) {
|
||||
_frame.attachEvent("onload", () => {
|
||||
hideLoading();
|
||||
});
|
||||
} else {
|
||||
iframe.onload = () => {
|
||||
hideLoading();
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = true;
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.frame {
|
||||
height: 100vh;
|
||||
z-index: 998;
|
||||
|
||||
.frame-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,12 @@
|
||||
import { computed } from "vue";
|
||||
import { router } from "/@/router";
|
||||
import { getConfig } from "/@/config";
|
||||
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 { remainingPaths } from "/@/router/modules/index";
|
||||
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
|
||||
|
||||
export function useNav() {
|
||||
@@ -29,6 +30,7 @@ export function useNav() {
|
||||
|
||||
// 动态title
|
||||
function changeTitle(meta: routeMetaType) {
|
||||
const Title = getConfig().Title;
|
||||
if (Title)
|
||||
document.title = `${transformI18n(meta.title, meta.i18n)} | ${Title}`;
|
||||
else document.title = transformI18n(meta.title, meta.i18n);
|
||||
@@ -67,6 +69,7 @@ export function useNav() {
|
||||
}
|
||||
|
||||
function menuSelect(indexPath: string, routers): void {
|
||||
if (isRemaining(indexPath)) return;
|
||||
let parentPath = "";
|
||||
const parentPathIndex = indexPath.lastIndexOf("/");
|
||||
if (parentPathIndex > 0) {
|
||||
@@ -93,6 +96,11 @@ export function useNav() {
|
||||
findCurrentRoute(indexPath, routers);
|
||||
}
|
||||
|
||||
// 判断路径是否参与菜单
|
||||
function isRemaining(path: string): boolean {
|
||||
return remainingPaths.includes(path);
|
||||
}
|
||||
|
||||
return {
|
||||
logout,
|
||||
backHome,
|
||||
|
||||
26
src/layout/hooks/useBoolean.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ref } from "vue";
|
||||
|
||||
export default function useBoolean(initValue = false) {
|
||||
const bool = ref(initValue);
|
||||
|
||||
function setBool(value: boolean) {
|
||||
bool.value = value;
|
||||
}
|
||||
function setTrue() {
|
||||
setBool(true);
|
||||
}
|
||||
function setFalse() {
|
||||
setBool(false);
|
||||
}
|
||||
function toggle() {
|
||||
setBool(!bool.value);
|
||||
}
|
||||
|
||||
return {
|
||||
bool,
|
||||
setBool,
|
||||
setTrue,
|
||||
setFalse,
|
||||
toggle
|
||||
};
|
||||
}
|
||||
@@ -16,5 +16,5 @@ replace({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div></div>
|
||||
<div />
|
||||
</template>
|
||||
@@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<router-view>
|
||||
<template #default="{ Component, route }">
|
||||
<transition appear name="fade-transform" mode="out-in">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</transition>
|
||||
</template>
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "layoutParentView"
|
||||
};
|
||||
</script>
|
||||
@@ -1,8 +1,8 @@
|
||||
/* 动态改变element-plus主题色 */
|
||||
import rgbHex from "rgb-hex";
|
||||
import { convert } from "css-color-function";
|
||||
import epCss from "./element.scss";
|
||||
import { TinyColor } from "@ctrl/tinycolor";
|
||||
import epCss from "element-plus/dist/index.css";
|
||||
import { convert } from "css-color-function";
|
||||
|
||||
// 色值表
|
||||
const formula = {
|
||||
|
||||
2
src/layout/theme/element.scss
Normal file
@@ -0,0 +1,2 @@
|
||||
/* 通过scss模块本地导入element-plus的全局样式文件,解决vite2.7.13版本后使用 import epCss from "element-plus/dist/index.css",打包后加载不到样式的问题 */
|
||||
@import "element-plus/dist/index.css";
|
||||
@@ -67,6 +67,7 @@ export type childrenType = {
|
||||
icon?: string;
|
||||
title?: string;
|
||||
i18n?: boolean;
|
||||
showParent?: boolean;
|
||||
extraIcon?: {
|
||||
svg?: boolean;
|
||||
name?: string;
|
||||
|
||||
14
src/main.ts
@@ -3,7 +3,7 @@ import router from "./router";
|
||||
import { setupStore } from "/@/store";
|
||||
import { getServerConfig } from "./config";
|
||||
import { createApp, Directive } from "vue";
|
||||
import { usI18n } from "../src/plugins/i18n";
|
||||
import { useI18n } from "../src/plugins/i18n";
|
||||
import { MotionPlugin } from "@vueuse/motion";
|
||||
import { useTable } from "../src/plugins/vxe-table";
|
||||
import { useElementPlus } from "../src/plugins/element-plus";
|
||||
@@ -13,6 +13,8 @@ import "animate.css";
|
||||
import "virtual:windi.css";
|
||||
// 导入公共样式
|
||||
import "./style/index.scss";
|
||||
import "@pureadmin/components/dist/index.css";
|
||||
import "@pureadmin/components/dist/theme.css";
|
||||
// 导入字体图标
|
||||
import "./assets/iconfont/iconfont.js";
|
||||
import "./assets/iconfont/iconfont.css";
|
||||
@@ -37,14 +39,10 @@ app.component("IconifyIconOnline", IconifyIconOnline);
|
||||
app.component("FontIcon", FontIcon);
|
||||
|
||||
getServerConfig(app).then(async config => {
|
||||
app.use(router);
|
||||
await router.isReady();
|
||||
injectResponsiveStorage(app, config);
|
||||
setupStore(app);
|
||||
app
|
||||
.use(router)
|
||||
.use(MotionPlugin)
|
||||
.use(useElementPlus)
|
||||
.use(useTable)
|
||||
.use(usI18n);
|
||||
await router.isReady();
|
||||
app.use(MotionPlugin).use(useI18n).use(useElementPlus).use(useTable);
|
||||
app.mount("#app");
|
||||
});
|
||||
|
||||
@@ -43,6 +43,18 @@ import {
|
||||
ElEmpty,
|
||||
ElCollapse,
|
||||
ElCollapseItem,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElLink,
|
||||
ElColorPicker,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElTimeline,
|
||||
ElTimelineItem,
|
||||
ElResult,
|
||||
ElSteps,
|
||||
ElStep,
|
||||
ElTree,
|
||||
ElTreeV2,
|
||||
// 指令
|
||||
ElLoading,
|
||||
@@ -96,7 +108,19 @@ const components = [
|
||||
ElEmpty,
|
||||
ElCollapse,
|
||||
ElCollapseItem,
|
||||
ElTreeV2
|
||||
ElTree,
|
||||
ElTreeV2,
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElLink,
|
||||
ElColorPicker,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElTimeline,
|
||||
ElTimelineItem,
|
||||
ElResult,
|
||||
ElSteps,
|
||||
ElStep
|
||||
];
|
||||
|
||||
export function useElementPlus(app: App) {
|
||||
|
||||
72
src/plugins/i18n.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
// 多组件库的国际化和本地项目国际化兼容
|
||||
import { App, WritableComputedRef } from "vue";
|
||||
import { storageLocal } from "/@/utils/storage";
|
||||
import { type I18n, createI18n } from "vue-i18n";
|
||||
|
||||
// element-plus国际化
|
||||
import enLocale from "element-plus/lib/locale/lang/en";
|
||||
import zhLocale from "element-plus/lib/locale/lang/zh-cn";
|
||||
|
||||
function siphonI18n(prefix = "zh-CN") {
|
||||
return Object.fromEntries(
|
||||
Object.entries(import.meta.globEager("../../locales/*.y(a)?ml")).map(
|
||||
([key, value]) => {
|
||||
const matched = key.match(/([A-Za-z0-9-_]+)\./i)[1];
|
||||
return [matched, value.default];
|
||||
}
|
||||
)
|
||||
)[prefix];
|
||||
}
|
||||
|
||||
export const localesConfigs = {
|
||||
zh: {
|
||||
...siphonI18n("zh-CN"),
|
||||
...zhLocale
|
||||
},
|
||||
en: {
|
||||
...siphonI18n("en"),
|
||||
...enLocale
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 国际化转换工具函数
|
||||
* @param message message
|
||||
* @param isI18n 如果true,获取对应的消息,否则返回本身
|
||||
* @returns message
|
||||
*/
|
||||
export function transformI18n(
|
||||
message: string | unknown | object = "",
|
||||
isI18n: boolean | unknown = false
|
||||
) {
|
||||
if (!message) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 处理存储动态路由的title,格式 {zh:"",en:""}
|
||||
if (typeof message === "object") {
|
||||
const locale: string | WritableComputedRef<string> | any =
|
||||
i18n.global.locale;
|
||||
return message[locale?.value];
|
||||
}
|
||||
|
||||
if (isI18n) {
|
||||
return i18n.global.t.call(i18n.global.locale, message);
|
||||
} else {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
// 此函数只是配合i18n Ally插件来进行国际化智能提示,并无实际意义(只对提示起作用),如果不需要国际化可删除
|
||||
export const $t = (key: string) => key;
|
||||
|
||||
export const i18n: I18n = createI18n({
|
||||
legacy: false,
|
||||
locale: storageLocal.getItem("responsive-locale")?.locale ?? "zh",
|
||||
fallbackLocale: "en",
|
||||
messages: localesConfigs
|
||||
});
|
||||
|
||||
export function useI18n(app: App) {
|
||||
app.use(i18n);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { siphonI18n } from "./index";
|
||||
// vxe-table组件国际化
|
||||
import zhVxeTable from "vxe-table/lib/locale/lang/zh-CN";
|
||||
import enVxeTable from "vxe-table/lib/locale/lang/en-US";
|
||||
|
||||
// element-plus国际化
|
||||
import enLocale from "element-plus/lib/locale/lang/en";
|
||||
import zhLocale from "element-plus/lib/locale/lang/zh-cn";
|
||||
|
||||
// 项目内自定义国际化
|
||||
const zhModules = import.meta.globEager("./zh-CN/**/*.ts");
|
||||
const enModules = import.meta.globEager("./en/**/*.ts");
|
||||
|
||||
export const localesConfigs = {
|
||||
zh: {
|
||||
...siphonI18n(zhModules, "zh-CN"),
|
||||
...zhVxeTable,
|
||||
...zhLocale
|
||||
},
|
||||
en: {
|
||||
...siphonI18n(enModules, "en"),
|
||||
...enVxeTable,
|
||||
...enLocale
|
||||
}
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
export default {
|
||||
hsLoginOut: "LoginOut",
|
||||
hsfullscreen: "FullScreen",
|
||||
hsexitfullscreen: "ExitFullscreen",
|
||||
hsrefreshRoute: "RefreshRoute",
|
||||
hslogin: "Login",
|
||||
hsadd: "Add",
|
||||
hsmark: "Mark/Cancel",
|
||||
hssave: "Save",
|
||||
hssearch: "Search",
|
||||
hsexpendAll: "Expand All",
|
||||
hscollapseAll: "Collapse All",
|
||||
hssystemSet: "Open ProjectConfig",
|
||||
hsdelete: "Delete",
|
||||
hsreload: "Reload",
|
||||
hscloseCurrentTab: "Close CurrentTab",
|
||||
hscloseLeftTabs: "Close LeftTabs",
|
||||
hscloseRightTabs: "Close RightTabs",
|
||||
hscloseOtherTabs: "Close OtherTabs",
|
||||
hscloseAllTabs: "Close AllTabs"
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
export default {
|
||||
hshome: "Home",
|
||||
hslogin: "Login",
|
||||
hssysManagement: "System Manage",
|
||||
hsBaseinfo: "Base Info",
|
||||
hsDict: "Dict Manage",
|
||||
hseditor: "Editor",
|
||||
hserror: "Error Page",
|
||||
hsfourZeroFour: "404",
|
||||
hsfourZeroOne: "401",
|
||||
hscomponents: "Components",
|
||||
hsvideo: "Video Components",
|
||||
hsmap: "Map Components",
|
||||
hsdraggable: "Draggable Components",
|
||||
hssplitPane: "Split Pane",
|
||||
hsbutton: "Button Components",
|
||||
hscropping: "Picture Cropping",
|
||||
hscountTo: "Digital Animation",
|
||||
hsselector: "Selector Components",
|
||||
hsflowChart: "Flow Chart",
|
||||
hsseamless: "Seamless Scroll",
|
||||
hscontextmenu: "Context Menu",
|
||||
hsmenus: "MultiLevel Menu",
|
||||
hsmenu1: "Menu1",
|
||||
"hsmenu1-1": "Menu1-1",
|
||||
"hsmenu1-2": "Menu1-2",
|
||||
"hsmenu1-2-1": "Menu1-2-1",
|
||||
"hsmenu1-2-2": "Menu1-2-2",
|
||||
"hsmenu1-3": "Menu1-3",
|
||||
hsmenu2: "Menu2",
|
||||
permission: "Permission Manage",
|
||||
permissionPage: "Page Permission",
|
||||
permissionButton: "Button Permission",
|
||||
hstabs: "Tabs Operate",
|
||||
hsMenuTree: "Menu Tree",
|
||||
hsguide: "Guide",
|
||||
externalLink: "External Link"
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
// 多组件库的国际化和本地项目国际化兼容
|
||||
import { App } from "vue";
|
||||
import { set } from "lodash-es";
|
||||
import { createI18n } from "vue-i18n";
|
||||
import { localesConfigs } from "./config";
|
||||
import { storageLocal } from "/@/utils/storage";
|
||||
|
||||
/**
|
||||
* 国际化转换工具函数
|
||||
* @param message message
|
||||
* @param isI18n 如果true,获取对应的消息,否则返回本身
|
||||
* @returns message
|
||||
*/
|
||||
export function transformI18n(
|
||||
message: string | unknown | object = "",
|
||||
isI18n: boolean | unknown = false
|
||||
) {
|
||||
if (!message) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 处理存储动态路由的title,格式 {zh:"",en:""}
|
||||
if (typeof message === "object") {
|
||||
return message[i18n.global?.locale];
|
||||
}
|
||||
|
||||
if (isI18n) {
|
||||
//@ts-ignore
|
||||
return i18n.global.tc.call(i18n.global, message);
|
||||
} else {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从模块中抽取国际化
|
||||
* @param langs 存放国际化模块
|
||||
* @param prefix 语言 默认 zh-CN
|
||||
* @returns obj 格式:{模块名.**}
|
||||
*/
|
||||
export function siphonI18n(
|
||||
langs: Record<string, Record<string, any>>,
|
||||
prefix = "zh-CN"
|
||||
) {
|
||||
const langsObj: Recordable = {};
|
||||
Object.keys(langs).forEach((key: string) => {
|
||||
let fileName = key.replace(`./${prefix}/`, "").replace(/^\.\//, "");
|
||||
fileName = fileName.substring(0, fileName.lastIndexOf("."));
|
||||
const keyList = fileName.split("/");
|
||||
const moduleName = keyList.shift();
|
||||
const objKey = keyList.join(".");
|
||||
const langFileModule = langs[key].default;
|
||||
|
||||
if (moduleName) {
|
||||
if (objKey) {
|
||||
set(langsObj, moduleName, langsObj[moduleName] || {});
|
||||
set(langsObj[moduleName], objKey, langFileModule);
|
||||
} else {
|
||||
set(langsObj, moduleName, langFileModule || {});
|
||||
}
|
||||
}
|
||||
});
|
||||
return langsObj;
|
||||
}
|
||||
|
||||
// 此函数只是配合i18n Ally插件来进行国际化智能提示,并无实际意义(只对提示起作用),如果不需要国际化可删除
|
||||
export const $t = (key: string) => key;
|
||||
|
||||
export const i18n = createI18n({
|
||||
locale: storageLocal.getItem("responsive-locale")?.locale ?? "zh",
|
||||
fallbackLocale: "en",
|
||||
messages: localesConfigs
|
||||
});
|
||||
|
||||
export function usI18n(app: App) {
|
||||
app.use(i18n);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
export default {
|
||||
hsLoginOut: "退出系统",
|
||||
hsfullscreen: "全屏",
|
||||
hsexitfullscreen: "退出全屏",
|
||||
hsrefreshRoute: "刷新路由",
|
||||
hslogin: "登陆",
|
||||
hsadd: "新增",
|
||||
hsmark: "标记/取消",
|
||||
hssave: "保存",
|
||||
hssearch: "搜索",
|
||||
hsexpendAll: "全部展开",
|
||||
hscollapseAll: "全部折叠",
|
||||
hssystemSet: "打开项目配置",
|
||||
hsdelete: "删除",
|
||||
hsreload: "重新加载",
|
||||
hscloseCurrentTab: "关闭当前标签页",
|
||||
hscloseLeftTabs: "关闭左侧标签页",
|
||||
hscloseRightTabs: "关闭右侧标签页",
|
||||
hscloseOtherTabs: "关闭其他标签页",
|
||||
hscloseAllTabs: "关闭全部标签页"
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
export default {
|
||||
hshome: "首页",
|
||||
hslogin: "登陆",
|
||||
hssysManagement: "系统管理",
|
||||
hsBaseinfo: "基础信息",
|
||||
hsDict: "字典管理",
|
||||
hseditor: "编辑器",
|
||||
hserror: "错误页面",
|
||||
hsfourZeroFour: "404",
|
||||
hsfourZeroOne: "401",
|
||||
hscomponents: "组件",
|
||||
hsvideo: "视频组件",
|
||||
hsmap: "地图组件",
|
||||
hsdraggable: "拖拽组件",
|
||||
hssplitPane: "切割面板",
|
||||
hsbutton: "按钮组件",
|
||||
hscropping: "图片裁剪",
|
||||
hscountTo: "数字动画",
|
||||
hsselector: "选择器组件",
|
||||
hsflowChart: "流程图",
|
||||
hsseamless: "无缝滚动",
|
||||
hscontextmenu: "右键菜单",
|
||||
hsmenus: "多级菜单",
|
||||
hsmenu1: "菜单1",
|
||||
"hsmenu1-1": "菜单1-1",
|
||||
"hsmenu1-2": "菜单1-2",
|
||||
"hsmenu1-2-1": "菜单1-2-1",
|
||||
"hsmenu1-2-2": "菜单1-2-2",
|
||||
"hsmenu1-3": "菜单1-3",
|
||||
hsmenu2: "菜单2",
|
||||
permission: "权限管理",
|
||||
permissionPage: "页面权限",
|
||||
permissionButton: "按钮权限",
|
||||
hstabs: "标签页操作",
|
||||
hsMenuTree: "菜单树结构",
|
||||
hsguide: "引导页",
|
||||
externalLink: "外链"
|
||||
};
|
||||
@@ -1,10 +1,14 @@
|
||||
import "xe-utils";
|
||||
import { App } from "vue";
|
||||
import { i18n } from "../i18n/index";
|
||||
import XEUtils from "xe-utils";
|
||||
import { App, unref } from "vue";
|
||||
import { i18n } from "/@/plugins/i18n";
|
||||
import "font-awesome/css/font-awesome.min.css";
|
||||
import zh from "vxe-table/lib/locale/lang/zh-CN";
|
||||
import en from "vxe-table/lib/locale/lang/en-US";
|
||||
|
||||
import {
|
||||
// 核心
|
||||
VXETable,
|
||||
|
||||
// 表格功能
|
||||
Header,
|
||||
Footer,
|
||||
@@ -15,7 +19,6 @@ import {
|
||||
Export,
|
||||
Keyboard,
|
||||
Validator,
|
||||
|
||||
// 可选组件
|
||||
Column,
|
||||
Colgroup,
|
||||
@@ -41,7 +44,6 @@ import {
|
||||
Modal,
|
||||
List,
|
||||
Pulldown,
|
||||
|
||||
// 表格
|
||||
Table
|
||||
} from "vxe-table";
|
||||
@@ -60,17 +62,15 @@ VXETable.setup({
|
||||
input: {
|
||||
clearable: true
|
||||
},
|
||||
// 对组件内置的提示语进行国际化翻译
|
||||
// @ts-ignore
|
||||
i18n: (key, args) => i18n.global.t(key, args),
|
||||
// 可选,对参数中的列头、校验提示..等进行自动翻译(只对支持国际化的有效)
|
||||
translate(key, args) {
|
||||
// 例如,只翻译 "buttons." 开头的键值
|
||||
if (key && key.indexOf("buttons.") > -1) {
|
||||
return i18n.global.t(key, args);
|
||||
}
|
||||
if (key && key.indexOf("el.") > -1) {
|
||||
return i18n.global.t(key, args);
|
||||
i18n: (key, args) => {
|
||||
return unref(i18n.global.locale) === "zh"
|
||||
? XEUtils.toFormatString(XEUtils.get(zh, key), args)
|
||||
: XEUtils.toFormatString(XEUtils.get(en, key), args);
|
||||
},
|
||||
translate(key) {
|
||||
const NAMESPACED = ["el.", "buttons."];
|
||||
if (key && NAMESPACED.findIndex(v => key.includes(v)) !== -1) {
|
||||
return i18n.global.t.call(i18n.global.locale, key);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
@@ -87,7 +87,6 @@ export function useTable(app: App) {
|
||||
.use(Export)
|
||||
.use(Keyboard)
|
||||
.use(Validator)
|
||||
|
||||
// 可选组件
|
||||
.use(Column)
|
||||
.use(Colgroup)
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { isUrl } from "/@/utils/is";
|
||||
import { getConfig } from "/@/config";
|
||||
import { toRouteType } from "./types";
|
||||
import { openLink } from "/@/utils/link";
|
||||
import NProgress from "/@/utils/progress";
|
||||
import { constantRoutes } from "./modules";
|
||||
import { split, findIndex } from "lodash-es";
|
||||
import { findIndex } from "lodash-unified";
|
||||
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";
|
||||
@@ -52,10 +53,11 @@ router.beforeEach((to: toRouteType, _from, next) => {
|
||||
}
|
||||
const name = storageSession.getItem("info");
|
||||
NProgress.start();
|
||||
const externalLink = to?.redirectedFrom?.fullPath;
|
||||
const externalLink = isUrl(to?.name);
|
||||
if (!externalLink)
|
||||
to.matched.some(item => {
|
||||
if (!item.meta.title) return "";
|
||||
const Title = getConfig().Title;
|
||||
if (Title)
|
||||
document.title = `${transformI18n(
|
||||
item.meta.title,
|
||||
@@ -65,9 +67,9 @@ router.beforeEach((to: toRouteType, _from, next) => {
|
||||
});
|
||||
if (name) {
|
||||
if (_from?.name) {
|
||||
// 如果路由包含http 则是超链接 反之是普通路由
|
||||
if (externalLink && externalLink.includes("http")) {
|
||||
openLink(`http${split(externalLink, "http")[1]}`);
|
||||
// name为超链接
|
||||
if (externalLink) {
|
||||
openLink(to?.name);
|
||||
NProgress.done();
|
||||
} else {
|
||||
next();
|
||||
|
||||
99
src/router/modules/able.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const ableRouter = {
|
||||
path: "/able",
|
||||
component: Layout,
|
||||
redirect: "/able/watermark",
|
||||
meta: {
|
||||
icon: "ubuntu-fill",
|
||||
title: $t("menus.hsAble"),
|
||||
i18n: true,
|
||||
rank: 3
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/able/watermark",
|
||||
name: "reWatermark",
|
||||
component: () => import("/@/views/able/watermark.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsWatermark"),
|
||||
i18n: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/able/print",
|
||||
name: "rePrint",
|
||||
component: () => import("/@/views/able/print.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsPrint"),
|
||||
i18n: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/able/iconSelect",
|
||||
name: "reIconSelect",
|
||||
component: () => import("/@/views/able/icon-select.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsIconSelect"),
|
||||
i18n: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/able/timeline",
|
||||
name: "reTimeline",
|
||||
component: () => import("/@/views/able/timeline.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsTimeline"),
|
||||
i18n: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/able/menuTree",
|
||||
name: "reMenuTree",
|
||||
component: () => import("/@/views/able/menu-tree.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsMenuTree"),
|
||||
i18n: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/able/lineTree",
|
||||
name: "reLineTree",
|
||||
component: () => import("/@/views/able/line-tree.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsLineTree"),
|
||||
i18n: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/able/antTabs",
|
||||
name: "reAntTabs",
|
||||
component: () => import("/@/views/able/ant-tabs.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsAntTabs"),
|
||||
i18n: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/able/antAnchor",
|
||||
name: "reAntAnchor",
|
||||
component: () => import("/@/views/able/ant-anchor.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsAntAnchor"),
|
||||
i18n: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/able/antTreeSelect",
|
||||
name: "reAntTreeSelect",
|
||||
component: () => import("/@/views/able/ant-treeSelect.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsAntTreeSelect"),
|
||||
i18n: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default ableRouter;
|
||||
27
src/router/modules/about.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const aboutRouter = {
|
||||
path: "/about",
|
||||
component: Layout,
|
||||
redirect: "/about/index",
|
||||
meta: {
|
||||
icon: "question-line",
|
||||
title: $t("menus.hsAbout"),
|
||||
i18n: true,
|
||||
rank: 14
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/about/index",
|
||||
name: "reAbout",
|
||||
component: () => import("/@/views/about/index.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsAbout"),
|
||||
i18n: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default aboutRouter;
|
||||
@@ -1,9 +1,8 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const componentsRouter = {
|
||||
path: "/components",
|
||||
name: "components",
|
||||
component: Layout,
|
||||
redirect: "/components/video",
|
||||
meta: {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const editorRouter = {
|
||||
path: "/editor",
|
||||
name: "reEditor",
|
||||
component: Layout,
|
||||
redirect: "/editor/index",
|
||||
meta: {
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const errorRouter = {
|
||||
path: "/error",
|
||||
name: "error",
|
||||
component: Layout,
|
||||
redirect: "/error/401",
|
||||
redirect: "/error/403",
|
||||
meta: {
|
||||
icon: "position",
|
||||
icon: "information-line",
|
||||
title: $t("menus.hserror"),
|
||||
i18n: true,
|
||||
rank: 7
|
||||
rank: 9
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/error/401",
|
||||
name: "401",
|
||||
component: () => import("/@/views/error/401.vue"),
|
||||
path: "/error/403",
|
||||
name: "403",
|
||||
component: () => import("/@/views/error/403.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsfourZeroOne"),
|
||||
i18n: true
|
||||
@@ -30,6 +29,15 @@ const errorRouter = {
|
||||
title: $t("menus.hsfourZeroFour"),
|
||||
i18n: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/error/500",
|
||||
name: "500",
|
||||
component: () => import("/@/views/error/500.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsFive"),
|
||||
i18n: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
|
||||
const externalLink = {
|
||||
path: "/external",
|
||||
name: "external",
|
||||
component: Layout,
|
||||
meta: {
|
||||
icon: "link",
|
||||
title: $t("menus.externalLink"),
|
||||
i18n: true,
|
||||
rank: 190
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "https://github.com/xiaoxian521/vue-pure-admin",
|
||||
meta: {
|
||||
title: $t("menus.externalLink"),
|
||||
i18n: true,
|
||||
rank: 191
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default externalLink;
|
||||
@@ -1,9 +1,8 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const flowChartRouter = {
|
||||
path: "/flowChart",
|
||||
name: "flowChart",
|
||||
component: Layout,
|
||||
redirect: "/flowChart/index",
|
||||
meta: {
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const guideRouter = {
|
||||
path: "/guide",
|
||||
name: "reGuide",
|
||||
component: Layout,
|
||||
redirect: "/guide/index",
|
||||
meta: {
|
||||
icon: "guide",
|
||||
title: $t("menus.hsguide"),
|
||||
i18n: true,
|
||||
rank: 10
|
||||
rank: 13
|
||||
},
|
||||
children: [
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const homeRouter = {
|
||||
path: "/",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// 静态路由
|
||||
import about from "./about";
|
||||
import homeRouter from "./home";
|
||||
import ableRouter from "./able";
|
||||
import errorRouter from "./error";
|
||||
import guideRouter from "./guide";
|
||||
import resultRouter from "./result";
|
||||
import editorRouter from "./editor";
|
||||
import nestedRouter from "./nested";
|
||||
import menuTreeRouter from "./menuTree";
|
||||
import externalLink from "./externalLink";
|
||||
import flowChartRouter from "./flowchart";
|
||||
import remainingRouter from "./remaining";
|
||||
import componentsRouter from "./components";
|
||||
@@ -20,13 +21,14 @@ import { buildHierarchyTree } from "/@/utils/tree";
|
||||
|
||||
// 原始静态路由(未做任何处理)
|
||||
const routes = [
|
||||
about,
|
||||
homeRouter,
|
||||
ableRouter,
|
||||
errorRouter,
|
||||
guideRouter,
|
||||
resultRouter,
|
||||
nestedRouter,
|
||||
externalLink,
|
||||
editorRouter,
|
||||
menuTreeRouter,
|
||||
flowChartRouter,
|
||||
componentsRouter
|
||||
];
|
||||
@@ -40,3 +42,8 @@ export const constantRoutes: Array<RouteRecordRaw> = formatTwoStageRoutes(
|
||||
export const constantMenus: Array<RouteComponent> = ascending(routes).concat(
|
||||
...remainingRouter
|
||||
);
|
||||
|
||||
// 不参与菜单的路由
|
||||
export const remainingPaths = Object.keys(remainingRouter).map(v => {
|
||||
return remainingRouter[v].path;
|
||||
});
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
|
||||
const menuTreeRouter = {
|
||||
path: "/menuTree",
|
||||
name: "reMenuTree",
|
||||
component: Layout,
|
||||
redirect: "/menuTree/index",
|
||||
meta: {
|
||||
icon: "node-tree",
|
||||
title: $t("menus.hsMenuTree"),
|
||||
i18n: true,
|
||||
rank: 9
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/menuTree/index",
|
||||
name: "reMenuTree",
|
||||
component: () => import("/@/views/menu-tree/index.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsMenuTree"),
|
||||
i18n: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default menuTreeRouter;
|
||||
@@ -1,22 +1,19 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const nestedRouter = {
|
||||
path: "/nested",
|
||||
component: Layout,
|
||||
redirect: "/nested/menu1/menu1-1",
|
||||
name: "Nested",
|
||||
meta: {
|
||||
title: $t("menus.hsmenus"),
|
||||
icon: "histogram",
|
||||
i18n: true,
|
||||
rank: 5
|
||||
rank: 11
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/nested/menu1",
|
||||
component: () => import("/@/layout/routerView/parent.vue"),
|
||||
name: "Menu1",
|
||||
meta: {
|
||||
title: $t("menus.hsmenu1"),
|
||||
i18n: true,
|
||||
@@ -36,8 +33,6 @@ const nestedRouter = {
|
||||
},
|
||||
{
|
||||
path: "/nested/menu1/menu1-2",
|
||||
component: () => import("/@/layout/routerView/parent.vue"),
|
||||
name: "Menu1-2",
|
||||
redirect: "/nested/menu1/menu1-2/menu1-2-1",
|
||||
meta: {
|
||||
title: $t("menus.hsmenu1-2"),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const remainingRouter = [
|
||||
{
|
||||
@@ -15,7 +15,6 @@ const remainingRouter = [
|
||||
},
|
||||
{
|
||||
path: "/redirect",
|
||||
name: "redirect",
|
||||
component: Layout,
|
||||
meta: {
|
||||
icon: "home-filled",
|
||||
@@ -28,7 +27,7 @@ const remainingRouter = [
|
||||
{
|
||||
path: "/redirect/:path(.*)",
|
||||
name: "redirect",
|
||||
component: () => import("/@/views/redirect.vue")
|
||||
component: () => import("/@/layout/redirect.vue")
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
36
src/router/modules/result.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { $t } from "/@/plugins/i18n";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
|
||||
const resultRouter = {
|
||||
path: "/result",
|
||||
component: Layout,
|
||||
redirect: "/result/success",
|
||||
meta: {
|
||||
icon: "checkbox-circle-line",
|
||||
title: $t("menus.hsResult"),
|
||||
i18n: true,
|
||||
rank: 8
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/result/success",
|
||||
name: "reSuccess",
|
||||
component: () => import("/@/views/result/success.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsSuccess"),
|
||||
i18n: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/result/fail",
|
||||
name: "reFail",
|
||||
component: () => import("/@/views/result/fail.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsFail"),
|
||||
i18n: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default resultRouter;
|
||||
@@ -8,10 +8,12 @@ import {
|
||||
} from "vue-router";
|
||||
import { router } from "./index";
|
||||
import { loadEnv } from "../../build";
|
||||
import Layout from "/@/layout/index.vue";
|
||||
import { useTimeoutFn } from "@vueuse/core";
|
||||
import { RouteConfigs } from "/@/layout/types";
|
||||
import { buildHierarchyTree } from "/@/utils/tree";
|
||||
import { usePermissionStoreHook } from "/@/store/modules/permission";
|
||||
const Layout = () => import("/@/layout/index.vue");
|
||||
const IFrame = () => import("/@/layout/frameView.vue");
|
||||
// https://cn.vitejs.dev/guide/features.html#glob-import
|
||||
const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}");
|
||||
|
||||
@@ -20,6 +22,14 @@ import { getAsyncRoutes } from "/@/api/routes";
|
||||
|
||||
// 按照路由中meta下的rank等级升序来排序路由
|
||||
function ascending(arr: any[]) {
|
||||
arr.forEach(v => {
|
||||
if (v?.meta?.rank === null) v.meta.rank = undefined;
|
||||
if (v?.meta?.rank === 0) {
|
||||
if (v.name !== "home" && v.path !== "/") {
|
||||
console.warn("rank only the home page can be 0");
|
||||
}
|
||||
}
|
||||
});
|
||||
return arr.sort(
|
||||
(a: { meta: { rank: number } }, b: { meta: { rank: number } }) => {
|
||||
return a?.meta?.rank - b?.meta?.rank;
|
||||
@@ -125,6 +135,10 @@ function initRouter(name: string) {
|
||||
// 最终路由进行升序
|
||||
ascending(router.options.routes[0].children);
|
||||
if (!router.hasRoute(v?.name)) router.addRoute(v);
|
||||
const flattenRouters = router
|
||||
.getRoutes()
|
||||
.find(n => n.path === "/");
|
||||
router.addRoute(flattenRouters);
|
||||
}
|
||||
resolve(router);
|
||||
}
|
||||
@@ -146,14 +160,15 @@ function initRouter(name: string) {
|
||||
*/
|
||||
function formatFlatteningRoutes(routesList: RouteRecordRaw[]) {
|
||||
if (routesList.length === 0) return routesList;
|
||||
for (let i = 0; i < routesList.length; i++) {
|
||||
if (routesList[i].children) {
|
||||
routesList = routesList
|
||||
let hierarchyList = buildHierarchyTree(routesList);
|
||||
for (let i = 0; i < hierarchyList.length; i++) {
|
||||
if (hierarchyList[i].children) {
|
||||
hierarchyList = hierarchyList
|
||||
.slice(0, i + 1)
|
||||
.concat(routesList[i].children, routesList.slice(i + 1));
|
||||
.concat(hierarchyList[i].children, hierarchyList.slice(i + 1));
|
||||
}
|
||||
}
|
||||
return routesList;
|
||||
return hierarchyList;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,6 +231,8 @@ function addAsyncRoutes(arrRoutes: Array<RouteRecordRaw>) {
|
||||
arrRoutes.forEach((v: RouteRecordRaw) => {
|
||||
if (v.redirect) {
|
||||
v.component = Layout;
|
||||
} else if (v.meta?.frameSrc) {
|
||||
v.component = IFrame;
|
||||
} else {
|
||||
const index = modulesRoutesKeys.findIndex(ev => ev.includes(v.path));
|
||||
v.component = modulesRoutes[modulesRoutesKeys[index]];
|
||||
|
||||