Compare commits

...

83 Commits

Author SHA1 Message Date
xiaoxian521
a06de54113 release: update 3.2.0 2022-03-22 00:25:55 +08:00
xiaoxian521
5b94930463 style: menu min-width 2022-03-22 00:10:47 +08:00
啝裳
5885706988 perf: menu (#220)
perf: menu
2022-03-21 23:15:41 +08:00
xiaoxian521
997711b264 feat: add vue/html-self-closing eslint 2022-03-21 15:40:29 +08:00
xiaoxian521
7beb3e63fe perf: route rank is null 2022-03-17 19:53:10 +08:00
xiaoxian521
ae57e42612 perf: router rank 2022-03-17 19:00:25 +08:00
xiaoxian521
99140bbd1e fix: router 2022-03-17 18:12:42 +08:00
xiaoxian521
89f4817cfe chore: update pnpm.lock 2022-03-17 17:55:27 +08:00
xiaoxian521
9802a0c51b feat: add @pureadmin/theme plugin 2022-03-17 15:52:01 +08:00
xiaoxian521
d2d324e3f0 chore: update 2022-03-17 12:23:15 +08:00
xiaoxian521
bee2315566 fix: supprimer les commentaires redondants 2022-03-14 21:39:26 +08:00
xiaoxian521
6d38b7a6aa fix: use import local scss resolve vite build bug 2022-03-14 19:12:30 +08:00
xiaoxian521
0f6e4ab4e0 perf: use getConfig replace import 2022-03-14 18:31:51 +08:00
xiaoxian521
ee1a6ffeb6 chore: update @pureadmin/components 2022-03-14 14:52:17 +08:00
xiaoxian521
b36850f79f perf: redirect 2022-03-14 12:43:02 +08:00
xiaoxian521
bef0d9234e fix: i18n 2022-03-14 12:35:25 +08:00
xiaoxian521
0bba4b7d64 fix: vxe i18n 2022-03-13 15:49:13 +08:00
xiaoxian521
a7576f6971 fix: vxe-table i18n 2022-03-13 14:01:51 +08:00
一万
51d08e4b82 fix: menu search (#214)
* fix: menu search

* fix: i18

* perf: delete route name
2022-03-12 23:32:32 +08:00
啝裳
494ce8f41b refactor: i18n
* refactor: i18n
2022-03-11 21:28:43 +08:00
xiaoxian521
8b3f642cf2 chore: update @pureadmin/components 2022-03-10 10:51:45 +08:00
xiaoxian521
2e5667f652 feat: add anchor、tabs、treeSelect Components 2022-03-10 00:36:11 +08:00
xiaoxian521
8d539d4c21 feat: add @pureadmin/components 2022-03-09 22:54:28 +08:00
一万
c9026a45cc feat: menu search (#209)
* feat: menu search

* style(layout): 剔除windcss依赖
2022-03-09 13:54:16 +08:00
xiaoxian521
81c4184cc4 docs: update 2022-03-07 22:06:13 +08:00
xiaoxian521
244f657be2 docs: update 2022-03-07 20:43:27 +08:00
xiaoxian521
74a6b3ffdc feat: add tree line 2022-03-07 20:21:15 +08:00
xiaoxian521
8e7a79cf94 feat: add result page 2022-03-06 12:33:41 +08:00
xiaoxian521
849b46533d perf: timeline 2022-03-05 22:48:46 +08:00
xiaoxian521
7f5aeed4d1 feat: add timeline 2022-03-05 20:53:36 +08:00
xiaoxian521
c749149c2f perf: icon 2022-03-05 14:09:29 +08:00
xiaoxian521
0a7d22248f feat: add iconSelect component 2022-03-05 13:47:24 +08:00
xiaoxian521
a35dc9d7b6 docs: update 2022-03-04 17:56:33 +08:00
xiaoxian521
a6e819e169 perf: error page 2022-03-04 11:10:02 +08:00
xiaoxian521
3701161022 release: update 3.1.0 2022-03-03 22:57:11 +08:00
xiaoxian521
e3898df731 fix: tags 2022-03-03 22:50:39 +08:00
xiaoxian521
32172220f7 fix: initRouter 2022-03-03 22:40:53 +08:00
xiaoxian521
fcdfe6d0c0 perf: menu 2022-03-03 09:27:53 +08:00
xiaoxian521
67bc933d5c fix: router is initialized first 2022-03-03 09:18:30 +08:00
xiaoxian521
feb1532549 fix: selector Component 2022-03-02 21:21:47 +08:00
xiaoxian521
1722ee7a9b perf: merge 2022-03-02 20:48:46 +08:00
xiaoxian521
710e119397 feat: add about page 2022-03-02 20:47:14 +08:00
Line
f21d9f38e5 fix: PureHttpResoponse => PureHttpResponse (#207)
Co-authored-by: yang.li12 <yang.li12@bluefocus.com>
2022-03-02 18:15:21 +08:00
xiaoxian521
727c0fe3c0 feat: print and perf style 2022-03-02 13:56:07 +08:00
一万
e3fda52801 feat: add iframe router 2022-03-02 11:15:51 +08:00
xiaoxian521
37762e45fd chore: update vite latest 2022-03-01 11:24:39 +08:00
啝裳
d43316f7c9 feat: watermark (#203)
* feat: add watermark
2022-03-01 10:44:26 +08:00
一万
6971ba6c53 fix: 跳转路由新增标签页 (#199)
* fix: 跳转路由新增标签页

* fix: 跳转路由新增标签页兼容

* perf: 删除取消watch监听
2022-02-28 22:08:56 +08:00
hexiaobang
a175cf9fe0 fix: 解决toggleClass函数多次运行后html元素的class中会遗留大量无效空格的问题 (#198) 2022-02-27 11:23:45 +08:00
xiaoxian521
9927e6f217 fix: router 2022-02-27 11:17:45 +08:00
xiaoxian521
bc300eab18 feat: 添加运行、打包信息, 使用lodash-unified替换lodash-es, lodash-unified支持ESM 2022-02-27 02:44:29 +08:00
xiaoxian521
3ca4729421 fix: delete routerView 2022-02-23 15:13:36 +08:00
烂柯人
dfaa76cc29 docs: update (#192) 2022-02-18 16:12:40 +08:00
xiaoxian521
7103b04283 fix: axios type 2022-02-18 14:54:16 +08:00
xiaoxian521
d2ddb49314 fix: axios post and get type 2022-02-18 14:51:58 +08:00
xiaoxian521
a4a042bfd7 fix: el-button plain model theme bug 2022-02-18 11:45:18 +08:00
xiaoxian521
a9a8115d46 perf: useRenderIcon hooks 2022-02-18 10:53:03 +08:00
huohuoit
3676014eb6 fix: 解决页面横向滚动条不显示问题 (#190)
Co-authored-by: Qingxuan001 <534794892@qqcom>
2022-02-17 18:13:09 +08:00
xiaoxian521
52910602ff fix: vxe-table icon not show 2022-02-16 11:23:20 +08:00
xiaoxian521
a2d1cf0e5f docs: update 2022-02-15 15:22:48 +08:00
xiaoxian521
8e886e83e7 ci: update 2022-02-15 15:05:58 +08:00
xiaoxian521
3e8dfb2bcb release: update 3.0 2022-02-14 23:19:32 +08:00
xiaoxian521
6fb0f7ef4d fix: mixNav route 2022-02-14 23:12:04 +08:00
xiaoxian521
08983fdbc1 fix: 固定vite@2.7.13,2.8+打包有问题 2022-02-14 22:51:36 +08:00
啝裳
ef05b2b614 feat: add mix nav (#187)
feat: add mix nav
2022-02-14 22:09:39 +08:00
xiaoxian521
a2dde02994 docs: update 2022-02-10 13:13:38 +08:00
rich1e
7544cf058e fix: epThemeColor error
Closes #183
2022-02-10 12:54:16 +08:00
xiaoxian521
43193fd2b6 docs: update 2022-02-10 12:01:36 +08:00
xiaoxian521
25c55c5d1d chore: update eslint@8.8.0 2022-02-07 15:59:04 +08:00
xiaoxian521
217258b972 chore: update element-plus@2.0.0 2022-02-07 13:43:54 +08:00
xiaoxian521
dbe0815ac4 feat: add vxe-table/lib/locale/lang optimizeDeps 2022-02-05 17:36:21 +08:00
xiaoxian521
fd4cad8d4c release: update 2.9.0 2022-02-05 17:34:01 +08:00
xiaoxian521
e33bdb52f3 fix: vite@2.7.0-beta.8 build incompatible template 2022-01-21 18:25:29 +08:00
xiaoxian521
46a48a5650 release: update 2.8.5 2022-01-21 16:46:48 +08:00
xiaoxian521
8fa3448af2 chore: update typescript 2022-01-21 16:33:45 +08:00
xiaoxian521
f236829b0f refactor: use @iconify-icons/ep 2022-01-21 16:10:10 +08:00
xiaoxian521
47dea87275 fix: update lintstagedrc 2022-01-18 17:06:15 +08:00
xiaoxian521
5e9198c2e0 fix: vscode setting 2022-01-18 17:05:42 +08:00
xiaoxian521
c9c8f20cc5 docs: update 2022-01-09 18:44:23 +08:00
xiaoxian521
a19c97f152 fix: add vite-plugin-live-reload 2022-01-09 12:11:50 +08:00
xiaoxian521
5e1e9d3c5f feat: 添加线上环境删console插件vite-plugin-remove-console 2022-01-07 17:57:55 +08:00
xiaoxian521
96153f8a14 feat: 添加WindiCSS支持 2022-01-07 17:20:25 +08:00
xiaoxian521
14adf692ab perf: theme 2022-01-07 14:45:21 +08:00
194 changed files with 9700 additions and 4308 deletions

View File

@@ -37,7 +37,7 @@ module.exports = {
"eslint:recommended", "eslint:recommended",
"@vue/typescript/recommended", "@vue/typescript/recommended",
"@vue/prettier", "@vue/prettier",
"@vue/prettier/@typescript-eslint" "@vue/eslint-config-typescript"
], ],
parser: "vue-eslint-parser", parser: "vue-eslint-parser",
parserOptions: { parserOptions: {
@@ -50,6 +50,10 @@ module.exports = {
} }
}, },
rules: { rules: {
"vue/no-v-html": "off",
"vue/require-default-prop": "off",
"vue/require-explicit-emits": "off",
"vue/multi-word-component-names": "off",
"@typescript-eslint/no-explicit-any": "off", // any "@typescript-eslint/no-explicit-any": "off", // any
"no-debugger": "off", "no-debugger": "off",
"@typescript-eslint/explicit-module-boundary-types": "off", // setup() "@typescript-eslint/explicit-module-boundary-types": "off", // setup()
@@ -57,6 +61,18 @@ module.exports = {
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-non-null-assertion": "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": [ "@typescript-eslint/no-unused-vars": [
"error", "error",
{ {

34
.github/workflows/preview.yml vendored Normal file
View 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

View File

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

View File

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

View File

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

29
.vscode/settings.json vendored
View File

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

View File

@@ -1,3 +1,70 @@
# 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
- Added mix navigation
### 🐞 Bug fixes
- Fix tab page bug
# 2.9.0 (2022-2-5)
### 🎫 Feat
- Added package size analysis, command `pnpm report`
### 🍏 Perf
- Use `iconify` to introduce icons on demand, optimize icon size, and reduce network requests
- Optimize the route, the route can not pass `showLink: true`, it is displayed by default
# 2.8.5 (2022-1-21)
### 🎫 Feat
- Added `WindiCSS` support
- Add online environment remove console plugin `vite-plugin-remove-console`
### ✔️ refactor
- Replace `@element-plus/icons-vue` with `@iconify-icons/ep`
# 2.8.0(2022-1-4) # 2.8.0(2022-1-4)
### 🎫 Feat ### 🎫 Feat

View File

@@ -1,3 +1,70 @@
# 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
- Added mix navigation
### 🐞 Bug fixes
- Fix tab page bug
# 2.9.0 (2022-2-5)
### 🎫 Feat
- Added package size analysis, command `pnpm report`
### 🍏 Perf
- Use `iconify` to introduce icons on demand, optimize icon size, and reduce network requests
- Optimize the route, the route can not pass `showLink: true`, it is displayed by default
# 2.8.5 (2022-1-21)
### 🎫 Feat
- Added `WindiCSS` support
- Add online environment remove console plugin `vite-plugin-remove-console`
### ✔️ refactor
- Replace `@element-plus/icons-vue` with `@iconify-icons/ep`
# 2.8.0(2022-1-4) # 2.8.0(2022-1-4)
### 🎫 Feat ### 🎫 Feat

View File

@@ -1,3 +1,70 @@
# 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
- 添加混合导航
### 🐞 Bug fixes
- 修复标签页 bug
# 2.9.0(2022-2-5)
### 🎫 Feat
- 添加打包大小分析,命令`pnpm report`
### 🍏 Perf
- 采用`iconify`按需引入图标,优化图标大小,减少网络请求
- 优化路由,路由可不传`showLink: true`,默认显示
# 2.8.5(2022-1-21)
### 🎫 Feat
- 添加 `WindiCSS` 支持
- 添加线上环境删 console 插件`vite-plugin-remove-console`
### ✔️ refactor
- 使用`@iconify-icons/ep`替换`@element-plus/icons-vue`
# 2.8.0(2022-1-4) # 2.8.0(2022-1-4)
### 🎫 Feat ### 🎫 Feat

View File

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

View File

@@ -1,6 +1,8 @@
<h1>vue-pure-admin</h1> <h1>vue-pure-admin</h1>
[![license](https://img.shields.io/github/license/xiaoxian521/vue-pure-admin.svg)](LICENSE) ![GitHub license](https://img.shields.io/github/license/xiaoxian521/vue-pure-admin?style=flat)
![GitHub stars](https://img.shields.io/github/stars/xiaoxian521/vue-pure-admin?color=fa6470&style=flat)
![GitHub forks](https://img.shields.io/github/forks/xiaoxian521/vue-pure-admin?style=flat)
**English** | [中文](./README.md) **English** | [中文](./README.md)
@@ -10,20 +12,20 @@ vue-pure-admin is a free and open source middle and back-end template. Using the
## Supporting Video ## Supporting Video
Tutorial: <https://www.bilibili.com/video/BV1534y1S7HV/> - [Click Watch Tutorial](https://www.bilibili.com/video/BV1534y1S7HV)
UI Design: <https://www.bilibili.com/video/BV17g411T7rq/> - [Click Watch UI Design](https://www.bilibili.com/video/BV17g411T7rq)
## Docs ## Docs
<https://pure-admin-doc.vercel.app/> - [Click Watch Docs](https://pure-admin-doc.vercel.app)
## Thin ## Thin
Github Address: <https://github.com/xiaoxian521/pure-admin-thin> - [Click Watch Thin](https://github.com/xiaoxian521/pure-admin-thin)
## Preview ## Preview
- [vue-pure-admin](http://yiming_chang.gitee.io/manages) - [vue-pure-admin](https://vue-pure-admin.vercel.app)
<p align="center"> <p align="center">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4857fc7eb7d4c0f8deeefc644c1f7dd~tplv-k3u1fbpfcp-watermark.awebp?"> <img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4857fc7eb7d4c0f8deeefc644c1f7dd~tplv-k3u1fbpfcp-watermark.awebp?">
@@ -44,7 +46,7 @@ Open the project in Gitpod (free online dev environment for GitHub) and start co
```bash ```bash
git clone https://github.com/xiaoxian521/vue-pure-admin.git git clone https://github.com/xiaoxian521/vue-pure-admin.git
or 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 - Installation dependencies
@@ -118,32 +120,26 @@ Support modern browsers, not IE
## Donate ## 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" />
## WeChat Exchange Group
For the better development of the project, you can choose to donate 10 yuan and add the following WeChat to pull you into the group. After adding, please consciously send a screenshot of the donation
<img src="http://yiming_chang.gitee.io/manages/kf.jpg" width="150px" height="195px" />
## License ## License
In principle, no fees and copyrights are charged, so you can use it with confidence In principle, no fees and copyrights are charged, and you can use it with confidence, but if you need secondary open source, please contact the author for permission!
[MIT © xiaoxian521-2020](./LICENSE) [MIT © xiaoxian521-2020](./LICENSE)
## Backers ## Backers
Thank you very much for your support, I believe the project will get better and better! ! ! :heart: Thank you very much for your support, I believe the project will get better and better :heart:
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen | | 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/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 ## Contributors
This project exists thanks to all the people who contribute!!! :heart: This project exists thanks to all the people who contribute :heart:
<a href="https://github.com/xiaoxian521/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=xiaoxian521/vue-pure-admin" /></a> <a href="https://github.com/xiaoxian521/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=xiaoxian521/vue-pure-admin" /></a>

View File

@@ -1,6 +1,8 @@
<h1>vue-pure-admin</h1> <h1>vue-pure-admin</h1>
[![license](https://img.shields.io/github/license/xiaoxian521/vue-pure-admin.svg)](LICENSE) ![GitHub license](https://img.shields.io/github/license/xiaoxian521/vue-pure-admin?style=flat)
![GitHub stars](https://img.shields.io/github/stars/xiaoxian521/vue-pure-admin?color=fa6470&style=flat)
![GitHub forks](https://img.shields.io/github/forks/xiaoxian521/vue-pure-admin?style=flat)
**中文** | [English](./README.en-US.md) **中文** | [English](./README.en-US.md)
@@ -10,20 +12,20 @@ vue-pure-admin 是一个免费开源的中后台模版。使用了最新的`vue3
## 配套视频 ## 配套视频
教程:<https://www.bilibili.com/video/BV1534y1S7HV/> - [点我查看教程](https://www.bilibili.com/video/BV1534y1S7HV)
UI 设计:<https://www.bilibili.com/video/BV17g411T7rq/> - [点我查看 UI 设计](https://www.bilibili.com/video/BV17g411T7rq)
## 配套文档 ## 配套文档
<https://pure-admin-doc.vercel.app/> - [点我查看文档](https://pure-admin-doc.vercel.app)
## 精简版 ## 精简版
仓库地址:<https://github.com/xiaoxian521/pure-admin-thin> - [点我查看精简版](https://github.com/xiaoxian521/pure-admin-thin)
## 预览 ## 预览
- [vue-pure-admin](http://yiming_chang.gitee.io/manages) - [vue-pure-admin](https://vue-pure-admin.vercel.app)
<p align="center"> <p align="center">
<img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4857fc7eb7d4c0f8deeefc644c1f7dd~tplv-k3u1fbpfcp-watermark.awebp?"> <img alt="PureAdmin Logo" width="100%" src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4857fc7eb7d4c0f8deeefc644c1f7dd~tplv-k3u1fbpfcp-watermark.awebp?">
@@ -44,7 +46,7 @@ UI 设计:<https://www.bilibili.com/video/BV17g411T7rq/>
```bash ```bash
git clone https://github.com/xiaoxian521/vue-pure-admin.git git clone https://github.com/xiaoxian521/vue-pure-admin.git
or or
git clone https://github.com.cnpmjs.org/xiaoxian521/vue-pure-admin.git git clone https://gitee.com/yiming_chang/vue-pure-admin.git
``` ```
- 安装依赖 - 安装依赖
@@ -118,32 +120,32 @@ 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 交流群
为了项目更好的发展,你可选择捐赠 10 元后添加下图微信拉你进群,添加后请自觉发捐赠截图 群里严禁`黄``赌``毒``vpn`等违法行为!
<img src="http://yiming_chang.gitee.io/manages/kf.jpg" width="150px" height="195px" /> <img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f0697596aec84661b724f6eebdf8db17~tplv-k3u1fbpfcp-watermark.awebp?" width="150px" height="225px" />
## 许可证 ## 许可证
原则上不收取任何费用及版权,可以放心使用 原则上不收取任何费用及版权,可以放心使用,不过如需二次开源(比如用此平台二次开发并开源)请联系作者获取许可!
[MIT © xiaoxian521-2020](./LICENSE) [MIT © xiaoxian521-2020](./LICENSE)
## 捐赠者 ## 捐赠者
非常感谢你们的支持,相信项目会越来越好:heart: 非常感谢你们的支持,相信项目会越来越好 :heart:
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen | | 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/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> |
## 贡献者 ## 贡献者
这个项目的存在感谢所有做出贡献的人:heart: 这个项目的存在感谢所有做出贡献的人 :heart:
<a href="https://github.com/xiaoxian521/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=xiaoxian521/vue-pure-admin" /></a> <a href="https://github.com/xiaoxian521/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=xiaoxian521/vue-pure-admin" /></a>

View File

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

85
build/info.ts Normal file
View 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)
)}`
)
)
);
});
}
}
};
}

131
build/plugins.ts Normal file
View File

@@ -0,0 +1,131 @@
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";
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 "@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/**/*"]),
// 自定义主题
themePreprocessorPlugin({
scss: {
multipleScopeVars: [
{
scopeName: "layout-theme-default",
path: "src/layout/theme/default-vars.scss"
},
{
scopeName: "layout-theme-light",
path: "src/layout/theme/light-vars.scss"
},
{
scopeName: "layout-theme-dusk",
path: "src/layout/theme/dusk-vars.scss"
},
{
scopeName: "layout-theme-volcano",
path: "src/layout/theme/volcano-vars.scss"
},
{
scopeName: "layout-theme-yellow",
path: "src/layout/theme/yellow-vars.scss"
},
{
scopeName: "layout-theme-mingQing",
path: "src/layout/theme/mingQing-vars.scss"
},
{
scopeName: "layout-theme-auroraGreen",
path: "src/layout/theme/auroraGreen-vars.scss"
},
{
scopeName: "layout-theme-pink",
path: "src/layout/theme/pink-vars.scss"
},
{
scopeName: "layout-theme-saucePurple",
path: "src/layout/theme/saucePurple-vars.scss"
}
],
// 默认取 multipleScopeVars[0].scopeName
defaultScopeName: "",
// 在生产模式是否抽取独立的主题css文件extract为true以下属性有效
extract: true,
// 独立主题css文件的输出路径默认取 viteConfig.build.assetsDir 相对于 (viteConfig.build.outDir)
outputDir: "",
// 会选取defaultScopeName对应的主题css文件在html添加link
themeLinkTagId: "head",
// "head"||"head-prepend" || "body" ||"body-prepend"
themeLinkTagInjectTo: "head",
// 是否对抽取的css文件内对应scopeName的权重类名移除
removeCssScopeName: false,
// 可以自定义css文件名称的函数
customThemeCssFileName: scopeName => scopeName
}
}),
// svg组件化支持
svgLoader(),
// 按需加载vxe-table
styleImport({
libs: [
{
libraryName: "vxe-table",
esModule: true,
ensureStyleFile: true,
resolveComponent: name => `vxe-table/es/${name}`,
resolveStyle: name => `vxe-table/es/${name}/style.css`
}
]
}),
ElementPlus({}),
// mock支持
viteMockServe({
mockPath: "mock",
localEnabled: command === "serve",
prodEnabled: command !== "serve" && prodMock,
injectCode: `
import { setupProdMockServer } from './mockProdServer';
setupProdMockServer();
`,
logger: true
}),
// 是否为打包后的文件提供传统浏览器兼容性支持
VITE_LEGACY
? legacy({
targets: ["ie >= 11"],
additionalLegacyPolyfills: ["regenerator-runtime/runtime"]
})
: null,
// 打包分析
lifecycle === "report"
? visualizer({ open: true, brotliSize: true, filename: "report.html" })
: null
];
}

30
deploy.sh Normal file
View 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

View File

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

74
locales/en.yaml Normal file
View 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
View 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树型选择器

View File

@@ -4,13 +4,11 @@ import { MockMethod } from "vite-plugin-mock";
// http://mockjs.com/examples.html#Object // http://mockjs.com/examples.html#Object
const systemRouter = { const systemRouter = {
path: "/system", path: "/system",
name: "system",
redirect: "/system/user/index", redirect: "/system/user/index",
meta: { meta: {
icon: "Setting", icon: "setting",
title: "menus.hssysManagement", title: "menus.hssysManagement",
i18n: true, i18n: true,
showLink: true,
rank: 6 rank: 6
}, },
children: [ children: [
@@ -19,8 +17,7 @@ const systemRouter = {
name: "user", name: "user",
meta: { meta: {
title: "menus.hsBaseinfo", title: "menus.hsBaseinfo",
i18n: true, i18n: true
showLink: true
} }
}, },
{ {
@@ -29,7 +26,6 @@ const systemRouter = {
meta: { meta: {
title: "menus.hsDict", title: "menus.hsDict",
i18n: true, i18n: true,
showLink: true,
keepAlive: true keepAlive: true
} }
} }
@@ -38,14 +34,12 @@ const systemRouter = {
const permissionRouter = { const permissionRouter = {
path: "/permission", path: "/permission",
name: "permission",
redirect: "/permission/page/index", redirect: "/permission/page/index",
meta: { meta: {
title: "menus.permission", title: "menus.permission",
icon: "Lollipop", icon: "lollipop",
i18n: true, i18n: true,
showLink: true, rank: 7
rank: 3
}, },
children: [ children: [
{ {
@@ -53,8 +47,7 @@ const permissionRouter = {
name: "permissionPage", name: "permissionPage",
meta: { meta: {
title: "menus.permissionPage", title: "menus.permissionPage",
i18n: true, i18n: true
showLink: true
} }
}, },
{ {
@@ -63,23 +56,59 @@ const permissionRouter = {
meta: { meta: {
title: "menus.permissionButton", title: "menus.permissionButton",
i18n: true, i18n: true,
showLink: true,
authority: [] authority: []
} }
} }
] ]
}; };
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 = { const tabsRouter = {
path: "/tabs", path: "/tabs",
name: "reTabs",
redirect: "/tabs/index", redirect: "/tabs/index",
meta: { meta: {
icon: "IF-team-icontabs", icon: "IF-team-icontabs",
title: "menus.hstabs", title: "menus.hstabs",
i18n: true, i18n: true,
showLink: true, rank: 12
rank: 8
}, },
children: [ children: [
{ {
@@ -87,7 +116,6 @@ const tabsRouter = {
name: "reTabs", name: "reTabs",
meta: { meta: {
title: "menus.hstabs", title: "menus.hstabs",
showLink: true,
i18n: true i18n: true
} }
}, },
@@ -121,6 +149,7 @@ export default [
code: 0, code: 0,
info: [ info: [
tabsRouter, tabsRouter,
frameRouter,
systemRouter, systemRouter,
setDifAuthority("v-admin", permissionRouter) setDifAuthority("v-admin", permissionRouter)
] ]

View File

@@ -1,15 +1,13 @@
{ {
"name": "vue-pure-admin", "name": "vue-pure-admin",
"version": "2.8.0", "version": "3.2.0",
"private": true, "private": true,
"engines": {
"node": ">= 16",
"pnpm": ">= 6"
},
"scripts": { "scripts": {
"dev": "cross-env --max_old_space_size=4096 vite", "dev": "cross-env --max_old_space_size=4096 vite",
"serve": "pnpm dev", "serve": "pnpm dev",
"build": "rimraf dist && cross-env vite build", "build": "rimraf dist && cross-env vite build",
"report": "rimraf dist && cross-env vite build",
"deploy": "bash deploy.sh",
"preview": "vite preview", "preview": "vite preview",
"preview:build": "pnpm build && vite preview", "preview:build": "pnpm build && vite preview",
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install", "clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install",
@@ -18,7 +16,7 @@
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,css,scss,postcss,less}\" --cache --cache-location node_modules/.cache/stylelint/", "lint:stylelint": "stylelint --cache --fix \"**/*.{vue,css,scss,postcss,less}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js", "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
"lint:pretty": "pretty-quick --staged", "lint:pretty": "pretty-quick --staged",
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint && pnpm lint:pretty", "lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
"prepare": "husky install", "prepare": "husky install",
"preinstall": "npx only-allow pnpm" "preinstall": "npx only-allow pnpm"
}, },
@@ -30,92 +28,107 @@
"dependencies": { "dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1", "@amap/amap-jsapi-loader": "^1.0.1",
"@ctrl/tinycolor": "^3.4.0", "@ctrl/tinycolor": "^3.4.0",
"@element-plus/icons-vue": "^0.2.4",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^3.0.0-5",
"@logicflow/core": "0.7.1", "@logicflow/core": "0.7.1",
"@logicflow/extension": "0.7.1", "@logicflow/extension": "0.7.1",
"@vueuse/core": "^6.7.1", "@pureadmin/components": "^1.0.2",
"@vueuse/motion": "^2.0.0-beta.4", "@vueuse/core": "^8.0.0",
"@vueuse/motion": "^2.0.0-beta.9",
"@vueuse/shared": "^8.0.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^0.21.1", "axios": "^0.26.1",
"cropperjs": "^1.5.11", "cropperjs": "^1.5.12",
"css-color-function": "^1.3.3", "css-color-function": "^1.3.3",
"dayjs": "^1.10.7", "dayjs": "^1.11.0",
"driver.js": "^0.9.8", "driver.js": "^0.9.8",
"echarts": "^5.2.1", "echarts": "^5.3.0",
"element-plus": "1.3.0-beta.1", "element-plus": "^2.1.4",
"element-resize-detector": "^1.2.3", "element-resize-detector": "^1.2.3",
"font-awesome": "^4.7.0",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lodash-unified": "^1.0.2",
"mitt": "^3.0.0", "mitt": "^3.0.0",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path": "^0.12.7", "path": "^0.12.7",
"pinia": "^2.0.0-rc.14", "pinia": "^2.0.12",
"qs": "^6.10.1", "qs": "^6.10.1",
"remixicon": "^2.5.0",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"responsive-storage": "^1.0.11", "responsive-storage": "^1.0.11",
"rgb-hex": "^4.0.0", "rgb-hex": "^4.0.0",
"v-contextmenu": "3.0.0", "v-contextmenu": "3.0.0",
"vue": "^3.2.24", "vue": "^3.2.31",
"vue-i18n": "^9.2.0-beta.3", "vue-i18n": "^9.2.0-beta.32",
"vue-json-pretty": "^2.0.2", "vue-json-pretty": "^2.0.2",
"vue-router": "^4.0.12", "vue-router": "^4.0.14",
"vue-types": "^4.1.0", "vue-types": "^4.1.1",
"vuedraggable": "4.1.0", "vuedraggable": "4.1.0",
"vxe-table": "^4.1.18", "vxe-table": "^4.2.0",
"wangeditor": "^4.7.9", "wangeditor": "^4.7.12",
"xe-utils": "^3.5.2", "xe-utils": "^3.5.4",
"xgplayer": "2.28.0" "xgplayer": "^2.31.4"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "13.1.0", "@commitlint/cli": "13.1.0",
"@commitlint/config-conventional": "13.1.0", "@commitlint/config-conventional": "13.1.0",
"@iconify-icons/ep": "^1.1.3",
"@iconify-icons/fa": "^1.1.1",
"@iconify-icons/fa-solid": "^1.1.2",
"@iconify-icons/ri": "^1.1.1",
"@iconify/vue": "^3.1.4",
"@intlify/vite-plugin-vue-i18n": "^3.3.1",
"@pureadmin/theme": "^0.0.1",
"@types/element-resize-detector": "1.1.3", "@types/element-resize-detector": "1.1.3",
"@types/js-cookie": "^3.0.1", "@types/js-cookie": "^3.0.1",
"@types/lodash": "^4.14.180",
"@types/lodash-es": "^4.17.6",
"@types/mockjs": "1.0.3", "@types/mockjs": "1.0.3",
"@types/node": "14.14.14", "@types/node": "14.14.14",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@typescript-eslint/eslint-plugin": "4.31.0", "@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "4.31.0", "@typescript-eslint/parser": "^5.10.2",
"@vitejs/plugin-legacy": "^1.6.4", "@vitejs/plugin-legacy": "^1.7.1",
"@vitejs/plugin-vue": "^1.10.2", "@vitejs/plugin-vue": "^2.2.4",
"@vitejs/plugin-vue-jsx": "^1.3.1", "@vitejs/plugin-vue-jsx": "^1.3.8",
"@vue/compiler-sfc": "^3.2.24", "@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-prettier": "6.0.0", "@vue/eslint-config-typescript": "^10.0.0",
"@vue/eslint-config-typescript": "7.0.0", "autoprefixer": "^10.4.2",
"@zougt/vite-plugin-theme-preprocessor": "^1.4.0",
"autoprefixer": "10.2.4",
"babel-plugin-transform-remove-console": "6.9.4",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "7.30.0", "eslint": "^8.8.0",
"eslint-plugin-prettier": "3.4.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "7.17.0", "eslint-plugin-vue": "^8.4.1",
"husky": "7.0.2", "font-awesome": "^4.7.0",
"husky": "^7.0.4",
"lint-staged": "11.1.2", "lint-staged": "11.1.2",
"postcss": "8.2.6", "picocolors": "^1.0.0",
"postcss": "^8.4.6",
"postcss-html": "^1.3.0",
"postcss-import": "14.0.0", "postcss-import": "14.0.0",
"prettier": "2.3.2", "postcss-scss": "^4.0.3",
"prettier": "^2.5.1",
"pretty-quick": "3.1.1", "pretty-quick": "3.1.1",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"sass": "^1.45.0", "rollup": "^2.70.1",
"sass-loader": "^12.3.0", "rollup-plugin-visualizer": "^5.6.0",
"stylelint": "13.13.1", "sass": "^1.49.9",
"stylelint-config-prettier": "8.0.2", "stylelint": "^14.3.0",
"stylelint-config-standard": "22.0.0", "stylelint-config-html": "^1.0.0",
"stylelint-order": "4.1.0", "stylelint-config-prettier": "^9.0.3",
"typescript": "4.4.2", "stylelint-config-recommended": "^6.0.0",
"unplugin-element-plus": "^0.1.3", "stylelint-config-standard": "^24.0.0",
"vite": "2.6.14", "stylelint-order": "^5.0.0",
"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-mock": "^2.9.6",
"vite-plugin-style-import": "^1.2.1", "vite-plugin-remove-console": "^0.0.6",
"vite-svg-loader": "^2.2.0", "vite-plugin-style-import": "1.4.1",
"vue-eslint-parser": "7.10.0" "vite-plugin-windicss": "^1.8.3",
"vite-svg-loader": "2.2.0",
"vue-eslint-parser": "^8.2.0",
"windicss": "^3.5.1"
}, },
"repository": "git@github.com:xiaoxian521/vue-pure-admin.git", "repository": "git@github.com:xiaoxian521/vue-pure-admin.git",
"author": "xiaoxian521", "author": "xiaoxian521",

3837
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,5 +1,5 @@
{ {
"Version": "2.8.0", "Version": "3.2.0",
"Title": "PureAdmin", "Title": "PureAdmin",
"FixedHeader": true, "FixedHeader": true,
"HiddenSideBar": false, "HiddenSideBar": false,

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

View 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

View 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

View File

@@ -92,5 +92,5 @@ tryOnUnmounted(() => {
</script> </script>
<template> <template>
<div :class="'bar' + props.index" style="width: 100%; height: 35vh"></div> <div :class="'bar' + props.index" style="width: 100%; height: 35vh" />
</template> </template>

View File

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

View File

@@ -78,9 +78,9 @@ let classOption = reactive({
> >
<ul class="item"> <ul class="item">
<li v-for="(item, index) in listData" :key="index"> <li v-for="(item, index) in listData" :key="index">
<span v-text="item.date"></span> <span v-text="item.date" />
<span v-text="item.name"></span> <span v-text="item.name" />
<span v-text="item.star"></span> <span v-text="item.star" />
</li> </li>
</ul> </ul>
</SeamlessScroll> </SeamlessScroll>

View File

@@ -80,5 +80,5 @@ tryOnUnmounted(() => {
</script> </script>
<template> <template>
<div :class="'line' + props.index" style="width: 100%; height: 35vh"></div> <div :class="'line' + props.index" style="width: 100%; height: 35vh" />
</template> </template>

View File

@@ -83,5 +83,5 @@ tryOnUnmounted(() => {
</script> </script>
<template> <template>
<div :class="'pie' + props.index" style="width: 100%; height: 35vh"></div> <div :class="'pie' + props.index" style="width: 100%; height: 35vh" />
</template> </template>

View File

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

View File

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

View File

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

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

View 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: () => []
}
);
}
});
}

View File

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

View File

@@ -107,7 +107,7 @@ onMounted(() => {
}" }"
@click="onControl(item, key)" @click="onControl(item, key)"
> >
<span :class="'iconfont ' + item.icon"></span> <span :class="'iconfont ' + item.icon" />
<p>{{ item.text }}</p> <p>{{ item.text }}</p>
</button> </button>
</li> </li>
@@ -126,7 +126,7 @@ onMounted(() => {
} }
.iconfont { .iconfont {
font-size: 25px; font-size: 18px;
} }
.control-container p { .control-container p {

View File

@@ -13,5 +13,5 @@ const props = defineProps({
:deep="3" :deep="3"
:showLength="true" :showLength="true"
:data="props.graphData" :data="props.graphData"
></vue-json-pretty> />
</template> </template>

View File

@@ -44,7 +44,7 @@ const nodeDragNode = item => {
<div <div
v-if="item.type === 'user' || item.type === 'time'" v-if="item.type === 'user' || item.type === 'time'"
class="shape" class="shape"
></div> />
</div> </div>
<span class="node-label">{{ item.text }}</span> <span class="node-label">{{ item.text }}</span>
</div> </div>

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -0,0 +1,47 @@
import { iconType } from "./types";
import { h, defineComponent, Component } from "vue";
import { IconifyIconOffline, FontIcon } from "../index";
/**
* 支持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
if (ifReg.test(icon)) {
// iconfont
const name = icon.split(ifReg)[1];
const iconName = name.slice(
0,
name.indexOf(" ") == -1 ? name.length : name.indexOf(" ")
);
const iconType = name.slice(name.indexOf(" ") + 1, name.length);
return defineComponent({
name: "FontIcon",
render() {
return h(FontIcon, {
icon: iconName,
iconType,
...attrs
});
}
});
} else if (typeof icon === "function") {
// svg
return icon;
} else {
return defineComponent({
name: "Icon",
render() {
return h(IconifyIconOffline, {
icon: icon,
...attrs
});
}
});
}
}

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -19,10 +19,6 @@ type resultType = {
info: Array<undefined>; info: Array<undefined>;
}; };
export interface mapInter {
loading: boolean;
}
let MarkerCluster; let MarkerCluster;
let map: MapConfigureInter; let map: MapConfigureInter;
@@ -126,7 +122,7 @@ onUnmounted(() => {
</script> </script>
<template> <template>
<div id="mapview" ref="mapview" v-loading="mapSet.loading"></div> <div id="mapview" ref="mapview" v-loading="mapSet.loading" />
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -498,7 +498,7 @@ defineExpose({
:class="leftSwitchClass" :class="leftSwitchClass"
@click="leftSwitchClick" @click="leftSwitchClick"
> >
<slot name="left-switch"></slot> <slot name="left-switch" />
</div> </div>
<div <div
:style="rightSwitch" :style="rightSwitch"
@@ -506,7 +506,7 @@ defineExpose({
:class="rightSwitchClass" :class="rightSwitchClass"
@click="rightSwitchClick" @click="rightSwitchClick"
> >
<slot name="right-switch"></slot> <slot name="right-switch" />
</div> </div>
<div <div
:ref="'realBox' + classOption['key']" :ref="'realBox' + classOption['key']"
@@ -519,9 +519,9 @@ defineExpose({
@mousewheel="wheel" @mousewheel="wheel"
> >
<div :ref="'slotList' + classOption['key']" :style="float"> <div :ref="'slotList' + classOption['key']" :style="float">
<slot></slot> <slot />
</div> </div>
<div v-html="copyHtml" :style="float"></div> <div v-html="copyHtml" :style="float" />
</div> </div>
</div> </div>
</template> </template>

View File

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

View File

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

View File

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

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

View 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"
}
})
])
);
}
});

View File

@@ -7,8 +7,7 @@ import {
defineComponent, defineComponent,
getCurrentInstance getCurrentInstance
} from "vue"; } from "vue";
import { RouterView } from "vue-router"; import backTop from "/@/assets/svg/back_top.svg?component";
import backTop from "/@/assets/svg/back_top.svg";
import { usePermissionStoreHook } from "/@/store/modules/permission"; import { usePermissionStoreHook } from "/@/store/modules/permission";
const props = defineProps({ const props = defineProps({

View File

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

View File

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

View File

@@ -1,8 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import NoticeList from "./noticeList.vue";
import { noticesData } from "./data"; 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 activeName = ref(noticesData[0].name);
const notices = ref(noticesData); const notices = ref(noticesData);
@@ -10,36 +13,51 @@ let noticesNum = ref(0);
notices.value.forEach(notice => { notices.value.forEach(notice => {
noticesNum.value += notice.list.length; noticesNum.value += notice.list.length;
}); });
function tabClick() {
// @ts-expect-error
dropdownDom.value.handleOpen();
}
</script> </script>
<template> <template>
<el-dropdown trigger="click" placement="bottom-end"> <el-dropdown ref="dropdownDom" trigger="click" placement="bottom-end">
<span class="dropdown-badge"> <span class="dropdown-badge">
<el-badge :value="noticesNum" :max="99"> <el-badge :value="noticesNum" :max="99">
<el-icon class="header-notice-icon"><bell /></el-icon> <span class="header-notice-icon">
<IconifyIconOffline icon="bell" />
</span>
</el-badge> </el-badge>
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <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"> <template v-for="item in notices" :key="item.key">
<el-tab-pane <TabPane :tab="`${item.name}(${item.list.length})`">
:label="`${item.name}(${item.list.length})`"
:name="item.name"
>
<el-scrollbar max-height="330px"> <el-scrollbar max-height="330px">
<div class="noticeList-container"> <div class="noticeList-container">
<NoticeList :list="item.list" /> <NoticeList :list="item.list" />
</div> </div>
</el-scrollbar> </el-scrollbar>
</el-tab-pane> </TabPane>
</template> </template>
</el-tabs> </Tabs>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
</template> </template>
<style>
.ant-tabs-dropdown {
z-index: 2900 !important;
}
</style>
<style lang="scss" scoped> <style lang="scss" scoped>
.dropdown-badge { .dropdown-badge {
display: flex; display: flex;
@@ -77,4 +95,8 @@ notices.value.forEach(notice => {
padding: 15px 24px 0 24px; padding: 15px 24px 0 24px;
} }
} }
:deep(.ant-tabs-nav) {
margin-bottom: 0;
}
</style> </style>

View File

@@ -10,8 +10,8 @@ const props = defineProps({
}); });
const titleRef = ref(null); const titleRef = ref(null);
const descriptionRef = ref(null);
const titleTooltip = ref(false); const titleTooltip = ref(false);
const descriptionRef = ref(null);
const descriptionTooltip = ref(false); const descriptionTooltip = ref(false);
function hoverTitle() { function hoverTitle() {
@@ -50,7 +50,7 @@ function hoverDescription(event, description) {
:size="30" :size="30"
:src="props.noticeItem.avatar" :src="props.noticeItem.avatar"
class="notice-container-avatar" class="notice-container-avatar"
></el-avatar> />
<div class="notice-container-text"> <div class="notice-container-text">
<div class="notice-text-title"> <div class="notice-text-title">
<el-tooltip <el-tooltip

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { PropType } from "vue"; import { PropType } from "vue";
import NoticeItem from "./noticeItem.vue";
import { ListItem } from "./data"; import { ListItem } from "./data";
import NoticeItem from "./noticeItem.vue";
const props = defineProps({ const props = defineProps({
list: { list: {
@@ -17,7 +17,7 @@ const props = defineProps({
v-for="(item, index) in props.list" v-for="(item, index) in props.list"
:noticeItem="item" :noticeItem="item"
:key="index" :key="index"
></NoticeItem> />
</div> </div>
<el-empty v-else description="暂无数据"></el-empty> <el-empty v-else description="暂无数据" />
</template> </template>

View File

@@ -23,10 +23,10 @@ emitter.on("openPanel", () => {
<div class="project-configuration"> <div class="project-configuration">
<h3>项目配置</h3> <h3>项目配置</h3>
<el-icon title="关闭配置" class="el-icon-close" @click="show = !show"> <el-icon title="关闭配置" class="el-icon-close" @click="show = !show">
<Close /> <IconifyIconOffline icon="close" />
</el-icon> </el-icon>
</div> </div>
<div style="border-bottom: 1px solid #dcdfe6"></div> <div style="border-bottom: 1px solid #dcdfe6" />
<slot /> <slot />
</div> </div>
</div> </div>
@@ -125,7 +125,7 @@ emitter.on("openPanel", () => {
&:hover { &:hover {
cursor: pointer; cursor: pointer;
color: red; color: var(--el-color-primary);
} }
} }
} }

View File

@@ -1,22 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { useFullscreen } from "@vueuse/core"; import { useFullscreen } from "@vueuse/core";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const { isFullscreen, toggle } = useFullscreen(); const { isFullscreen, toggle } = useFullscreen();
</script> </script>
<template> <template>
<div class="screen-full" @click="toggle"> <div class="screen-full" @click="toggle">
<i <FontIcon
:title=" :title="
isFullscreen isFullscreen ? t('buttons.hsexitfullscreen') : t('buttons.hsfullscreen')
? $t('buttons.hsexitfullscreen')
: $t('buttons.hsfullscreen')
" "
:class=" :icon="isFullscreen ? 'team-iconexit-fullscreen' : 'team-iconfullscreen'"
isFullscreen />
? 'iconfont team-iconexit-fullscreen'
: 'iconfont team-iconfullscreen'
"
></i>
</div> </div>
</template> </template>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,3 @@
import SearchModal from "./SearchModal.vue";
export { SearchModal };

View 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>

View File

@@ -10,15 +10,13 @@ import {
getCurrentInstance getCurrentInstance
} from "vue"; } from "vue";
import rgbHex from "rgb-hex"; import rgbHex from "rgb-hex";
import { find } from "lodash-es"; import { find } from "lodash-unified";
import { getConfig } from "/@/config"; import { getConfig } from "/@/config";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import panel from "../panel/index.vue"; import panel from "../panel/index.vue";
import { emitter } from "/@/utils/mitt"; import { emitter } from "/@/utils/mitt";
import { templateRef } from "@vueuse/core"; import { templateRef } from "@vueuse/core";
import dayIcon from "/@/assets/svg/day.svg";
import { debounce } from "/@/utils/debounce"; import { debounce } from "/@/utils/debounce";
import darkIcon from "/@/assets/svg/dark.svg";
import { themeColorsType } from "../../types"; import { themeColorsType } from "../../types";
import { useAppStoreHook } from "/@/store/modules/app"; import { useAppStoreHook } from "/@/store/modules/app";
import { shadeBgColor } from "../../theme/element-plus"; import { shadeBgColor } from "../../theme/element-plus";
@@ -26,7 +24,10 @@ import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
import { storageLocal, storageSession } from "/@/utils/storage"; import { storageLocal, storageSession } from "/@/utils/storage";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
import { createNewStyle, writeNewStyle } from "../../theme/element-plus"; 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";
const router = useRouter(); const router = useRouter();
const { isSelect } = useCssModule(); const { isSelect } = useCssModule();
@@ -60,6 +61,7 @@ let themeColors = ref<Array<themeColorsType>>([
const verticalRef = templateRef<HTMLElement | null>("verticalRef", null); const verticalRef = templateRef<HTMLElement | null>("verticalRef", null);
const horizontalRef = templateRef<HTMLElement | null>("horizontalRef", null); const horizontalRef = templateRef<HTMLElement | null>("horizontalRef", null);
const mixRef = templateRef<HTMLElement | null>("mixRef", null);
let layoutTheme = let layoutTheme =
ref(storageLocal.getItem("responsive-layout")) || ref(storageLocal.getItem("responsive-layout")) ||
@@ -100,7 +102,7 @@ const getThemeColorStyle = computed(() => {
}; };
}); });
function changeStorageConfigure(key, val) { function storageConfigureChange<T>(key: string, val: T): void {
const storageConfigure = instance.configure; const storageConfigure = instance.configure;
storageConfigure[key] = val; storageConfigure[key] = val;
instance.configure = storageConfigure; instance.configure = storageConfigure;
@@ -109,14 +111,14 @@ function changeStorageConfigure(key, val) {
function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) { function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
const targetEl = target || document.body; const targetEl = target || document.body;
let { className } = targetEl; let { className } = targetEl;
className = className.replace(clsName, ""); className = className.replace(clsName, "").trim();
targetEl.className = flag ? `${className} ${clsName} ` : className; targetEl.className = flag ? `${className} ${clsName} ` : className;
} }
// 灰色模式设置 // 灰色模式设置
const greyChange = (value): void => { const greyChange = (value): void => {
toggleClass(settings.greyVal, "html-grey", document.querySelector("html")); toggleClass(settings.greyVal, "html-grey", document.querySelector("html"));
changeStorageConfigure("grey", value); storageConfigureChange("grey", value);
}; };
// 色弱模式设置 // 色弱模式设置
@@ -126,58 +128,55 @@ const weekChange = (value): void => {
"html-weakness", "html-weakness",
document.querySelector("html") document.querySelector("html")
); );
changeStorageConfigure("weak", value); storageConfigureChange("weak", value);
}; };
const tagsChange = () => { const tagsChange = () => {
let showVal = settings.tabsVal; let showVal = settings.tabsVal;
changeStorageConfigure("hideTabs", showVal); storageConfigureChange("hideTabs", showVal);
emitter.emit("tagViewsChange", showVal); emitter.emit("tagViewsChange", showVal);
}; };
const multiTagsCacheChange = () => { const multiTagsCacheChange = () => {
let multiTagsCache = settings.multiTagsCache; let multiTagsCache = settings.multiTagsCache;
changeStorageConfigure("multiTagsCache", multiTagsCache); storageConfigureChange("multiTagsCache", multiTagsCache);
useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache); useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache);
}; };
// 清空缓存并返回登录页 // 清空缓存并返回登录页
function onReset() { function onReset() {
toggleClass(getConfig().Grey, "html-grey", document.querySelector("html")); router.push("/login");
toggleClass( const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig();
getConfig().Weak, useAppStoreHook().setLayout(Layout);
"html-weakness", useEpThemeStoreHook().setEpThemeColor(EpThemeColor);
document.querySelector("html") useMultiTagsStoreHook().multiTagsCacheChange(MultiTagsCache);
); toggleClass(Grey, "html-grey", document.querySelector("html"));
toggleClass(Weak, "html-weakness", document.querySelector("html"));
useMultiTagsStoreHook().handleTags("equal", [ useMultiTagsStoreHook().handleTags("equal", [
{ {
path: "/welcome", path: "/welcome",
parentPath: "/", parentPath: "/",
meta: { meta: {
title: "menus.hshome", title: "menus.hshome",
icon: "HomeFilled", icon: "home-filled",
i18n: true, i18n: true
showLink: true
} }
} }
]); ]);
useMultiTagsStoreHook().multiTagsCacheChange(getConfig().MultiTagsCache);
useEpThemeStoreHook().setEpThemeColor(getConfig().EpThemeColor);
storageLocal.clear(); storageLocal.clear();
storageSession.clear(); storageSession.clear();
router.push("/login");
} }
function onChange(label) { function onChange(label) {
changeStorageConfigure("showModel", label); storageConfigureChange("showModel", label);
emitter.emit("tagViewsShowModel", label); emitter.emit("tagViewsShowModel", label);
} }
// 侧边栏Logo // 侧边栏Logo
function logoChange() { function logoChange() {
unref(logoVal) unref(logoVal)
? changeStorageConfigure("showLogo", true) ? storageConfigureChange("showLogo", true)
: changeStorageConfigure("showLogo", false); : storageConfigureChange("showLogo", false);
emitter.emit("logoChange", unref(logoVal)); emitter.emit("logoChange", unref(logoVal));
} }
@@ -192,10 +191,17 @@ watch(instance, ({ layout }) => {
case "vertical": case "vertical":
toggleClass(true, isSelect, unref(verticalRef)); toggleClass(true, isSelect, unref(verticalRef));
debounce(setFalse([horizontalRef]), 50); debounce(setFalse([horizontalRef]), 50);
debounce(setFalse([mixRef]), 50);
break; break;
case "horizontal": case "horizontal":
toggleClass(true, isSelect, unref(horizontalRef)); toggleClass(true, isSelect, unref(horizontalRef));
debounce(setFalse([verticalRef]), 50); debounce(setFalse([verticalRef]), 50);
debounce(setFalse([mixRef]), 50);
break;
case "mix":
toggleClass(true, isSelect, unref(mixRef));
debounce(setFalse([verticalRef]), 50);
debounce(setFalse([horizontalRef]), 50);
break; break;
} }
}); });
@@ -310,30 +316,40 @@ nextTick(() => {
:active-icon="dayIcon" :active-icon="dayIcon"
:inactive-icon="darkIcon" :inactive-icon="darkIcon"
@change="dataThemeChange" @change="dataThemeChange"
> />
</el-switch>
<el-divider>导航栏模式</el-divider> <el-divider>导航栏模式</el-divider>
<ul class="pure-theme"> <ul class="pure-theme">
<el-tooltip class="item" content="左侧菜单模式" placement="bottom"> <el-tooltip class="item" content="左侧模式" placement="bottom">
<li <li
:class="layoutTheme.layout === 'vertical' ? $style.isSelect : ''" :class="layoutTheme.layout === 'vertical' ? $style.isSelect : ''"
ref="verticalRef" ref="verticalRef"
@click="setLayoutModel('vertical')" @click="setLayoutModel('vertical')"
> >
<div></div> <div />
<div></div> <div />
</li> </li>
</el-tooltip> </el-tooltip>
<el-tooltip class="item" content="顶部菜单模式" placement="bottom"> <el-tooltip class="item" content="顶部模式" placement="bottom">
<li <li
:class="layoutTheme.layout === 'horizontal' ? $style.isSelect : ''" :class="layoutTheme.layout === 'horizontal' ? $style.isSelect : ''"
ref="horizontalRef" ref="horizontalRef"
@click="setLayoutModel('horizontal')" @click="setLayoutModel('horizontal')"
> >
<div></div> <div />
<div></div> <div />
</li>
</el-tooltip>
<el-tooltip class="item" content="混合模式" placement="bottom">
<li
:class="layoutTheme.layout === 'mix' ? $style.isSelect : ''"
ref="mixRef"
@click="setLayoutModel('mix')"
>
<div />
<div />
</li> </li>
</el-tooltip> </el-tooltip>
</ul> </ul>
@@ -351,7 +367,7 @@ nextTick(() => {
:size="17" :size="17"
:color="getThemeColor(item.themeColor)" :color="getThemeColor(item.themeColor)"
> >
<Check /> <IconifyIconOffline icon="check" />
</el-icon> </el-icon>
</li> </li>
</ul> </ul>
@@ -367,8 +383,7 @@ nextTick(() => {
active-text="" active-text=""
inactive-text="" inactive-text=""
@change="greyChange" @change="greyChange"
> />
</el-switch>
</li> </li>
<li v-show="!dataTheme"> <li v-show="!dataTheme">
<span>色弱模式</span> <span>色弱模式</span>
@@ -379,8 +394,7 @@ nextTick(() => {
active-text="" active-text=""
inactive-text="" inactive-text=""
@change="weekChange" @change="weekChange"
> />
</el-switch>
</li> </li>
<li> <li>
<span>隐藏标签页</span> <span>隐藏标签页</span>
@@ -391,8 +405,7 @@ nextTick(() => {
active-text="" active-text=""
inactive-text="" inactive-text=""
@change="tagsChange" @change="tagsChange"
> />
</el-switch>
</li> </li>
<li> <li>
<span>侧边栏Logo</span> <span>侧边栏Logo</span>
@@ -405,8 +418,7 @@ nextTick(() => {
active-text="" active-text=""
inactive-text="" inactive-text=""
@change="logoChange" @change="logoChange"
> />
</el-switch>
</li> </li>
<li> <li>
<span>标签页持久化</span> <span>标签页持久化</span>
@@ -417,8 +429,7 @@ nextTick(() => {
active-text="" active-text=""
inactive-text="" inactive-text=""
@change="multiTagsCacheChange" @change="multiTagsCacheChange"
> />
</el-switch>
</li> </li>
<li> <li>
@@ -436,7 +447,12 @@ nextTick(() => {
style="width: 90%; margin: 24px 15px" style="width: 90%; margin: 24px 15px"
@click="onReset" @click="onReset"
> >
<i class="fa fa-sign-out"></i> <IconifyIconOffline
icon="fa-sign-out"
width="15"
height="15"
style="margin-right: 4px"
/>
清空缓存并返回登录页</el-button 清空缓存并返回登录页</el-button
> >
</panel> </panel>
@@ -444,7 +460,7 @@ nextTick(() => {
<style scoped module> <style scoped module>
.isSelect { .isSelect {
border: 2px solid #0960bd; border: 2px solid var(--el-color-primary);
} }
</style> </style>
@@ -476,15 +492,14 @@ nextTick(() => {
.pure-theme { .pure-theme {
margin-top: 25px; margin-top: 25px;
width: 100%; width: 100%;
height: 100px; height: 50px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-around; justify-content: space-around;
li { li {
margin: 10px; width: 18%;
width: 36%; height: 45px;
height: 70px;
background: #f0f2f5; background: #f0f2f5;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
@@ -522,6 +537,27 @@ nextTick(() => {
} }
} }
} }
&:nth-child(3) {
div {
&:nth-child(1) {
width: 100%;
height: 30%;
background: #1b2a47;
box-shadow: 0 0 1px #888;
}
&:nth-child(2) {
width: 30%;
height: 70%;
bottom: 0;
left: 0;
background: #fff;
box-shadow: 0 0 1px #888;
position: absolute;
}
}
}
} }
} }

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import { isEqual } from "lodash-es"; import { isEqual } from "lodash-unified";
import { transformI18n } from "/@/plugins/i18n"; import { transformI18n } from "/@/plugins/i18n";
import { getParentPaths, findRouteByPath } from "/@/router/utils"; import { getParentPaths, findRouteByPath } from "/@/router/utils";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,22 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { import { ref, PropType, nextTick, computed, CSSProperties } from "vue";
ref,
PropType,
nextTick,
computed,
CSSProperties,
getCurrentInstance
} from "vue";
import path from "path"; import path from "path";
import { useNav } from "../../hooks/nav";
import { childrenType } from "../../types"; import { childrenType } from "../../types";
import { transformI18n } from "/@/plugins/i18n"; import { transformI18n } from "/@/plugins/i18n";
import { findIconReg } from "/@/components/ReIcon";
import Icon from "/@/components/ReIcon/src/Icon.vue";
import { useAppStoreHook } from "/@/store/modules/app"; import { useAppStoreHook } from "/@/store/modules/app";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
const instance = getCurrentInstance().appContext.app.config.globalProperties; const { pureApp } = useNav();
const menuMode = instance.$storage.layout?.layout === "vertical"; const menuMode = ["vertical", "mix"].includes(pureApp.layout);
const pureApp = useAppStoreHook();
const props = defineProps({ const props = defineProps({
item: { item: {
@@ -32,6 +24,19 @@ const props = defineProps({
} }
}); });
const getExtraIconStyle = computed((): CSSProperties => {
if (useAppStoreHook().getSidebarStatus) {
return {
position: "absolute",
right: "10px"
};
} else {
return {
position: "static"
};
}
});
const getNoDropdownStyle = computed((): CSSProperties => { const getNoDropdownStyle = computed((): CSSProperties => {
return { return {
display: "flex", display: "flex",
@@ -93,7 +98,6 @@ function hoverMenu(key) {
: Object.assign(key, { : Object.assign(key, {
showTooltip: false showTooltip: false
}); });
hoverMenuMap.set(key, true); hoverMenuMap.set(key, true);
}); });
} }
@@ -107,6 +111,10 @@ function hasOneShowingChild(
return true; return true;
}); });
if (showingChildren[0]?.meta?.showParent) {
return false;
}
if (showingChildren.length === 1) { if (showingChildren.length === 1) {
return true; return true;
} }
@@ -120,8 +128,8 @@ function hasOneShowingChild(
function resolvePath(routePath) { function resolvePath(routePath) {
const httpReg = /^http(s?):\/\//; const httpReg = /^http(s?):\/\//;
if (httpReg.test(routePath)) { if (httpReg.test(routePath) || httpReg.test(props.basePath)) {
return props.basePath + "/" + routePath; return routePath || props.basePath;
} else { } else {
return path.resolve(props.basePath, routePath); return path.resolve(props.basePath, routePath);
} }
@@ -140,16 +148,28 @@ function resolvePath(routePath) {
:class="{ 'submenu-title-noDropdown': !isNest }" :class="{ 'submenu-title-noDropdown': !isNest }"
:style="getNoDropdownStyle" :style="getNoDropdownStyle"
> >
<el-icon v-show="props.item.meta.icon"> <div class="el-icon" v-show="props.item.meta.icon">
<component <component
:is=" :is="
findIconReg( useRenderIcon(
onlyOneChild.meta.icon || onlyOneChild.meta.icon ||
(props.item.meta && props.item.meta.icon) (props.item.meta && props.item.meta.icon)
) )
" "
></component> />
</el-icon> </div>
<div
v-if="
!pureApp.sidebar.opened &&
pureApp.layout === 'mix' &&
props.item?.pathList?.length === 2
"
:style="getDivStyle"
>
<span :style="getMenuTextStyle">
{{ transformI18n(onlyOneChild.meta.title, onlyOneChild.meta.i18n) }}
</span>
</div>
<template #title> <template #title>
<div :style="getDivStyle"> <div :style="getDivStyle">
<span v-if="!menuMode">{{ <span v-if="!menuMode">{{
@@ -176,10 +196,13 @@ function resolvePath(routePath) {
}} }}
</span> </span>
</el-tooltip> </el-tooltip>
<Icon <FontIcon
v-if="onlyOneChild.meta.extraIcon" v-if="onlyOneChild.meta.extraIcon"
width="30px"
height="30px"
:style="getExtraIconStyle"
:icon="onlyOneChild.meta.extraIcon.name"
:svg="onlyOneChild.meta.extraIcon.svg ? true : false" :svg="onlyOneChild.meta.extraIcon.svg ? true : false"
:content="`${onlyOneChild.meta.extraIcon.name}`"
/> />
</div> </div>
</template> </template>
@@ -193,11 +216,14 @@ function resolvePath(routePath) {
popper-append-to-body popper-append-to-body
> >
<template #title> <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 <component
:is="findIconReg(props.item.meta && props.item.meta.icon)" :is="useRenderIcon(props.item.meta && props.item.meta.icon)"
></component> />
</el-icon> </div>
<span v-if="!menuMode">{{ <span v-if="!menuMode">{{
transformI18n(props.item.meta.title, props.item.meta.i18n) transformI18n(props.item.meta.title, props.item.meta.i18n)
}}</span> }}</span>
@@ -220,10 +246,13 @@ function resolvePath(routePath) {
</span> </span>
</div> </div>
</el-tooltip> </el-tooltip>
<Icon <FontIcon
v-if="props.item.meta.extraIcon" v-if="props.item.meta.extraIcon"
width="30px"
height="30px"
style="position: absolute; right: 10px"
:icon="props.item.meta.extraIcon.name"
:svg="props.item.meta.extraIcon.svg ? true : false" :svg="props.item.meta.extraIcon.svg ? true : false"
:content="`${props.item.meta.extraIcon.name}`"
/> />
</template> </template>
<sidebar-item <sidebar-item

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ import {
ref, ref,
watch, watch,
unref, unref,
reactive,
nextTick, nextTick,
computed, computed,
ComputedRef, ComputedRef,
@@ -11,28 +12,28 @@ import {
getCurrentInstance getCurrentInstance
} from "vue"; } from "vue";
import close from "/@/assets/svg/close.svg"; import close from "/@/assets/svg/close.svg?component";
import refresh from "/@/assets/svg/refresh.svg"; import refresh from "/@/assets/svg/refresh.svg?component";
import closeAll from "/@/assets/svg/close_all.svg"; import closeAll from "/@/assets/svg/close_all.svg?component";
import closeLeft from "/@/assets/svg/close_left.svg"; import closeLeft from "/@/assets/svg/close_left.svg?component";
import closeOther from "/@/assets/svg/close_other.svg"; import closeOther from "/@/assets/svg/close_other.svg?component";
import closeRight from "/@/assets/svg/close_right.svg"; import closeRight from "/@/assets/svg/close_right.svg?component";
import { $t as t } from "/@/plugins/i18n"; import { useI18n } from "vue-i18n";
import { emitter } from "/@/utils/mitt"; import { emitter } from "/@/utils/mitt";
import { isEqual, isEmpty } from "lodash-es";
import { transformI18n } from "/@/plugins/i18n";
import { storageLocal } from "/@/utils/storage"; import { storageLocal } from "/@/utils/storage";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import { isEqual, isEmpty } from "lodash-unified";
import { transformI18n, $t } from "/@/plugins/i18n";
import { RouteConfigs, tagsViewsType } from "../../types"; import { RouteConfigs, tagsViewsType } from "../../types";
import { useSettingStoreHook } from "/@/store/modules/settings"; import { useSettingStoreHook } from "/@/store/modules/settings";
import { handleAliveRoute, delAliveRoutes } from "/@/router/utils"; import { handleAliveRoute, delAliveRoutes } from "/@/router/utils";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
import { usePermissionStoreHook } from "/@/store/modules/permission"; import { usePermissionStoreHook } from "/@/store/modules/permission";
import { toggleClass, removeClass, hasClass } from "/@/utils/operate"; import { toggleClass, removeClass, hasClass } from "/@/utils/operate";
import { templateRef, useResizeObserver, useDebounceFn } from "@vueuse/core"; import { templateRef, useResizeObserver, useDebounceFn } from "@vueuse/core";
const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const translateX = ref<number>(0); const translateX = ref<number>(0);
@@ -40,11 +41,11 @@ const activeIndex = ref<number>(-1);
let refreshButton = "refresh-button"; let refreshButton = "refresh-button";
const instance = getCurrentInstance(); const instance = getCurrentInstance();
const pureSetting = useSettingStoreHook(); const pureSetting = useSettingStoreHook();
const showTags = ref(storageLocal.getItem("tagsVal") || false);
const tabDom = templateRef<HTMLElement | null>("tabDom", null); const tabDom = templateRef<HTMLElement | null>("tabDom", null);
const containerDom = templateRef<HTMLElement | null>("containerDom", null); const containerDom = templateRef<HTMLElement | null>("containerDom", null);
const scrollbarDom = templateRef<HTMLElement | null>("scrollbarDom", null); const scrollbarDom = templateRef<HTMLElement | null>("scrollbarDom", null);
const showTags =
ref(storageLocal.getItem("responsive-configure").hideTabs) ?? "false";
let multiTags: ComputedRef<Array<RouteConfigs>> = computed(() => { let multiTags: ComputedRef<Array<RouteConfigs>> = computed(() => {
return useMultiTagsStoreHook()?.multiTags; return useMultiTagsStoreHook()?.multiTags;
}); });
@@ -106,7 +107,11 @@ const iconIsActive = computed(() => {
const dynamicTagView = () => { const dynamicTagView = () => {
const index = multiTags.value.findIndex(item => { const index = multiTags.value.findIndex(item => {
return item.path === route.path; if (item?.query) {
return isEqual(route?.query, item?.query);
} else {
return item.path === route.path;
}
}); });
moveToView(index); moveToView(index);
}; };
@@ -128,15 +133,15 @@ const moveToView = (index: number): void => {
if (!instance.refs["dynamic" + index]) { if (!instance.refs["dynamic" + index]) {
return; return;
} }
const tabItemEl = instance.refs["dynamic" + index]; const tabItemEl = instance.refs["dynamic" + index][0];
const tabItemElOffsetLeft = (tabItemEl as HTMLElement).offsetLeft; const tabItemElOffsetLeft = (tabItemEl as HTMLElement)?.offsetLeft;
const tabItemOffsetWidth = (tabItemEl as HTMLElement).offsetWidth; const tabItemOffsetWidth = (tabItemEl as HTMLElement)?.offsetWidth;
// 标签页导航栏可视长度(不包含溢出部分) // 标签页导航栏可视长度(不包含溢出部分)
const scrollbarDomWidth = scrollbarDom.value const scrollbarDomWidth = scrollbarDom.value
? scrollbarDom.value.offsetWidth ? scrollbarDom.value?.offsetWidth
: 0; : 0;
// 已有标签页总长度(包含溢出部分) // 已有标签页总长度(包含溢出部分)
const tabDomWidth = tabDom.value ? tabDom.value.offsetWidth : 0; const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) { if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) {
translateX.value = 0; translateX.value = 0;
@@ -186,45 +191,45 @@ const handleScroll = (offset: number): void => {
} }
}; };
const tagsViews = ref<Array<tagsViewsType>>([ const tagsViews = reactive<Array<tagsViewsType>>([
{ {
icon: refresh, icon: refresh,
text: t("buttons.hsreload"), text: $t("buttons.hsreload"),
divided: false, divided: false,
disabled: false, disabled: false,
show: true show: true
}, },
{ {
icon: close, icon: close,
text: t("buttons.hscloseCurrentTab"), text: $t("buttons.hscloseCurrentTab"),
divided: false, divided: false,
disabled: multiTags.value.length > 1 ? false : true, disabled: multiTags.value.length > 1 ? false : true,
show: true show: true
}, },
{ {
icon: closeLeft, icon: closeLeft,
text: t("buttons.hscloseLeftTabs"), text: $t("buttons.hscloseLeftTabs"),
divided: true, divided: true,
disabled: multiTags.value.length > 1 ? false : true, disabled: multiTags.value.length > 1 ? false : true,
show: true show: true
}, },
{ {
icon: closeRight, icon: closeRight,
text: t("buttons.hscloseRightTabs"), text: $t("buttons.hscloseRightTabs"),
divided: false, divided: false,
disabled: multiTags.value.length > 1 ? false : true, disabled: multiTags.value.length > 1 ? false : true,
show: true show: true
}, },
{ {
icon: closeOther, icon: closeOther,
text: t("buttons.hscloseOtherTabs"), text: $t("buttons.hscloseOtherTabs"),
divided: true, divided: true,
disabled: multiTags.value.length > 2 ? false : true, disabled: multiTags.value.length > 2 ? false : true,
show: true show: true
}, },
{ {
icon: closeAll, icon: closeAll,
text: t("buttons.hscloseAllTabs"), text: $t("buttons.hscloseAllTabs"),
divided: false, divided: false,
disabled: multiTags.value.length > 1 ? false : true, disabled: multiTags.value.length > 1 ? false : true,
show: true show: true
@@ -314,8 +319,7 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
meta: { meta: {
title: "menus.hshome", title: "menus.hshome",
i18n: true, i18n: true,
icon: "el-icon-s-home", icon: "home-filled"
showLink: true
} }
}, },
obj obj
@@ -424,6 +428,12 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
}); });
} }
function handleCommand(command: object) {
// @ts-expect-error
const { key, item } = command;
onClickDrop(key, item);
}
// 触发右键中菜单的点击事件 // 触发右键中菜单的点击事件
function selectTag(key, item) { function selectTag(key, item) {
onClickDrop(key, item, currentSelect.value); onClickDrop(key, item, currentSelect.value);
@@ -435,13 +445,13 @@ function closeMenu() {
function showMenus(value: boolean) { function showMenus(value: boolean) {
Array.of(1, 2, 3, 4, 5).forEach(v => { Array.of(1, 2, 3, 4, 5).forEach(v => {
tagsViews.value[v].show = value; tagsViews[v].show = value;
}); });
} }
function disabledMenus(value: boolean) { function disabledMenus(value: boolean) {
Array.of(1, 2, 3, 4, 5).forEach(v => { Array.of(1, 2, 3, 4, 5).forEach(v => {
tagsViews.value[v].disabled = value; tagsViews[v].disabled = value;
}); });
} }
@@ -463,35 +473,34 @@ function showMenuModel(
showMenus(true); showMenus(true);
if (refresh) { if (refresh) {
tagsViews.value[0].show = true; tagsViews[0].show = true;
} }
/** /**
* currentIndex为1时左侧的菜单是首页则不显示关闭左侧标签页 * currentIndex为1时左侧的菜单是首页则不显示关闭左侧标签页
* 如果currentIndex等于routeLength-1右侧没有菜单则不显示关闭右侧标签页 * 如果currentIndex等于routeLength-1右侧没有菜单则不显示关闭右侧标签页
*/ */
if (currentIndex === 1 && routeLength !== 2) { if (currentIndex === 1 && routeLength !== 2) {
// 左侧的菜单是首页,右侧存在别的菜单 // 左侧的菜单是首页,右侧存在别的菜单
tagsViews.value[2].show = false; tagsViews[2].show = false;
Array.of(1, 3, 4, 5).forEach(v => { Array.of(1, 3, 4, 5).forEach(v => {
tagsViews.value[v].disabled = false; tagsViews[v].disabled = false;
}); });
tagsViews.value[2].disabled = true; tagsViews[2].disabled = true;
} else if (currentIndex === 1 && routeLength === 2) { } else if (currentIndex === 1 && routeLength === 2) {
disabledMenus(false); disabledMenus(false);
// 左侧的菜单是首页,右侧不存在别的菜单 // 左侧的菜单是首页,右侧不存在别的菜单
Array.of(2, 3, 4).forEach(v => { Array.of(2, 3, 4).forEach(v => {
tagsViews.value[v].show = false; tagsViews[v].show = false;
tagsViews.value[v].disabled = true; tagsViews[v].disabled = true;
}); });
} else if (routeLength - 1 === currentIndex && currentIndex !== 0) { } else if (routeLength - 1 === currentIndex && currentIndex !== 0) {
// 当前路由是所有路由中的最后一个 // 当前路由是所有路由中的最后一个
tagsViews.value[3].show = false; tagsViews[3].show = false;
Array.of(1, 2, 4, 5).forEach(v => { Array.of(1, 2, 4, 5).forEach(v => {
tagsViews.value[v].disabled = false; tagsViews[v].disabled = false;
}); });
tagsViews.value[3].disabled = true; tagsViews[3].disabled = true;
} else if (currentIndex === 0 || currentPath === "/redirect/welcome") { } else if (currentIndex === 0 || currentPath === "/redirect/welcome") {
// 当前路由为首页 // 当前路由为首页
disabledMenus(true); disabledMenus(true);
@@ -505,10 +514,10 @@ function openMenu(tag, e) {
if (tag.path === "/welcome") { if (tag.path === "/welcome") {
// 右键菜单为首页,只显示刷新 // 右键菜单为首页,只显示刷新
showMenus(false); showMenus(false);
tagsViews.value[0].show = true; tagsViews[0].show = true;
} else if (route.path !== tag.path) { } else if (route.path !== tag.path) {
// 右键菜单不匹配当前路由,隐藏刷新 // 右键菜单不匹配当前路由,隐藏刷新
tagsViews.value[0].show = false; tagsViews[0].show = false;
showMenuModel(tag.path, tag.query); showMenuModel(tag.path, tag.query);
} else if ( } else if (
// eslint-disable-next-line no-dupe-else-if // eslint-disable-next-line no-dupe-else-if
@@ -517,7 +526,7 @@ function openMenu(tag, e) {
) { ) {
showMenus(true); showMenus(true);
// 只有两个标签时不显示关闭其他标签页 // 只有两个标签时不显示关闭其他标签页
tagsViews.value[4].show = false; tagsViews[4].show = false;
} else if (route.path === tag.path) { } else if (route.path === tag.path) {
// 右键当前激活的菜单 // 右键当前激活的菜单
showMenuModel(tag.path, tag.query, true); showMenuModel(tag.path, tag.query, true);
@@ -552,30 +561,32 @@ function tagOnClick(item) {
} }
// 鼠标移入 // 鼠标移入
function onMouseenter(item, index) { function onMouseenter(index) {
if (index) activeIndex.value = index; if (index) activeIndex.value = index;
if (unref(showModel) === "smart") { if (unref(showModel) === "smart") {
if (hasClass(instance.refs["schedule" + index], "schedule-active")) return; if (hasClass(instance.refs["schedule" + index][0], "schedule-active"))
toggleClass(true, "schedule-in", instance.refs["schedule" + index]); return;
toggleClass(false, "schedule-out", instance.refs["schedule" + index]); toggleClass(true, "schedule-in", instance.refs["schedule" + index][0]);
toggleClass(false, "schedule-out", instance.refs["schedule" + index][0]);
} else { } else {
if (hasClass(instance.refs["dynamic" + index], "card-active")) return; if (hasClass(instance.refs["dynamic" + index][0], "card-active")) return;
toggleClass(true, "card-in", instance.refs["dynamic" + index]); toggleClass(true, "card-in", instance.refs["dynamic" + index][0]);
toggleClass(false, "card-out", instance.refs["dynamic" + index]); toggleClass(false, "card-out", instance.refs["dynamic" + index][0]);
} }
} }
// 鼠标移出 // 鼠标移出
function onMouseleave(item, index) { function onMouseleave(index) {
activeIndex.value = -1; activeIndex.value = -1;
if (unref(showModel) === "smart") { if (unref(showModel) === "smart") {
if (hasClass(instance.refs["schedule" + index], "schedule-active")) return; if (hasClass(instance.refs["schedule" + index][0], "schedule-active"))
toggleClass(false, "schedule-in", instance.refs["schedule" + index]); return;
toggleClass(true, "schedule-out", instance.refs["schedule" + index]); toggleClass(false, "schedule-in", instance.refs["schedule" + index][0]);
toggleClass(true, "schedule-out", instance.refs["schedule" + index][0]);
} else { } else {
if (hasClass(instance.refs["dynamic" + index], "card-active")) return; if (hasClass(instance.refs["dynamic" + index][0], "card-active")) return;
toggleClass(false, "card-in", instance.refs["dynamic" + index]); toggleClass(false, "card-in", instance.refs["dynamic" + index][0]);
toggleClass(true, "card-out", instance.refs["dynamic" + index]); toggleClass(true, "card-out", instance.refs["dynamic" + index][0]);
} }
} }
@@ -629,7 +640,9 @@ const getContextMenuStyle = computed((): CSSProperties => {
<template> <template>
<div ref="containerDom" class="tags-view" v-if="!showTags"> <div ref="containerDom" class="tags-view" v-if="!showTags">
<i class="ri-arrow-left-s-line" @click="handleScroll(200)"></i> <div class="arrow-left">
<IconifyIconOffline icon="arrow-left-s-line" @click="handleScroll(200)" />
</div>
<div ref="scrollbarDom" class="scroll-container"> <div ref="scrollbarDom" class="scroll-container">
<div class="tab" ref="tabDom" :style="getTabStyle"> <div class="tab" ref="tabDom" :style="getTabStyle">
<div <div
@@ -644,14 +657,14 @@ const getContextMenuStyle = computed((): CSSProperties => {
: '' : ''
]" ]"
@contextmenu.prevent="openMenu(item, $event)" @contextmenu.prevent="openMenu(item, $event)"
@mouseenter.prevent="onMouseenter(item, index)" @mouseenter.prevent="onMouseenter(index)"
@mouseleave.prevent="onMouseleave(item, index)" @mouseleave.prevent="onMouseleave(index)"
@click="tagOnClick(item)" @click="tagOnClick(item)"
> >
<router-link :to="item.path" <router-link :to="item.path"
>{{ transformI18n(item.meta.title, item.meta.i18n) }} >{{ transformI18n(item.meta.title, item.meta.i18n) }}
</router-link> </router-link>
<el-icon <span
v-if=" v-if="
iconIsActive(item, index) || iconIsActive(item, index) ||
(index === activeIndex && index !== 0) (index === activeIndex && index !== 0)
@@ -659,17 +672,22 @@ const getContextMenuStyle = computed((): CSSProperties => {
class="el-icon-close" class="el-icon-close"
@click.stop="deleteMenu(item)" @click.stop="deleteMenu(item)"
> >
<CloseBold /> <IconifyIconOffline icon="close-bold" />
</el-icon> </span>
<div <div
:ref="'schedule' + index" :ref="'schedule' + index"
v-if="showModel !== 'card'" v-if="showModel !== 'card'"
:class="[scheduleIsActive(item)]" :class="[scheduleIsActive(item)]"
></div> />
</div> </div>
</div> </div>
</div> </div>
<i class="ri-arrow-right-s-line" @click="handleScroll(-200)"></i> <span class="arrow-right">
<IconifyIconOffline
icon="arrow-right-s-line"
@click="handleScroll(-200)"
/>
</span>
<!-- 右键菜单按钮 --> <!-- 右键菜单按钮 -->
<transition name="el-zoom-in-top"> <transition name="el-zoom-in-top">
<ul <ul
@@ -685,7 +703,7 @@ const getContextMenuStyle = computed((): CSSProperties => {
> >
<li v-if="item.show" @click="selectTag(key, item)"> <li v-if="item.show" @click="selectTag(key, item)">
<component :is="item.icon" :key="key" /> <component :is="item.icon" :key="key" />
{{ $t(item.text) }} {{ t(item.text) }}
</li> </li>
</div> </div>
</ul> </ul>
@@ -693,37 +711,43 @@ const getContextMenuStyle = computed((): CSSProperties => {
<!-- 右侧功能按钮 --> <!-- 右侧功能按钮 -->
<ul class="right-button"> <ul class="right-button">
<li> <li>
<el-icon <span
:title="$t('buttons.hsrefreshRoute')" :title="t('buttons.hsrefreshRoute')"
class="el-icon-refresh-right rotate" class="el-icon-refresh-right rotate"
@click="onFresh" @click="onFresh"
> >
<RefreshRight /> <IconifyIconOffline icon="refresh-right" />
</el-icon> </span>
</li> </li>
<li> <li>
<el-dropdown trigger="click" placement="bottom-end"> <el-dropdown
<el-icon> trigger="click"
<ArrowDown /> placement="bottom-end"
</el-icon> @command="handleCommand"
>
<IconifyIconOffline icon="arrow-down" />
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item <el-dropdown-item
v-for="(item, key) in tagsViews" v-for="(item, key) in tagsViews"
:key="key" :key="key"
:command="{ key, item }"
:divided="item.divided" :divided="item.divided"
:disabled="item.disabled" :disabled="item.disabled"
@click="onClickDrop(key, item)"
> >
<component :is="item.icon" :key="key" /> <component
{{ $t(item.text) }} :is="item.icon"
:key="key"
style="margin-right: 6px"
/>
{{ t(item.text) }}
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
</li> </li>
<li> <li>
<slot></slot> <slot />
</li> </li>
</ul> </ul>
</div> </div>

63
src/layout/frameView.vue Normal file
View 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>

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

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

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

View File

@@ -11,14 +11,15 @@ import { setType } from "./types";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { routerArrays } from "./types"; import { routerArrays } from "./types";
import { emitter } from "/@/utils/mitt"; import { emitter } from "/@/utils/mitt";
import backTop from "/@/assets/svg/back_top.svg";
import { useAppStoreHook } from "/@/store/modules/app"; import { useAppStoreHook } from "/@/store/modules/app";
import fullScreen from "/@/assets/svg/full_screen.svg";
import exitScreen from "/@/assets/svg/exit_screen.svg";
import { deviceDetection } from "/@/utils/deviceDetection"; import { deviceDetection } from "/@/utils/deviceDetection";
import { useMultiTagsStore } from "/@/store/modules/multiTags"; import { useMultiTagsStore } from "/@/store/modules/multiTags";
import { useSettingStoreHook } from "/@/store/modules/settings"; import { useSettingStoreHook } from "/@/store/modules/settings";
import backTop from "/@/assets/svg/back_top.svg?component";
import fullScreen from "/@/assets/svg/full_screen.svg?component";
import exitScreen from "/@/assets/svg/exit_screen.svg?component";
import navbar from "./components/navbar.vue"; import navbar from "./components/navbar.vue";
import tag from "./components/tag/index.vue"; import tag from "./components/tag/index.vue";
import appMain from "./components/appMain.vue"; import appMain from "./components/appMain.vue";
@@ -170,7 +171,8 @@ const layoutHeader = defineComponent({
}, },
{ {
default: () => [ default: () => [
!pureSetting.hiddenSideBar && layout.value.includes("vertical") !pureSetting.hiddenSideBar &&
(layout.value.includes("vertical") || layout.value.includes("mix"))
? h(navbar) ? h(navbar)
: h("div"), : h("div"),
!pureSetting.hiddenSideBar && layout.value.includes("horizontal") !pureSetting.hiddenSideBar && layout.value.includes("horizontal")
@@ -212,7 +214,10 @@ const layoutHeader = defineComponent({
@click="useAppStoreHook().toggleSideBar()" @click="useAppStoreHook().toggleSideBar()"
/> />
<Vertical <Vertical
v-show="!pureSetting.hiddenSideBar && layout.includes('vertical')" v-show="
!pureSetting.hiddenSideBar &&
(layout.includes('vertical') || layout.includes('mix'))
"
/> />
<div <div
:class="[ :class="[

View File

@@ -16,5 +16,5 @@ replace({
</script> </script>
<template> <template>
<div></div> <div />
</template> </template>

View File

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

View File

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

View File

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

View File

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

View 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";

View File

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

View File

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

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