mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-11-03 13:44:47 +08:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b48bc8049 | ||
|
|
21ff69b10e | ||
|
|
66f5d6d423 | ||
|
|
fd9ad7eb21 | ||
|
|
a0618c01ba | ||
|
|
7a3c1ab3cd | ||
|
|
5032a75221 | ||
|
|
384c789fc0 | ||
|
|
281675bdaf | ||
|
|
0004f1318c | ||
|
|
ab39864ef4 | ||
|
|
4e14ab22ba | ||
|
|
cd21f1e050 | ||
|
|
5fbb664da7 | ||
|
|
244ab7f990 | ||
|
|
96152ed134 | ||
|
|
37ab40f188 | ||
|
|
91ae63a8c5 | ||
|
|
6d7e92fed1 | ||
|
|
0706f37254 | ||
|
|
8a9695cf7c | ||
|
|
d395c9c6ba | ||
|
|
3dec3c002f | ||
|
|
bae1122e58 | ||
|
|
37e9d8a1ac | ||
|
|
dea9664677 | ||
|
|
775d7a2d32 | ||
|
|
13e7a13e9d | ||
|
|
47afa9209e | ||
|
|
933ced4ac4 | ||
|
|
8816e61e3a | ||
|
|
edf82ea727 | ||
|
|
a75cf8394e | ||
|
|
33a89834d7 | ||
|
|
649aab9c7d | ||
|
|
7a6ee58e6d | ||
|
|
b402a8924f | ||
|
|
9a5523d1c7 | ||
|
|
1e6d0283c9 | ||
|
|
a8377f8d45 | ||
|
|
cd653c83f0 | ||
|
|
643b36fc42 | ||
|
|
17ed4d640d | ||
|
|
613d20fc1b | ||
|
|
8934499349 | ||
|
|
bb86962186 | ||
|
|
30f598dfa5 | ||
|
|
de00537fdd | ||
|
|
5238ee7ad9 | ||
|
|
3ca9a7b5bc |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -32,7 +32,7 @@ body:
|
||||
label: 验证 (Verify)
|
||||
description: 在提交问题之前,请确保您执行以下操作 (Before submitting an issue, please ensure you do the following)
|
||||
options:
|
||||
- label: 是否仔细阅读过 [文档](https://pure-admin.github.io/pure-admin-doc/) (Have you read [documentation](https://pure-admin.github.io/pure-admin-doc/) carefully)
|
||||
- label: 是否仔细阅读过 [文档](https://pure-admin.cn/) (Have you read [documentation](https://pure-admin.cn/) carefully)
|
||||
required: true
|
||||
- label: 检查是否存在相同或类似的问题 [issues](https://github.com/pure-admin/vue-pure-admin/issues) (Check for the same or similar [issues](https://github.com/pure-admin/vue-pure-admin/issues))
|
||||
required: true
|
||||
|
||||
2
.github/workflows/linter.yml
vendored
2
.github/workflows/linter.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8.6.10
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Get pnpm store directory
|
||||
|
||||
2
.github/workflows/pages.yml
vendored
2
.github/workflows/pages.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
name: Install pnpm
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8.6.10
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Deploy 🔧
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"prettier --cache --ignore-unknown --write",
|
||||
"eslint --cache --fix"
|
||||
],
|
||||
"{!(package)*.json,*.code-snippets,.!({browserslist,nvm})*rc}": [
|
||||
"{!(package)*.json,*.code-snippets,.!({browserslist,npm,nvm})*rc}": [
|
||||
"prettier --cache --write--parser json"
|
||||
],
|
||||
"package.json": ["prettier --cache --write"],
|
||||
|
||||
5
.npmrc
5
.npmrc
@@ -1,3 +1,4 @@
|
||||
shell-emulator=true
|
||||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
||||
shell-emulator=true
|
||||
enable-pre-post-scripts=false
|
||||
strict-peer-dependencies=false
|
||||
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"christian-kohler.path-intellisense",
|
||||
"warmthsea.vscode-custom-code-color",
|
||||
"vscode-icons-team.vscode-icons",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"ms-azuretools.vscode-docker",
|
||||
|
||||
27
.vscode/settings.json
vendored
27
.vscode/settings.json
vendored
@@ -31,11 +31,18 @@
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"i18n-ally.namespace": true,
|
||||
"i18n-ally.enabledParsers": ["yaml", "js"],
|
||||
"i18n-ally.enabledParsers": [
|
||||
"yaml",
|
||||
"js"
|
||||
],
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.displayLanguage": "zh-CN",
|
||||
"i18n-ally.enabledFrameworks": ["vue"],
|
||||
"iconify.excludes": ["el"],
|
||||
"i18n-ally.enabledFrameworks": [
|
||||
"vue"
|
||||
],
|
||||
"iconify.excludes": [
|
||||
"el"
|
||||
],
|
||||
"vsmqtt.brokerProfiles": [
|
||||
{
|
||||
"name": "broker.emqx.io",
|
||||
@@ -43,5 +50,15 @@
|
||||
"port": 1883,
|
||||
"clientId": "vsmqtt_client_db34"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"vscodeCustomCodeColor.highlightValue": [
|
||||
"v-loading",
|
||||
"v-auth",
|
||||
"v-copy",
|
||||
"v-longpress",
|
||||
"v-optimize",
|
||||
"v-perms",
|
||||
"v-ripple"
|
||||
],
|
||||
"vscodeCustomCodeColor.highlightValueColor": "#b392f0",
|
||||
}
|
||||
@@ -1,10 +1,79 @@
|
||||
# 5.9.0 (2024-12-10)
|
||||
|
||||
### ✔️Refactor
|
||||
|
||||
- Upgrade `vite` to `v6` version, upgrade `sass` to the latest version, reconstruct the theme writing method, and deprecate [@pureadmin/theme](https://www.npmjs.com/package/@pureadmin/theme) , click to view [Related optimization point details](https://github.com/pure-admin/vue-pure-admin/pull/1188#issue-2630095115). For users who have the [Max version](https://pure-admin.cn/pages/max/), it is strongly recommended to upgrade. Subsequent Max version users will enjoy a more modern, beautiful and highly customized theme color
|
||||
- Use [code-inspector-plugin](https://www.npmjs.com/package/code-inspector-plugin) to replace [vite-plugin-vue-inspector](https://www.npmjs.com/package/vite-plugin-vue-inspector)
|
||||
|
||||
### 🎫Feat
|
||||
|
||||
- Added `ReDrawer` component
|
||||
- `pure-table` adds dynamic table header example
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed an issue where the height of the table does not automatically adapt when the full screen function is enabled or disabled after the table is expanded in the menu and department management
|
||||
|
||||
### 🍏Perf
|
||||
|
||||
- Optimize the layout of the department tree on the left side of user management
|
||||
|
||||
# 5.8.0 (2024-08-19)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Added a second button permission command (judged based on the `permissions` field returned by the login interface)
|
||||
- Functional pop-up box `ReDialog` adds whether to enable the `loading` loading animation function after clicking the confirmation button
|
||||
- `PureTableBar` component adds full screen and exit full screen functions
|
||||
- `VxeTableBar` component adds full screen and exit full screen functions
|
||||
- The OK button of the `ReDialog` component provides the close button animation `closeLoading` function
|
||||
- Add development environment code debugging `vite-plugin-vue-inspector` plug-in to improve development experience
|
||||
- Added `vite-plugin-checker` plugin for stricter type and `eslint` verification
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed the problem that the current tab cannot be closed after configuring the routing attribute `fixedTag` to `false`
|
||||
- Fixed the issue where `logo` cannot be hidden in top menu mode
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Optimize type hints for custom instructions
|
||||
- Optimize the press enter login function on the login page
|
||||
- Optimize the mask level of the left menu on the mobile side
|
||||
- Optimize system management-permission function style of role management
|
||||
- Upgraded dependencies, compatible with the latest version of `element-plus`
|
||||
|
||||
# 5.7.0 (2024-06-04)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Add Google style tabs
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed the issue where the text exceeds and is not hidden after the menu is folded in Firefox browser
|
||||
|
||||
# 5.6.0 (2024-05-14)
|
||||
|
||||
### ✔️ Refactor
|
||||
|
||||
- Upgrade `pnpm` to `v9` version, requiring `pnpm` version `>=9`
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed the issue where clicking on an external link would jump twice
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Optimize `ReSegmented` component
|
||||
|
||||
# 5.5.0 (2024-05-07)
|
||||
|
||||
### 📄 Docs
|
||||
|
||||
The addresses of the document site and full version preview site have been changed!
|
||||
|
||||
- The latest document site address: https://pure-admin.github.io/pure-admin-doc
|
||||
- The latest document site address: https://pure-admin.cn
|
||||
- The latest full version preview site address: https://pure-admin.github.io/vue-pure-admin
|
||||
|
||||
### ✔️ Refactor
|
||||
@@ -405,7 +474,7 @@ Totally `ESM` version
|
||||
### ✔️ Refactor
|
||||
|
||||
- completely removed `lodash` and its related libraries
|
||||
[Click here to see Why Removed? How to integrate it yourself? ](https://pure-admin.github.io/pure-admin-doc/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-5-%E7 %89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-lodash-%E5%92%8C% E5%85%B6%E7%9B%B8%E5%85%B3%E5%BA%93-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9 %99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
[Click here to see Why Removed? How to integrate it yourself? ](https://pure-admin.cn/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-5-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-lodash-%E5%92%8C%E5%85%B6%E7%9B%B8%E5%85%B3%E5%BA%93-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
@@ -425,7 +494,7 @@ Totally `ESM` version
|
||||
### ✔️ Refactor
|
||||
|
||||
- Completely removed `vxe-table`, after removal, the overall package size of the full version is reduced by `1.82MB`, and the initial startup time is basically the same as the lite version 🐮
|
||||
[Click here to see Why Removed? How to integrate it yourself?](https://pure-admin.github.io/pure-admin-doc/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-4-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-vxe-table-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
[Click here to see Why Removed? How to integrate it yourself?](https://pure-admin.cn/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-4-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-vxe-table-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
@@ -638,7 +707,7 @@ Totally `ESM` version
|
||||
|
||||
### ✔️ Refactor
|
||||
|
||||
- Replace `unocss` with `tailwindcss`, add `tailwindcss` [documentation](https://pure-admin.github.io/pure-admin-doc/pages/tailwindcss/)
|
||||
- Replace `unocss` with `tailwindcss`, add `tailwindcss` [documentation](https://pure-admin.cn/pages/tailwindcss/)
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
|
||||
77
CHANGELOG.md
77
CHANGELOG.md
@@ -1,10 +1,79 @@
|
||||
# 5.9.0 (2024-12-10)
|
||||
|
||||
### ✔️Refactor
|
||||
|
||||
- Upgrade `vite` to `v6` version, upgrade `sass` to the latest version, reconstruct the theme writing method, and deprecate [@pureadmin/theme](https://www.npmjs.com/package/@pureadmin/theme) , click to view [Related optimization point details](https://github.com/pure-admin/vue-pure-admin/pull/1188#issue-2630095115). For users who have the [Max version](https://pure-admin.cn/pages/max/), it is strongly recommended to upgrade. Subsequent Max version users will enjoy a more modern, beautiful and highly customized theme color
|
||||
- Use [code-inspector-plugin](https://www.npmjs.com/package/code-inspector-plugin) to replace [vite-plugin-vue-inspector](https://www.npmjs.com/package/vite-plugin-vue-inspector)
|
||||
|
||||
### 🎫Feat
|
||||
|
||||
- Added `ReDrawer` component
|
||||
- `pure-table` adds dynamic table header example
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed an issue where the height of the table does not automatically adapt when the full screen function is enabled or disabled after the table is expanded in the menu and department management
|
||||
|
||||
### 🍏Perf
|
||||
|
||||
- Optimize the layout of the department tree on the left side of user management
|
||||
|
||||
# 5.8.0 (2024-08-19)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Added a second button permission command (judged based on the `permissions` field returned by the login interface)
|
||||
- Functional pop-up box `ReDialog` adds whether to enable the `loading` loading animation function after clicking the confirmation button
|
||||
- `PureTableBar` component adds full screen and exit full screen functions
|
||||
- `VxeTableBar` component adds full screen and exit full screen functions
|
||||
- The OK button of the `ReDialog` component provides the close button animation `closeLoading` function
|
||||
- Add development environment code debugging `vite-plugin-vue-inspector` plug-in to improve development experience
|
||||
- Added `vite-plugin-checker` plugin for stricter type and `eslint` verification
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed the problem that the current tab cannot be closed after configuring the routing attribute `fixedTag` to `false`
|
||||
- Fixed the issue where `logo` cannot be hidden in top menu mode
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Optimize type hints for custom instructions
|
||||
- Optimize the press enter login function on the login page
|
||||
- Optimize the mask level of the left menu on the mobile side
|
||||
- Optimize system management-permission function style of role management
|
||||
- Upgraded dependencies, compatible with the latest version of `element-plus`
|
||||
|
||||
# 5.7.0 (2024-06-04)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Add Google style tabs
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed the issue where the text exceeds and is not hidden after the menu is folded in Firefox browser
|
||||
|
||||
# 5.6.0 (2024-05-14)
|
||||
|
||||
### ✔️ Refactor
|
||||
|
||||
- Upgrade `pnpm` to `v9` version, requiring `pnpm` version `>=9`
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed the issue where clicking on an external link would jump twice
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Optimize `ReSegmented` component
|
||||
|
||||
# 5.5.0 (2024-05-07)
|
||||
|
||||
### 📄 Docs
|
||||
|
||||
The addresses of the document site and full version preview site have been changed!
|
||||
|
||||
- The latest document site address: https://pure-admin.github.io/pure-admin-doc
|
||||
- The latest document site address: https://pure-admin.cn
|
||||
- The latest full version preview site address: https://pure-admin.github.io/vue-pure-admin
|
||||
|
||||
### ✔️ Refactor
|
||||
@@ -405,7 +474,7 @@ Totally `ESM` version
|
||||
### ✔️ Refactor
|
||||
|
||||
- completely removed `lodash` and its related libraries
|
||||
[Click here to see Why Removed? How to integrate it yourself? ](https://pure-admin.github.io/pure-admin-doc/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-5-%E7 %89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-lodash-%E5%92%8C% E5%85%B6%E7%9B%B8%E5%85%B3%E5%BA%93-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9 %99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
[Click here to see Why Removed? How to integrate it yourself? ](https://pure-admin.cn/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-5-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-lodash-%E5%92%8C%E5%85%B6%E7%9B%B8%E5%85%B3%E5%BA%93-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
@@ -425,7 +494,7 @@ Totally `ESM` version
|
||||
### ✔️ Refactor
|
||||
|
||||
- Completely removed `vxe-table`, after removal, the overall package size of the full version is reduced by `1.82MB`, and the initial startup time is basically the same as the lite version 🐮
|
||||
[Click here to see Why Removed? How to integrate it yourself?](https://pure-admin.github.io/pure-admin-doc/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-4-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-vxe-table-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
[Click here to see Why Removed? How to integrate it yourself?](https://pure-admin.cn/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-4-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-vxe-table-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
@@ -638,7 +707,7 @@ Totally `ESM` version
|
||||
|
||||
### ✔️ Refactor
|
||||
|
||||
- Replace `unocss` with `tailwindcss`, add `tailwindcss` [documentation](https://pure-admin.github.io/pure-admin-doc/pages/tailwindcss/)
|
||||
- Replace `unocss` with `tailwindcss`, add `tailwindcss` [documentation](https://pure-admin.cn/pages/tailwindcss/)
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
|
||||
@@ -1,10 +1,79 @@
|
||||
# 5.9.0 (2024-12-10)
|
||||
|
||||
### ✔️ Refactor
|
||||
|
||||
- 升级`vite`至`v6`版本,升级`sass`至最新版,重构主题写法,弃用 [@pureadmin/theme](https://www.npmjs.com/package/@pureadmin/theme),点击查看 [相关优化点细节](https://github.com/pure-admin/vue-pure-admin/pull/1188#issue-2630095115)。对于拥有 [Max版本](https://pure-admin.cn/pages/max/) 的用户平台强烈建议升级,后续`Max版本用户`会享有一套更现代、美观且自定义程度高的主题色
|
||||
- 使用 [code-inspector-plugin](https://www.npmjs.com/package/code-inspector-plugin) 替换 [vite-plugin-vue-inspector](https://www.npmjs.com/package/vite-plugin-vue-inspector)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- 新增函数式抽屉组件
|
||||
- `pure-table`添加动态表头示例
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- 修复在菜单、部门管理中,表格展开后启用或关闭全屏功能时,表格高度未自动适应的问题
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- 优化用户管理左侧部门树的布局
|
||||
|
||||
# 5.8.0 (2024-08-19)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- 新增第二种按钮权限指令(根据登录接口返回的`permissions`字段进行判断)
|
||||
- 函数式弹框`ReDialog`添加点击确认按钮后是否开启`loading`加载动画功能
|
||||
- `PureTableBar`组件添加全屏和退出全屏功能
|
||||
- `VxeTableBar`组件添加全屏和退出全屏功能
|
||||
- `ReDialog`组件的确定按钮提供关闭按钮动画`closeLoading`功能
|
||||
- 添加开发环境代码调试`vite-plugin-vue-inspector`插件,提升开发体验
|
||||
- 添加`vite-plugin-checker`插件,更严格的类型和`eslint`校验
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- 修复配置路由属性`fixedTag`为`false`后当前标签页不可关闭的问题
|
||||
- 修复顶部菜单模式下`logo`不可隐藏的问题
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- 优化自定义指令的类型提示
|
||||
- 优化登录页回车登录功能
|
||||
- 优化移动端左侧菜单遮罩层级
|
||||
- 优化系统管理-角色管理的权限功能样式
|
||||
- 升级依赖,`element-plus`最新版兼容处理
|
||||
|
||||
# 5.7.0 (2024-06-04)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- 添加谷歌风格的页签
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- 修复在火狐浏览器中菜单折叠后,文字超出未隐藏的问题
|
||||
|
||||
# 5.6.0 (2024-05-14)
|
||||
|
||||
### ✔️ Refactor
|
||||
|
||||
- 升级`pnpm`至`v9`版本,规定`pnpm`版本`>=9`
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- 修复点击外链会跳转两次的问题
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- 优化`ReSegmented`组件
|
||||
|
||||
# 5.5.0 (2024-05-07)
|
||||
|
||||
### 📄 Docs
|
||||
|
||||
文档站和完整版预览站地址更换!
|
||||
|
||||
- 最新文档站地址:https://pure-admin.github.io/pure-admin-doc
|
||||
- 最新文档站地址:https://pure-admin.cn
|
||||
- 最新完整版预览站地址:https://pure-admin.github.io/vue-pure-admin
|
||||
|
||||
### ✔️ Refactor
|
||||
@@ -404,7 +473,7 @@
|
||||
### ✔️ Refactor
|
||||
|
||||
- 完全移除了 `lodash` 和其相关库
|
||||
[点击此处查看为什么移除?如何自行集成?](https://pure-admin.github.io/pure-admin-doc/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-5-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-lodash-%E5%92%8C%E5%85%B6%E7%9B%B8%E5%85%B3%E5%BA%93-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
[点击此处查看为什么移除?如何自行集成?](https://pure-admin.cn/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-5-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-lodash-%E5%92%8C%E5%85%B6%E7%9B%B8%E5%85%B3%E5%BA%93-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
@@ -424,7 +493,7 @@
|
||||
### ✔️ Refactor
|
||||
|
||||
- 完全移除了 `vxe-table`,移除后,完整版整体打包大小减少 `1.82MB`,首启动时长基本和精简版持平 🐮
|
||||
[点击此处查看为什么移除?如何自行集成?](https://pure-admin.github.io/pure-admin-doc/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-4-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-vxe-table-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
[点击此处查看为什么移除?如何自行集成?](https://pure-admin.cn/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-4-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-vxe-table-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
@@ -637,7 +706,7 @@
|
||||
|
||||
### ✔️ Refactor
|
||||
|
||||
- 使用 `tailwindcss` 替换 `unocss`,新增 `tailwindcss` [使用文档](https://pure-admin.github.io/pure-admin-doc/pages/tailwindcss/)
|
||||
- 使用 `tailwindcss` 替换 `unocss`,新增 `tailwindcss` [使用文档](https://pure-admin.cn/pages/tailwindcss/)
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
FROM node:18-alpine as build-stage
|
||||
FROM node:20-alpine as build-stage
|
||||
|
||||
WORKDIR /app
|
||||
RUN corepack enable
|
||||
RUN corepack prepare pnpm@8.6.10 --activate
|
||||
RUN corepack prepare pnpm@latest --activate
|
||||
|
||||
RUN npm config set registry https://registry.npmmirror.com
|
||||
|
||||
|
||||
@@ -10,11 +10,9 @@
|
||||
|
||||
`vue-pure-admin` is an open source, free and out-of-the-box middle and backend management system template. Completely adopts `ECMAScript` module (`ESM`) specifications to write and organize code, using the latest `Vue3`, `Vite`, `Element-Plus`, `TypeScript`, `Pinia`, `Tailwindcss` and other mainstream technologies develop
|
||||
|
||||
## Sponsors
|
||||
## R&D philosophy
|
||||
|
||||
<a class="logo" href="https://ai-tools.cn/resume/start" target="_blank" rel="sponsored noopener">
|
||||
<img src="./public/sponsors/aitools.svg" alt="aitools">
|
||||
</a>
|
||||
Seek innovation in stability and see the future in technology
|
||||
|
||||
## Thin version (offering non-internationalized and internationalized versions)
|
||||
|
||||
@@ -30,12 +28,20 @@ The simplified version is based on the shelf extracted from [vue-pure-admin](htt
|
||||
|
||||
## Nanny-level documents
|
||||
|
||||
[Click me to view vue-pure-admin documentation](https://pure-admin.github.io/pure-admin-doc)
|
||||
[Click me to view vue-pure-admin documentation](https://pure-admin.cn/)
|
||||
[Click me to view @pureadmin/utils documentation](https://pure-admin-utils.netlify.app)
|
||||
|
||||
## Quality service, software outsourcing, sponsorship support
|
||||
|
||||
[Click me for details](https://pure-admin.github.io/pure-admin-doc/pages/service/)
|
||||
[Click me for details](https://pure-admin.cn/pages/service/)
|
||||
|
||||
## `js` version
|
||||
|
||||
[Click me to view js version](https://pure-admin.cn/pages/js/)
|
||||
|
||||
## `max` version
|
||||
|
||||
[Click me to view the max version](https://pure-admin.cn/pages/max/)
|
||||
|
||||
## Tauri
|
||||
|
||||
@@ -184,6 +190,8 @@ Thank you very much for your in-depth understanding of the source code and your
|
||||
| [WitMiao](https://github.com/WitMiao) | [code](https://github.com/pure-admin/vue-pure-admin/commits?author=WitMiao) |
|
||||
| [QFifteen](https://github.com/QFifteen) | [code](https://github.com/pure-admin/vue-pure-admin/commits?author=QFifteen) |
|
||||
| [edgexie](https://github.com/edgexie) | [code](https://github.com/pure-admin/vue-pure-admin/commits?author=edgexie) |
|
||||
| [way-jm](https://github.com/way-jm) | [code](https://github.com/pure-admin/vue-pure-admin/commits?author=way-jm) |
|
||||
| [simple-hui](https://github.com/simple-hui) | [code](https://github.com/pure-admin/vue-pure-admin/commits?author=simple-hui) |
|
||||
|
||||
## Git Contribution submission specification
|
||||
|
||||
|
||||
20
README.md
20
README.md
@@ -11,11 +11,9 @@
|
||||
`vue-pure-admin` 是一款开源免费且开箱即用的中后台管理系统模版。完全采用 `ECMAScript` 模块(`ESM`)规范来编写和组织代码,使用了最新的 `Vue3`、
|
||||
`Vite`、`Element-Plus`、`TypeScript`、`Pinia`、`Tailwindcss` 等主流技术开发
|
||||
|
||||
## 赞助商
|
||||
## 研发理念
|
||||
|
||||
<a class="logo" href="https://ai-tools.cn/resume/start" target="_blank" rel="sponsored noopener">
|
||||
<img src="./public/sponsors/aitools.svg" alt="aitools">
|
||||
</a>
|
||||
稳定中求创新,技术中见未来
|
||||
|
||||
## 精简版本(实际项目开发请用精简版本,提供 `非国际化` 、`国际化` 两个版本选择)
|
||||
|
||||
@@ -31,12 +29,20 @@
|
||||
|
||||
## 配套保姆级文档
|
||||
|
||||
[点我查看 vue-pure-admin 文档](https://pure-admin.github.io/pure-admin-doc)
|
||||
[点我查看 vue-pure-admin 文档](https://pure-admin.cn/)
|
||||
[点我查看 @pureadmin/utils 文档](https://pure-admin-utils.netlify.app)
|
||||
|
||||
## 优质服务、软件外包、赞助支持
|
||||
|
||||
[点我查看详情](https://pure-admin.github.io/pure-admin-doc/pages/service/)
|
||||
[点我查看详情](https://pure-admin.cn/pages/service/)
|
||||
|
||||
## `js` 版本
|
||||
|
||||
[点我查看 js 版本](https://pure-admin.cn/pages/js/)
|
||||
|
||||
## `max` 版本
|
||||
|
||||
[点我查看 max 版本](https://pure-admin.cn/pages/max/)
|
||||
|
||||
## `Tauri` 版本
|
||||
|
||||
@@ -185,6 +191,8 @@ docker run -dp 8080:80 --name pure-admin vue-pure-admin
|
||||
| [WitMiao](https://github.com/WitMiao) | [代码](https://github.com/pure-admin/vue-pure-admin/commits?author=WitMiao) |
|
||||
| [QFifteen](https://github.com/QFifteen) | [代码](https://github.com/pure-admin/vue-pure-admin/commits?author=QFifteen) |
|
||||
| [edgexie](https://github.com/edgexie) | [代码](https://github.com/pure-admin/vue-pure-admin/commits?author=edgexie) |
|
||||
| [way-jm](https://github.com/way-jm) | [代码](https://github.com/pure-admin/vue-pure-admin/commits?author=way-jm) |
|
||||
| [simple-hui](https://github.com/simple-hui) | [代码](https://github.com/pure-admin/vue-pure-admin/commits?author=simple-hui) |
|
||||
|
||||
## `Git` 贡献提交规范
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { Plugin } from "vite";
|
||||
import gradient from "gradient-string";
|
||||
import { getPackageSize } from "./utils";
|
||||
import dayjs, { type Dayjs } from "dayjs";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import gradientString from "gradient-string";
|
||||
import boxen, { type Options as BoxenOptions } from "boxen";
|
||||
dayjs.extend(duration);
|
||||
|
||||
const welcomeMessage = gradientString("cyan", "magenta").multiline(
|
||||
`您好! 欢迎使用 pure-admin 开源项目\n我们为您精心准备了下面两个贴心的保姆级文档\nhttps://pure-admin.github.io/pure-admin-doc\nhttps://pure-admin-utils.netlify.app`
|
||||
const welcomeMessage = gradient(["cyan", "magenta"]).multiline(
|
||||
`您好! 欢迎使用 pure-admin 开源项目\n我们为您精心准备了下面两个贴心的保姆级文档\nhttps://pure-admin.cn\nhttps://pure-admin-utils.netlify.app`
|
||||
);
|
||||
|
||||
const boxenOptions: BoxenOptions = {
|
||||
@@ -41,7 +41,7 @@ export function viteBuildInfo(): Plugin {
|
||||
callback: (size: string) => {
|
||||
console.log(
|
||||
boxen(
|
||||
gradientString("cyan", "magenta").multiline(
|
||||
gradient(["cyan", "magenta"]).multiline(
|
||||
`🎉 恭喜打包完成(总用时${dayjs
|
||||
.duration(endTime.diff(startTime))
|
||||
.format("mm分ss秒")},打包后的大小为${size})`
|
||||
|
||||
@@ -54,10 +54,6 @@ const include = [
|
||||
* 在预构建中强制排除的依赖项
|
||||
* 温馨提示:所有以 `@iconify-icons/` 开头引入的的本地图标模块,都应该加入到下面的 `exclude` 里,因为平台推荐的使用方式是哪里需要哪里引入而且都是单个的引入,不需要预构建,直接让浏览器加载就好
|
||||
*/
|
||||
const exclude = [
|
||||
"@iconify-icons/ep",
|
||||
"@iconify-icons/ri",
|
||||
"@pureadmin/theme/dist/browser-utils"
|
||||
];
|
||||
const exclude = ["@iconify-icons/ep", "@iconify-icons/ri"];
|
||||
|
||||
export { include, exclude };
|
||||
|
||||
@@ -9,9 +9,8 @@ import { configCompressPlugin } from "./compress";
|
||||
import removeNoMatch from "vite-plugin-router-warn";
|
||||
import { visualizer } from "rollup-plugin-visualizer";
|
||||
import removeConsole from "vite-plugin-remove-console";
|
||||
import { themePreprocessorPlugin } from "@pureadmin/theme";
|
||||
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
|
||||
import { genScssMultipleScopeVars } from "../src/layout/theme";
|
||||
import { codeInspectorPlugin } from "code-inspector-plugin";
|
||||
import { vitePluginFakeServer } from "vite-plugin-fake-server";
|
||||
|
||||
export function getPluginsList(
|
||||
@@ -24,9 +23,18 @@ export function getPluginsList(
|
||||
// jsx、tsx语法支持
|
||||
vueJsx(),
|
||||
VueI18nPlugin({
|
||||
jitCompilation: false,
|
||||
include: [pathResolve("../locales/**")]
|
||||
}),
|
||||
/**
|
||||
* 在页面上按住组合键时,鼠标在页面移动即会在 DOM 上出现遮罩层并显示相关信息,点击一下将自动打开 IDE 并将光标定位到元素对应的代码位置
|
||||
* Mac 默认组合键 Option + Shift
|
||||
* Windows 默认组合键 Alt + Shift
|
||||
* 更多用法看 https://inspector.fe-dev.cn/guide/start.html
|
||||
*/
|
||||
codeInspectorPlugin({
|
||||
bundler: "vite",
|
||||
hideConsole: true
|
||||
}),
|
||||
viteBuildInfo(),
|
||||
/**
|
||||
* 开发环境下移除非必要的vue-router动态路由警告No match found for location with path
|
||||
@@ -41,13 +49,6 @@ export function getPluginsList(
|
||||
infixName: false,
|
||||
enableProd: true
|
||||
}),
|
||||
// 自定义主题
|
||||
themePreprocessorPlugin({
|
||||
scss: {
|
||||
multipleScopeVars: genScssMultipleScopeVars(),
|
||||
extract: true
|
||||
}
|
||||
}),
|
||||
// svg组件化支持
|
||||
svgLoader(),
|
||||
VITE_CDN ? cdn : null,
|
||||
|
||||
@@ -78,7 +78,8 @@ export default defineFlatConfig([
|
||||
languageOptions: {
|
||||
parser: parserTypeScript,
|
||||
parserOptions: {
|
||||
sourceType: "module"
|
||||
sourceType: "module",
|
||||
warnOnUnsupportedTypeScriptVersion: false
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
@@ -93,6 +94,8 @@ export default defineFlatConfig([
|
||||
"@typescript-eslint/prefer-as-const": "warn",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-unused-expressions": "off",
|
||||
"@typescript-eslint/no-unsafe-function-type": "off",
|
||||
"@typescript-eslint/no-import-type-side-effects": "error",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
|
||||
@@ -53,6 +53,8 @@ panel:
|
||||
pureTagsStyleSmartTip: Smart tags add fun and brilliance
|
||||
pureTagsStyleCard: Card
|
||||
pureTagsStyleCardTip: Card tags for efficient browsing
|
||||
pureTagsStyleChrome: Chrome
|
||||
pureTagsStyleChromeTip: Chrome style is classic and elegant
|
||||
pureInterfaceDisplay: Interface Display
|
||||
pureGreyModel: Grey Model
|
||||
pureWeakModel: Weak Model
|
||||
@@ -81,6 +83,7 @@ menus:
|
||||
pureFive: "500"
|
||||
pureComponents: Components
|
||||
pureDialog: Dialog
|
||||
pureDrawer: Drawer
|
||||
pureMessage: Message Tips
|
||||
pureVideo: Video
|
||||
pureSegmented: Segmented
|
||||
@@ -123,6 +126,8 @@ menus:
|
||||
purePermission: Permission Manage
|
||||
purePermissionPage: Page Permission
|
||||
purePermissionButton: Button Permission
|
||||
purePermissionButtonRouter: Route return button permission
|
||||
purePermissionButtonLogin: Login interface return button permission
|
||||
pureTabs: Tabs Operate
|
||||
pureGuide: Guide
|
||||
pureAble: Able
|
||||
|
||||
@@ -53,6 +53,8 @@ panel:
|
||||
pureTagsStyleSmartTip: 灵动标签,添趣生辉
|
||||
pureTagsStyleCard: 卡片
|
||||
pureTagsStyleCardTip: 卡片标签,高效浏览
|
||||
pureTagsStyleChrome: 谷歌
|
||||
pureTagsStyleChromeTip: 谷歌风格,经典美观
|
||||
pureInterfaceDisplay: 界面显示
|
||||
pureGreyModel: 灰色模式
|
||||
pureWeakModel: 色弱模式
|
||||
@@ -81,6 +83,7 @@ menus:
|
||||
pureFive: "500"
|
||||
pureComponents: 组件
|
||||
pureDialog: 函数式弹框
|
||||
pureDrawer: 函数式抽屉
|
||||
pureMessage: 消息提示
|
||||
pureVideo: 视频
|
||||
pureSegmented: 分段控制器
|
||||
@@ -119,10 +122,12 @@ menus:
|
||||
pureMenu1-2-1: 菜单1-2-1
|
||||
pureMenu1-2-2: 菜单1-2-2
|
||||
pureMenu1-3: 菜单1-3
|
||||
pureMenu2: 菜单2
|
||||
pureMenu2: 菜单二
|
||||
purePermission: 权限管理
|
||||
purePermissionPage: 页面权限
|
||||
purePermissionButton: 按钮权限
|
||||
purePermissionButtonRouter: 路由返回按钮权限
|
||||
purePermissionButtonLogin: 登录接口返回按钮权限
|
||||
pureTabs: 标签页操作
|
||||
pureGuide: 引导页
|
||||
pureAble: 功能
|
||||
@@ -229,4 +234,4 @@ login:
|
||||
purePassWordRuleReg: 密码格式应为8-18位数字、字母、符号的任意两种组合
|
||||
purePassWordSureReg: 请输入确认密码
|
||||
purePassWordDifferentReg: 两次密码不一致!
|
||||
purePassWordUpdateReg: 修改密码成功
|
||||
purePassWordUpdateReg: 修改密码成功
|
||||
@@ -123,17 +123,34 @@ const permissionRouter = {
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/permission/button/index",
|
||||
name: "PermissionButton",
|
||||
path: "/permission/button",
|
||||
meta: {
|
||||
title: "menus.purePermissionButton",
|
||||
roles: ["admin", "common"],
|
||||
auths: [
|
||||
"permission:btn:add",
|
||||
"permission:btn:edit",
|
||||
"permission:btn:delete"
|
||||
]
|
||||
}
|
||||
roles: ["admin", "common"]
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/permission/button/router",
|
||||
component: "permission/button/index",
|
||||
name: "PermissionButtonRouter",
|
||||
meta: {
|
||||
title: "menus.purePermissionButtonRouter",
|
||||
auths: [
|
||||
"permission:btn:add",
|
||||
"permission:btn:edit",
|
||||
"permission:btn:delete"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/permission/button/login",
|
||||
component: "permission/button/perms",
|
||||
name: "PermissionButtonLogin",
|
||||
meta: {
|
||||
title: "menus.purePermissionButtonLogin"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -146,30 +163,6 @@ const frameRouter = {
|
||||
rank: frame
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/iframe/external",
|
||||
meta: {
|
||||
title: "menus.pureExternalDoc"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/external",
|
||||
name: "https://pure-admin.github.io/pure-admin-doc",
|
||||
meta: {
|
||||
title: "menus.pureExternalLink",
|
||||
roles: ["admin", "common"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/pureUtilsLink",
|
||||
name: "https://pure-admin-utils.netlify.app/",
|
||||
meta: {
|
||||
title: "menus.pureUtilsLink",
|
||||
roles: ["admin", "common"]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/iframe/embedded",
|
||||
meta: {
|
||||
@@ -257,6 +250,30 @@ const frameRouter = {
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/iframe/external",
|
||||
meta: {
|
||||
title: "menus.pureExternalDoc"
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/external",
|
||||
name: "https://pure-admin.cn/",
|
||||
meta: {
|
||||
title: "menus.pureExternalLink",
|
||||
roles: ["admin", "common"]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/pureUtilsLink",
|
||||
name: "https://pure-admin-utils.netlify.app/",
|
||||
meta: {
|
||||
title: "menus.pureUtilsLink",
|
||||
roles: ["admin", "common"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -15,6 +15,8 @@ export default defineFakeRoute([
|
||||
nickname: "小铭",
|
||||
// 一个用户可能有多个角色
|
||||
roles: ["admin"],
|
||||
// 按钮级别权限
|
||||
permissions: ["*:*:*"],
|
||||
accessToken: "eyJhbGciOiJIUzUxMiJ9.admin",
|
||||
refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh",
|
||||
expires: "2030/10/30 00:00:00"
|
||||
@@ -28,6 +30,7 @@ export default defineFakeRoute([
|
||||
username: "common",
|
||||
nickname: "小林",
|
||||
roles: ["common"],
|
||||
permissions: ["permission:btn:add", "permission:btn:edit"],
|
||||
accessToken: "eyJhbGciOiJIUzUxMiJ9.common",
|
||||
refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh",
|
||||
expires: "2030/10/30 00:00:00"
|
||||
|
||||
130
mock/system.ts
130
mock/system.ts
@@ -430,7 +430,7 @@ export default defineFakeRoute([
|
||||
id: 102,
|
||||
menuType: 2,
|
||||
title: "menus.pureExternalLink",
|
||||
name: "https://pure-admin.github.io/pure-admin-doc",
|
||||
name: "https://pure-admin.cn/",
|
||||
path: "/external",
|
||||
component: "",
|
||||
rank: null,
|
||||
@@ -696,7 +696,7 @@ export default defineFakeRoute([
|
||||
menuType: 0,
|
||||
title: "menus.purePermissionButton",
|
||||
name: "PermissionButton",
|
||||
path: "/permission/button/index",
|
||||
path: "/permission/button",
|
||||
component: "",
|
||||
rank: null,
|
||||
redirect: "",
|
||||
@@ -717,6 +717,30 @@ export default defineFakeRoute([
|
||||
{
|
||||
parentId: 202,
|
||||
id: 203,
|
||||
menuType: 0,
|
||||
title: "menus.purePermissionButtonRouter",
|
||||
name: "PermissionButtonRouter",
|
||||
path: "/permission/button/router",
|
||||
component: "permission/button/index",
|
||||
rank: null,
|
||||
redirect: "",
|
||||
icon: "",
|
||||
extraIcon: "",
|
||||
enterTransition: "",
|
||||
leaveTransition: "",
|
||||
activePath: "",
|
||||
auths: "",
|
||||
frameSrc: "",
|
||||
frameLoading: true,
|
||||
keepAlive: false,
|
||||
hiddenTag: false,
|
||||
fixedTag: false,
|
||||
showLink: true,
|
||||
showParent: false
|
||||
},
|
||||
{
|
||||
parentId: 203,
|
||||
id: 210,
|
||||
menuType: 3,
|
||||
title: "添加",
|
||||
name: "",
|
||||
@@ -739,8 +763,8 @@ export default defineFakeRoute([
|
||||
showParent: false
|
||||
},
|
||||
{
|
||||
parentId: 202,
|
||||
id: 204,
|
||||
parentId: 203,
|
||||
id: 211,
|
||||
menuType: 3,
|
||||
title: "修改",
|
||||
name: "",
|
||||
@@ -762,9 +786,105 @@ export default defineFakeRoute([
|
||||
showLink: true,
|
||||
showParent: false
|
||||
},
|
||||
{
|
||||
parentId: 203,
|
||||
id: 212,
|
||||
menuType: 3,
|
||||
title: "删除",
|
||||
name: "",
|
||||
path: "",
|
||||
component: "",
|
||||
rank: null,
|
||||
redirect: "",
|
||||
icon: "",
|
||||
extraIcon: "",
|
||||
enterTransition: "",
|
||||
leaveTransition: "",
|
||||
activePath: "",
|
||||
auths: "permission:btn:delete",
|
||||
frameSrc: "",
|
||||
frameLoading: true,
|
||||
keepAlive: false,
|
||||
hiddenTag: false,
|
||||
fixedTag: false,
|
||||
showLink: true,
|
||||
showParent: false
|
||||
},
|
||||
{
|
||||
parentId: 202,
|
||||
id: 205,
|
||||
id: 204,
|
||||
menuType: 0,
|
||||
title: "menus.purePermissionButtonLogin",
|
||||
name: "PermissionButtonLogin",
|
||||
path: "/permission/button/login",
|
||||
component: "permission/button/perms",
|
||||
rank: null,
|
||||
redirect: "",
|
||||
icon: "",
|
||||
extraIcon: "",
|
||||
enterTransition: "",
|
||||
leaveTransition: "",
|
||||
activePath: "",
|
||||
auths: "",
|
||||
frameSrc: "",
|
||||
frameLoading: true,
|
||||
keepAlive: false,
|
||||
hiddenTag: false,
|
||||
fixedTag: false,
|
||||
showLink: true,
|
||||
showParent: false
|
||||
},
|
||||
{
|
||||
parentId: 204,
|
||||
id: 220,
|
||||
menuType: 3,
|
||||
title: "添加",
|
||||
name: "",
|
||||
path: "",
|
||||
component: "",
|
||||
rank: null,
|
||||
redirect: "",
|
||||
icon: "",
|
||||
extraIcon: "",
|
||||
enterTransition: "",
|
||||
leaveTransition: "",
|
||||
activePath: "",
|
||||
auths: "permission:btn:add",
|
||||
frameSrc: "",
|
||||
frameLoading: true,
|
||||
keepAlive: false,
|
||||
hiddenTag: false,
|
||||
fixedTag: false,
|
||||
showLink: true,
|
||||
showParent: false
|
||||
},
|
||||
{
|
||||
parentId: 204,
|
||||
id: 221,
|
||||
menuType: 3,
|
||||
title: "修改",
|
||||
name: "",
|
||||
path: "",
|
||||
component: "",
|
||||
rank: null,
|
||||
redirect: "",
|
||||
icon: "",
|
||||
extraIcon: "",
|
||||
enterTransition: "",
|
||||
leaveTransition: "",
|
||||
activePath: "",
|
||||
auths: "permission:btn:edit",
|
||||
frameSrc: "",
|
||||
frameLoading: true,
|
||||
keepAlive: false,
|
||||
hiddenTag: false,
|
||||
fixedTag: false,
|
||||
showLink: true,
|
||||
showParent: false
|
||||
},
|
||||
{
|
||||
parentId: 204,
|
||||
id: 222,
|
||||
menuType: 3,
|
||||
title: "删除",
|
||||
name: "",
|
||||
|
||||
167
package.json
167
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vue-pure-admin",
|
||||
"version": "5.5.0",
|
||||
"version": "5.9.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -48,28 +48,28 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@howdyjs/mouse-menu": "^2.1.3",
|
||||
"@howdyjs/mouse-menu": "^2.1.6",
|
||||
"@infectoone/vue-ganttastic": "^2.3.2",
|
||||
"@logicflow/core": "^1.2.27",
|
||||
"@logicflow/extension": "^1.2.27",
|
||||
"@logicflow/core": "^1.2.28",
|
||||
"@logicflow/extension": "^1.2.28",
|
||||
"@pureadmin/descriptions": "^1.2.1",
|
||||
"@pureadmin/table": "^3.1.2",
|
||||
"@pureadmin/utils": "^2.4.7",
|
||||
"@vue-flow/background": "^1.3.0",
|
||||
"@vue-flow/core": "^1.33.6",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"@vueuse/motion": "^2.1.0",
|
||||
"@pureadmin/table": "^3.2.1",
|
||||
"@pureadmin/utils": "^2.5.0",
|
||||
"@vue-flow/background": "^1.3.2",
|
||||
"@vue-flow/core": "^1.41.6",
|
||||
"@vueuse/core": "^12.0.0",
|
||||
"@vueuse/motion": "^2.2.6",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.6.8",
|
||||
"axios": "^1.7.9",
|
||||
"china-area-data": "^5.0.1",
|
||||
"cropperjs": "^1.6.2",
|
||||
"dayjs": "^1.11.11",
|
||||
"echarts": "^5.5.0",
|
||||
"el-table-infinite-scroll": "^3.0.3",
|
||||
"element-plus": "2.7.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"el-table-infinite-scroll": "^3.0.6",
|
||||
"element-plus": "^2.9.0",
|
||||
"intro.js": "^7.2.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsbarcode": "^3.11.6",
|
||||
@@ -78,110 +78,115 @@
|
||||
"mitt": "^3.0.1",
|
||||
"mqtt": "4.3.7",
|
||||
"nprogress": "^0.2.0",
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^2.1.7",
|
||||
"pinyin-pro": "^3.20.3",
|
||||
"plus-pro-components": "^0.0.11",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.12.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"pinia": "^2.3.0",
|
||||
"pinyin-pro": "^3.26.0",
|
||||
"plus-pro-components": "^0.1.18",
|
||||
"qrcode": "^1.5.4",
|
||||
"qs": "^6.13.1",
|
||||
"responsive-storage": "^2.2.0",
|
||||
"sortablejs": "^1.15.2",
|
||||
"swiper": "^11.1.1",
|
||||
"typeit": "^8.8.3",
|
||||
"sortablejs": "^1.15.6",
|
||||
"swiper": "^11.1.15",
|
||||
"typeit": "^8.8.7",
|
||||
"v-contextmenu": "^3.2.0",
|
||||
"v3-infinite-loading": "^1.3.1",
|
||||
"version-rocket": "^1.7.1",
|
||||
"vue": "^3.4.26",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"v3-infinite-loading": "^1.3.2",
|
||||
"version-rocket": "^1.7.4",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^10.0.5",
|
||||
"vue-json-pretty": "^2.4.0",
|
||||
"vue-pdf-embed": "^2.0.3",
|
||||
"vue-router": "^4.3.2",
|
||||
"vue-tippy": "^6.4.1",
|
||||
"vue-types": "^5.1.2",
|
||||
"vue-pdf-embed": "^2.1.1",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-tippy": "^6.5.0",
|
||||
"vue-types": "^5.1.3",
|
||||
"vue-virtual-scroller": "2.0.0-beta.8",
|
||||
"vue-waterfall-plugin-next": "^2.4.3",
|
||||
"vue3-danmaku": "^1.6.0",
|
||||
"vue-waterfall-plugin-next": "^2.6.4",
|
||||
"vue3-danmaku": "^1.6.1",
|
||||
"vue3-puzzle-vcode": "^1.1.7",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vxe-table": "^4.6.9",
|
||||
"wavesurfer.js": "^7.7.13",
|
||||
"xgplayer": "^3.0.17",
|
||||
"vxe-table": "4.6.25",
|
||||
"wavesurfer.js": "^7.8.10",
|
||||
"xgplayer": "^3.0.20",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^19.3.0",
|
||||
"@commitlint/config-conventional": "^19.2.2",
|
||||
"@commitlint/types": "^19.0.3",
|
||||
"@eslint/js": "^9.2.0",
|
||||
"@faker-js/faker": "^8.4.1",
|
||||
"@commitlint/cli": "^19.6.0",
|
||||
"@commitlint/config-conventional": "^19.6.0",
|
||||
"@commitlint/types": "^19.5.0",
|
||||
"@eslint/js": "^9.16.0",
|
||||
"@faker-js/faker": "^9.3.0",
|
||||
"@iconify-icons/ep": "^1.2.12",
|
||||
"@iconify-icons/ri": "^1.2.10",
|
||||
"@iconify/vue": "^4.1.2",
|
||||
"@intlify/unplugin-vue-i18n": "^4.0.0",
|
||||
"@pureadmin/theme": "^3.2.0",
|
||||
"@iconify/vue": "^4.2.0",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.1",
|
||||
"@types/dagre": "^0.7.52",
|
||||
"@types/gradient-string": "^1.1.6",
|
||||
"@types/intro.js": "^5.1.5",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^20.12.8",
|
||||
"@types/node": "^20.17.9",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/path-browserify": "^1.0.3",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/qs": "^6.9.15",
|
||||
"@types/qs": "^6.9.17",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"boxen": "^7.1.1",
|
||||
"cssnano": "^7.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.0",
|
||||
"@typescript-eslint/parser": "^8.18.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"boxen": "^8.0.1",
|
||||
"code-inspector-plugin": "^0.18.2",
|
||||
"cssnano": "^7.0.6",
|
||||
"dagre": "^0.8.5",
|
||||
"eslint": "^9.2.0",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-define-config": "^2.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-vue": "^9.25.0",
|
||||
"gradient-string": "^2.0.2",
|
||||
"husky": "^9.0.11",
|
||||
"lint-staged": "^15.2.2",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-html": "^1.6.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"gradient-string": "^3.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^15.2.10",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-html": "^1.7.0",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"prettier": "^3.2.5",
|
||||
"rimraf": "^5.0.5",
|
||||
"prettier": "^3.4.2",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.76.0",
|
||||
"stylelint": "^16.5.0",
|
||||
"stylelint-config-recess-order": "^5.0.1",
|
||||
"sass": "^1.82.0",
|
||||
"stylelint": "^16.11.0",
|
||||
"stylelint-config-recess-order": "^5.1.1",
|
||||
"stylelint-config-recommended-vue": "^1.5.0",
|
||||
"stylelint-config-standard-scss": "^13.1.0",
|
||||
"stylelint-prettier": "^5.0.0",
|
||||
"svgo": "^3.2.0",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.11",
|
||||
"vite-plugin-cdn-import": "^0.3.5",
|
||||
"stylelint-prettier": "^5.0.2",
|
||||
"svgo": "^3.3.2",
|
||||
"tailwindcss": "^3.4.16",
|
||||
"typescript": "5.6.3",
|
||||
"vite": "^6.0.3",
|
||||
"vite-plugin-cdn-import": "^1.0.1",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-fake-server": "^2.1.1",
|
||||
"vite-plugin-fake-server": "^2.1.4",
|
||||
"vite-plugin-remove-console": "^2.2.0",
|
||||
"vite-plugin-router-warn": "^1.0.0",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vue-eslint-parser": "^9.4.2",
|
||||
"vue-tsc": "^1.8.27"
|
||||
"vue-eslint-parser": "^9.4.3",
|
||||
"vue-tsc": "^2.1.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0",
|
||||
"pnpm": ">=8.6.10"
|
||||
"node": "^18.18.0 || ^20.9.0 || >=22.0.0",
|
||||
"pnpm": ">=9"
|
||||
},
|
||||
"packageManager": "pnpm@8.6.10",
|
||||
"pnpm": {
|
||||
"allowedDeprecatedVersions": {
|
||||
"are-we-there-yet": "*",
|
||||
"sourcemap-codec": "*",
|
||||
"domexception": "*",
|
||||
"w3c-hr-time": "*",
|
||||
"inflight": "*",
|
||||
"npmlog": "*",
|
||||
"rimraf": "*",
|
||||
"stable": "*",
|
||||
"abab": "*"
|
||||
"gauge": "*",
|
||||
"abab": "*",
|
||||
"glob": "*"
|
||||
},
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
|
||||
14660
pnpm-lock.yaml
generated
14660
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Version": "5.5.0",
|
||||
"Version": "5.9.0",
|
||||
"Title": "PureAdmin",
|
||||
"FixedHeader": true,
|
||||
"HiddenSideBar": false,
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 80 KiB |
@@ -2,6 +2,7 @@
|
||||
<el-config-provider :locale="currentLocale">
|
||||
<router-view />
|
||||
<ReDialog />
|
||||
<ReDrawer />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
@@ -10,6 +11,7 @@ import { defineComponent } from "vue";
|
||||
import { checkVersion } from "version-rocket";
|
||||
import { ElConfigProvider } from "element-plus";
|
||||
import { ReDialog } from "@/components/ReDialog";
|
||||
import { ReDrawer } from "@/components/ReDrawer";
|
||||
import en from "element-plus/es/locale/lang/en";
|
||||
import zhCn from "element-plus/es/locale/lang/zh-cn";
|
||||
import plusEn from "plus-pro-components/es/locale/lang/en";
|
||||
@@ -19,7 +21,8 @@ export default defineComponent({
|
||||
name: "app",
|
||||
components: {
|
||||
[ElConfigProvider.name]: ElConfigProvider,
|
||||
ReDialog
|
||||
ReDialog,
|
||||
ReDrawer
|
||||
},
|
||||
computed: {
|
||||
currentLocale() {
|
||||
|
||||
@@ -11,6 +11,8 @@ export type UserResult = {
|
||||
nickname: string;
|
||||
/** 当前登录用户的角色 */
|
||||
roles: Array<string>;
|
||||
/** 按钮级别权限 */
|
||||
permissions: Array<string>;
|
||||
/** `token` */
|
||||
accessToken: string;
|
||||
/** 用于调用刷新`accessToken`的接口时所需的`token` */
|
||||
|
||||
@@ -15,6 +15,7 @@ defineOptions({
|
||||
name: "ReDialog"
|
||||
});
|
||||
|
||||
const sureBtnMap = ref({});
|
||||
const fullscreen = ref(false);
|
||||
|
||||
const footerButtons = computed(() => {
|
||||
@@ -43,10 +44,26 @@ const footerButtons = computed(() => {
|
||||
bg: true,
|
||||
popconfirm: options?.popconfirm,
|
||||
btnClick: ({ dialog: { options, index } }) => {
|
||||
const done = () =>
|
||||
if (options?.sureBtnLoading) {
|
||||
sureBtnMap.value[index] = Object.assign(
|
||||
{},
|
||||
sureBtnMap.value[index],
|
||||
{
|
||||
loading: true
|
||||
}
|
||||
);
|
||||
}
|
||||
const closeLoading = () => {
|
||||
if (options?.sureBtnLoading) {
|
||||
sureBtnMap.value[index].loading = false;
|
||||
}
|
||||
};
|
||||
const done = () => {
|
||||
closeLoading();
|
||||
closeDialog(options, index, { command: "sure" });
|
||||
};
|
||||
if (options?.beforeSure && isFunction(options?.beforeSure)) {
|
||||
options.beforeSure(done, { options, index });
|
||||
options.beforeSure(done, { options, index, closeLoading });
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
@@ -172,6 +189,7 @@ function handleClose(
|
||||
<el-button
|
||||
v-else
|
||||
v-bind="btn"
|
||||
:loading="key === 1 && sureBtnMap[index]?.loading"
|
||||
@click="
|
||||
btn.btnClick({
|
||||
dialog: { options, index },
|
||||
|
||||
@@ -69,11 +69,11 @@ type DialogProps = {
|
||||
type Popconfirm = {
|
||||
/** 标题 */
|
||||
title?: string;
|
||||
/** 确认按钮文字 */
|
||||
/** 确定按钮文字 */
|
||||
confirmButtonText?: string;
|
||||
/** 取消按钮文字 */
|
||||
cancelButtonText?: string;
|
||||
/** 确认按钮类型,默认 `primary` */
|
||||
/** 确定按钮类型,默认 `primary` */
|
||||
confirmButtonType?: ButtonType;
|
||||
/** 取消按钮类型,默认 `text` */
|
||||
cancelButtonType?: ButtonType;
|
||||
@@ -121,7 +121,7 @@ type ButtonProps = {
|
||||
round?: boolean;
|
||||
/** 是否为圆形按钮,默认 `false` */
|
||||
circle?: boolean;
|
||||
/** 确认按钮的 `Popconfirm` 气泡确认框相关配置 */
|
||||
/** 确定按钮的 `Popconfirm` 气泡确认框相关配置 */
|
||||
popconfirm?: Popconfirm;
|
||||
/** 是否为加载中状态,默认 `false` */
|
||||
loading?: boolean;
|
||||
@@ -160,8 +160,10 @@ interface DialogOptions extends DialogProps {
|
||||
props?: any;
|
||||
/** 是否隐藏 `Dialog` 按钮操作区的内容 */
|
||||
hideFooter?: boolean;
|
||||
/** 确认按钮的 `Popconfirm` 气泡确认框相关配置 */
|
||||
/** 确定按钮的 `Popconfirm` 气泡确认框相关配置 */
|
||||
popconfirm?: Popconfirm;
|
||||
/** 点击确定按钮后是否开启 `loading` 加载动画 */
|
||||
sureBtnLoading?: boolean;
|
||||
/**
|
||||
* @description 自定义对话框标题的内容渲染器
|
||||
* @see {@link https://element-plus.org/zh-CN/component/dialog.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%A4%B4%E9%83%A8}
|
||||
@@ -259,10 +261,13 @@ interface DialogOptions extends DialogProps {
|
||||
done: Function,
|
||||
{
|
||||
options,
|
||||
index
|
||||
index,
|
||||
closeLoading
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
/** 关闭确定按钮的 `loading` 加载动画 */
|
||||
closeLoading: Function;
|
||||
}
|
||||
) => void;
|
||||
}
|
||||
|
||||
64
src/components/ReDrawer/index.ts
Normal file
64
src/components/ReDrawer/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { ref } from "vue";
|
||||
import reDrawer from "./index.vue";
|
||||
import { useTimeoutFn } from "@vueuse/core";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
import type {
|
||||
EventType,
|
||||
ArgsType,
|
||||
DrawerProps,
|
||||
DrawerOptions,
|
||||
ButtonProps
|
||||
} from "./type";
|
||||
|
||||
const drawerStore = ref<Array<DrawerOptions>>([]);
|
||||
|
||||
/** 打开抽屉 */
|
||||
const addDrawer = (options: DrawerOptions) => {
|
||||
const open = () =>
|
||||
drawerStore.value.push(Object.assign(options, { visible: true }));
|
||||
if (options?.openDelay) {
|
||||
useTimeoutFn(() => {
|
||||
open();
|
||||
}, options.openDelay);
|
||||
} else {
|
||||
open();
|
||||
}
|
||||
};
|
||||
|
||||
/** 关闭抽屉 */
|
||||
const closeDrawer = (options: DrawerOptions, index: number, args?: any) => {
|
||||
drawerStore.value[index].visible = false;
|
||||
options.closeCallBack && options.closeCallBack({ options, index, args });
|
||||
|
||||
const closeDelay = options?.closeDelay ?? 200;
|
||||
useTimeoutFn(() => {
|
||||
drawerStore.value.splice(index, 1);
|
||||
}, closeDelay);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 更改抽屉自身属性值
|
||||
* @param value 属性值
|
||||
* @param key 属性,默认`title`
|
||||
* @param index 弹框索引(默认`0`,代表只有一个弹框,对于嵌套弹框要改哪个弹框的属性值就把该弹框索引赋给`index`)
|
||||
*/
|
||||
const updateDrawer = (value: any, key = "title", index = 0) => {
|
||||
drawerStore.value[index][key] = value;
|
||||
};
|
||||
|
||||
/** 关闭所有弹框 */
|
||||
const closeAllDrawer = () => {
|
||||
drawerStore.value = [];
|
||||
};
|
||||
|
||||
const ReDrawer = withInstall(reDrawer);
|
||||
|
||||
export type { EventType, ArgsType, DrawerOptions, DrawerProps, ButtonProps };
|
||||
export {
|
||||
ReDrawer,
|
||||
drawerStore,
|
||||
addDrawer,
|
||||
closeDrawer,
|
||||
updateDrawer,
|
||||
closeAllDrawer
|
||||
};
|
||||
169
src/components/ReDrawer/index.vue
Normal file
169
src/components/ReDrawer/index.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
type EventType,
|
||||
type ButtonProps,
|
||||
type DrawerOptions,
|
||||
closeDrawer,
|
||||
drawerStore
|
||||
} from "./index";
|
||||
import { computed, ref } from "vue";
|
||||
import { isFunction } from "@pureadmin/utils";
|
||||
|
||||
defineOptions({
|
||||
name: "ReDrawer"
|
||||
});
|
||||
|
||||
const sureBtnMap = ref({});
|
||||
|
||||
const footerButtons = computed(() => {
|
||||
return (options: DrawerOptions) => {
|
||||
return options?.footerButtons?.length > 0
|
||||
? options.footerButtons
|
||||
: ([
|
||||
{
|
||||
label: "取消",
|
||||
text: true,
|
||||
bg: true,
|
||||
btnClick: ({ drawer: { options, index } }) => {
|
||||
const done = () =>
|
||||
closeDrawer(options, index, { command: "cancel" });
|
||||
if (options?.beforeCancel && isFunction(options?.beforeCancel)) {
|
||||
options.beforeCancel(done, { options, index });
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "确定",
|
||||
type: "primary",
|
||||
text: true,
|
||||
bg: true,
|
||||
popConfirm: options?.popConfirm,
|
||||
btnClick: ({ drawer: { options, index } }) => {
|
||||
if (options?.sureBtnLoading) {
|
||||
sureBtnMap.value[index] = Object.assign(
|
||||
{},
|
||||
sureBtnMap.value[index],
|
||||
{
|
||||
loading: true
|
||||
}
|
||||
);
|
||||
}
|
||||
const closeLoading = () => {
|
||||
if (options?.sureBtnLoading) {
|
||||
sureBtnMap.value[index].loading = false;
|
||||
}
|
||||
};
|
||||
const done = () => {
|
||||
closeLoading();
|
||||
closeDrawer(options, index, { command: "sure" });
|
||||
};
|
||||
if (options?.beforeSure && isFunction(options?.beforeSure)) {
|
||||
options.beforeSure(done, { options, index, closeLoading });
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
] as Array<ButtonProps>);
|
||||
};
|
||||
});
|
||||
|
||||
function eventsCallBack(
|
||||
event: EventType,
|
||||
options: DrawerOptions,
|
||||
index: number
|
||||
) {
|
||||
if (options?.[event] && isFunction(options?.[event])) {
|
||||
return options?.[event]({ options, index });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {DrawerOptions} options - 包含抽屉相关配置的对象
|
||||
* @param {number} index - 抽屉的索引
|
||||
* @param {Object} args - 传递给关闭抽屉操作的参数对象,默认为 { command: 'close' }
|
||||
* @returns {void} 这个函数不返回任何值
|
||||
*/
|
||||
function handleClose(
|
||||
options: DrawerOptions,
|
||||
index: number,
|
||||
args = { command: "close" }
|
||||
) {
|
||||
closeDrawer(options, index, args);
|
||||
eventsCallBack("close", options, index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-drawer
|
||||
v-for="(options, index) in drawerStore"
|
||||
:key="index"
|
||||
v-bind="options"
|
||||
v-model="options.visible"
|
||||
class="pure-drawer"
|
||||
:append-to-body="!!options?.appendToBody"
|
||||
:append-to="options?.appendTo ? options.appendTo : 'body'"
|
||||
:destroy-on-close="!!options?.destroyOnClose"
|
||||
:lock-scroll="!!options?.lockScroll"
|
||||
@closed="handleClose(options, index)"
|
||||
@opened="eventsCallBack('open', options, index)"
|
||||
@open-auto-focus="eventsCallBack('openAutoFocus', options, index)"
|
||||
@close-auto-focus="eventsCallBack('closeAutoFocus', options, index)"
|
||||
>
|
||||
<!-- header -->
|
||||
<template
|
||||
v-if="options?.headerRenderer"
|
||||
#header="{ close, titleId, titleClass }"
|
||||
>
|
||||
<component
|
||||
:is="options?.headerRenderer({ close, titleId, titleClass })"
|
||||
/>
|
||||
</template>
|
||||
<!-- body -->
|
||||
<component
|
||||
v-bind="options?.props"
|
||||
:is="options.contentRenderer({ options, index })"
|
||||
@close="args => handleClose(options, index, args)"
|
||||
/>
|
||||
<!-- footer -->
|
||||
<template v-if="!options?.hideFooter" #footer>
|
||||
<template v-if="options?.footerRenderer">
|
||||
<component :is="options?.footerRenderer({ options, index })" />
|
||||
</template>
|
||||
<span v-else>
|
||||
<template v-for="(btn, key) in footerButtons(options)" :key="key">
|
||||
<el-popconfirm
|
||||
v-if="btn.popConfirm"
|
||||
v-bind="btn.popConfirm"
|
||||
@confirm="
|
||||
btn.btnClick({
|
||||
drawer: { options, index },
|
||||
button: { btn, index: key }
|
||||
})
|
||||
"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button v-bind="btn">{{ btn?.label }}</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
<el-button
|
||||
v-else
|
||||
v-bind="btn"
|
||||
:loading="key === 1 && sureBtnMap[index]?.loading"
|
||||
@click="
|
||||
btn.btnClick({
|
||||
drawer: { options, index },
|
||||
button: { btn, index: key }
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ btn?.label }}
|
||||
</el-button>
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
262
src/components/ReDrawer/type.ts
Normal file
262
src/components/ReDrawer/type.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import type { CSSProperties, VNode, Component } from "vue";
|
||||
|
||||
type DoneFn = (cancel?: boolean) => void;
|
||||
type EventType = "open" | "close" | "openAutoFocus" | "closeAutoFocus";
|
||||
type ArgsType = {
|
||||
/** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了 `esc` 键 */
|
||||
command: "cancel" | "sure" | "close";
|
||||
};
|
||||
|
||||
type ButtonType =
|
||||
| "primary"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "danger"
|
||||
| "info"
|
||||
| "text";
|
||||
|
||||
type DrawerProps = {
|
||||
/** `Drawer` 的显示与隐藏 */
|
||||
visible?: boolean;
|
||||
/** `Drawer` 自身是否插入至 `body` 元素上。嵌套的 `Drawer` 必须指定该属性并赋值为 `true`,默认 `false` */
|
||||
appendToBody?: boolean;
|
||||
/** 挂载到哪个 `DOM` 元素 将覆盖 `appendToBody` */
|
||||
appendTo?: string;
|
||||
/** 是否在 `Drawer` 出现时将 `body` 滚动锁定,默认 `true` */
|
||||
lockScroll?: boolean;
|
||||
/** 关闭前的回调,会暂停 `Drawer` 的关闭 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
|
||||
beforeClose?: (done: DoneFn) => void;
|
||||
/** 是否可以通过点击 `modal` 关闭 `Drawer` ,默认 `true` */
|
||||
closeOnClickModal?: boolean;
|
||||
/** 是否可以通过按下 `ESC` 关闭 `Drawer` ,默认 `true` */
|
||||
closeOnPressEscape?: boolean;
|
||||
/** 是否显示关闭按钮,默认 `true` */
|
||||
showClose?: boolean;
|
||||
/** `Drawer` 打开的延时时间,单位毫秒,默认 `0` */
|
||||
openDelay?: number;
|
||||
/** `Drawer` 关闭的延时时间,单位毫秒,默认 `0` */
|
||||
closeDelay?: number;
|
||||
/** `Drawer` 自定义类名 */
|
||||
class?: string;
|
||||
/** `Drawer` 的自定义样式 */
|
||||
style?: CSSProperties;
|
||||
/** 控制是否在关闭 `Drawer` 之后将子元素全部销毁,默认 `false` */
|
||||
destroyOnClose?: boolean;
|
||||
/** 是否需要遮罩层,默认 `true` */
|
||||
modal?: boolean;
|
||||
/** `Drawer` 打开的方向,默认 `rtl` */
|
||||
direction?: "rtl" | "ltr" | "ttb" | "btt";
|
||||
/** `Drawer` 窗体的大小, 当使用 `number` 类型时, 以像素为单位, 当使用 `string` 类型时, 请传入 `'x%'`, 否则便会以 `number` 类型解释 */
|
||||
size?: string | number;
|
||||
/** `Drawer` 的标题 */
|
||||
title?: string;
|
||||
/** 控制是否显示 `header` 栏, 默认为 `true`, 当此项为 `false` 时, `title attribute` 和 `title slot` 均不生效 */
|
||||
withHeader?: boolean;
|
||||
/** 遮罩层的自定义类名 */
|
||||
modalClass?: string;
|
||||
/** 设置 `z-index` */
|
||||
zIndex?: number;
|
||||
/** `header` 的 `aria-level` 属性,默认 `2` */
|
||||
headerAriaLevel?: string;
|
||||
};
|
||||
|
||||
//element-plus.org/zh-CN/component/popConfirm.html#attributes
|
||||
type PopConfirm = {
|
||||
/** 标题 */
|
||||
title?: string;
|
||||
/** 确认按钮文字 */
|
||||
confirmButtonText?: string;
|
||||
/** 取消按钮文字 */
|
||||
cancelButtonText?: string;
|
||||
/** 确认按钮类型,默认 `primary` */
|
||||
confirmButtonType?: ButtonType;
|
||||
/** 取消按钮类型,默认 `text` */
|
||||
cancelButtonType?: ButtonType;
|
||||
/** 自定义图标,默认 `QuestionFilled` */
|
||||
icon?: string | Component;
|
||||
/** `Icon` 颜色,默认 `#f90` */
|
||||
iconColor?: string;
|
||||
/** 是否隐藏 `Icon`,默认 `false` */
|
||||
hideIcon?: boolean;
|
||||
/** 关闭时的延迟,默认 `200` */
|
||||
hideAfter?: number;
|
||||
/** 是否将 `popover` 的下拉列表插入至 `body` 元素,默认 `true` */
|
||||
teleported?: boolean;
|
||||
/** 当 `popover` 组件长时间不触发且 `persistent` 属性设置为 `false` 时, `popover` 将会被删除,默认 `false` */
|
||||
persistent?: boolean;
|
||||
/** 弹层宽度,最小宽度 `150px`,默认 `150` */
|
||||
width?: string | number;
|
||||
};
|
||||
|
||||
type BtnClickDrawer = {
|
||||
options?: DrawerOptions;
|
||||
index?: number;
|
||||
};
|
||||
type BtnClickButton = {
|
||||
btn?: ButtonProps;
|
||||
index?: number;
|
||||
};
|
||||
/** https://element-plus.org/zh-CN/component/button.html#button-attributes */
|
||||
type ButtonProps = {
|
||||
/** 按钮文字 */
|
||||
label: string;
|
||||
/** 按钮尺寸 */
|
||||
size?: "large" | "default" | "small";
|
||||
/** 按钮类型 */
|
||||
type?: "primary" | "success" | "warning" | "danger" | "info";
|
||||
/** 是否为朴素按钮,默认 `false` */
|
||||
plain?: boolean;
|
||||
/** 是否为文字按钮,默认 `false` */
|
||||
text?: boolean;
|
||||
/** 是否显示文字按钮背景颜色,默认 `false` */
|
||||
bg?: boolean;
|
||||
/** 是否为链接按钮,默认 `false` */
|
||||
link?: boolean;
|
||||
/** 是否为圆角按钮,默认 `false` */
|
||||
round?: boolean;
|
||||
/** 是否为圆形按钮,默认 `false` */
|
||||
circle?: boolean;
|
||||
/** 确认按钮的 `PopConfirm` 气泡确认框相关配置 */
|
||||
popConfirm?: PopConfirm;
|
||||
/** 是否为加载中状态,默认 `false` */
|
||||
loading?: boolean;
|
||||
/** 自定义加载中状态图标组件 */
|
||||
loadingIcon?: string | Component;
|
||||
/** 按钮是否为禁用状态,默认 `false` */
|
||||
disabled?: boolean;
|
||||
/** 图标组件 */
|
||||
icon?: string | Component;
|
||||
/** 是否开启原生 `autofocus` 属性,默认 `false` */
|
||||
autofocus?: boolean;
|
||||
/** 原生 `type` 属性,默认 `button` */
|
||||
nativeType?: "button" | "submit" | "reset";
|
||||
/** 自动在两个中文字符之间插入空格 */
|
||||
autoInsertSpace?: boolean;
|
||||
/** 自定义按钮颜色, 并自动计算 `hover` 和 `active` 触发后的颜色 */
|
||||
color?: string;
|
||||
/** `dark` 模式, 意味着自动设置 `color` 为 `dark` 模式的颜色,默认 `false` */
|
||||
dark?: boolean;
|
||||
/** 自定义元素标签 */
|
||||
tag?: string | Component;
|
||||
/** 点击按钮后触发的回调 */
|
||||
btnClick?: ({
|
||||
drawer,
|
||||
button
|
||||
}: {
|
||||
/** 当前 `Drawer` 信息 */
|
||||
drawer: BtnClickDrawer;
|
||||
/** 当前 `button` 信息 */
|
||||
button: BtnClickButton;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
interface DrawerOptions extends DrawerProps {
|
||||
/** 内容区组件的 `props`,可通过 `defineProps` 接收 */
|
||||
props?: any;
|
||||
/** 是否隐藏 `Drawer` 按钮操作区的内容 */
|
||||
hideFooter?: boolean;
|
||||
/** 确认按钮的 `PopConfirm` 气泡确认框相关配置 */
|
||||
popConfirm?: PopConfirm;
|
||||
/** 点击确定按钮后是否开启 `loading` 加载动画 */
|
||||
sureBtnLoading?: boolean;
|
||||
/**
|
||||
* @description 自定义抽屉标题的内容渲染器
|
||||
* @see {@link https://element-plus.org/zh-CN/component/drawer.html#%E6%8F%92%E6%A7%BD}
|
||||
*/
|
||||
headerRenderer?: ({
|
||||
close,
|
||||
titleId,
|
||||
titleClass
|
||||
}: {
|
||||
close: Function;
|
||||
titleId: string;
|
||||
titleClass: string;
|
||||
}) => VNode | Component;
|
||||
/** 自定义内容渲染器 */
|
||||
contentRenderer?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DrawerOptions;
|
||||
index: number;
|
||||
}) => VNode | Component;
|
||||
/** 自定义按钮操作区的内容渲染器,会覆盖`footerButtons`以及默认的 `取消` 和 `确定` 按钮 */
|
||||
footerRenderer?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DrawerOptions;
|
||||
index: number;
|
||||
}) => VNode | Component;
|
||||
/** 自定义底部按钮操作 */
|
||||
footerButtons?: Array<ButtonProps>;
|
||||
/** `Drawer` 打开后的回调 */
|
||||
open?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DrawerOptions;
|
||||
index: number;
|
||||
}) => void;
|
||||
/** `Drawer` 关闭后的回调(只有点击右上角关闭按钮或空白页或按下了esc键关闭页面时才会触发) */
|
||||
close?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DrawerOptions;
|
||||
index: number;
|
||||
}) => void;
|
||||
/** `Drawer` 关闭后的回调。 `args` 返回的 `command` 值解析:`cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或空白页或按下了esc键 */
|
||||
closeCallBack?: ({
|
||||
options,
|
||||
index,
|
||||
args
|
||||
}: {
|
||||
options: DrawerOptions;
|
||||
index: number;
|
||||
args: any;
|
||||
}) => void;
|
||||
/** 输入焦点聚焦在 `Drawer` 内容时的回调 */
|
||||
openAutoFocus?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DrawerOptions;
|
||||
index: number;
|
||||
}) => void;
|
||||
/** 输入焦点从 `Drawer` 内容失焦时的回调 */
|
||||
closeAutoFocus?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DrawerOptions;
|
||||
index: number;
|
||||
}) => void;
|
||||
|
||||
/** 点击底部取消按钮的回调,会暂停 `Drawer` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
|
||||
beforeCancel?: (
|
||||
done: Function,
|
||||
{
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DrawerOptions;
|
||||
index: number;
|
||||
}
|
||||
) => void;
|
||||
/** 点击底部确定按钮的回调,会暂停 `Drawer` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
|
||||
beforeSure?: (
|
||||
done: Function,
|
||||
{
|
||||
options,
|
||||
index,
|
||||
closeLoading
|
||||
}: {
|
||||
options: DrawerOptions;
|
||||
index: number;
|
||||
closeLoading: Function;
|
||||
}
|
||||
) => void;
|
||||
}
|
||||
|
||||
export type { ButtonProps, DrawerOptions, ArgsType, DrawerProps, EventType };
|
||||
@@ -194,7 +194,7 @@ watch(
|
||||
:pager-count="5"
|
||||
layout="pager"
|
||||
background
|
||||
small
|
||||
size="small"
|
||||
@current-change="onCurrentChange"
|
||||
/>
|
||||
<el-button
|
||||
|
||||
@@ -4,7 +4,7 @@ import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";
|
||||
|
||||
/**
|
||||
* 支持 `iconfont`、自定义 `svg` 以及 `iconify` 中所有的图标
|
||||
* @see 点击查看文档图标篇 {@link https://pure-admin.github.io/pure-admin-doc/pages/icon/}
|
||||
* @see 点击查看文档图标篇 {@link https://pure-admin.cn/pages/icon/}
|
||||
* @param icon 必传 图标
|
||||
* @param attrs 可选 iconType 属性
|
||||
* @returns Component
|
||||
|
||||
5
src/components/RePerms/index.ts
Normal file
5
src/components/RePerms/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import perms from "./src/perms";
|
||||
|
||||
const Perms = perms;
|
||||
|
||||
export { Perms };
|
||||
20
src/components/RePerms/src/perms.tsx
Normal file
20
src/components/RePerms/src/perms.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { defineComponent, Fragment } from "vue";
|
||||
import { hasPerms } from "@/utils/auth";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Perms",
|
||||
props: {
|
||||
value: {
|
||||
type: undefined,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
return () => {
|
||||
if (!slots) return null;
|
||||
return hasPerms(props.value) ? (
|
||||
<Fragment>{slots.default?.()}</Fragment>
|
||||
) : null;
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
getKeyList
|
||||
} from "@pureadmin/utils";
|
||||
|
||||
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
|
||||
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
|
||||
import DragIcon from "@/assets/table-bar/drag.svg?component";
|
||||
import ExpandIcon from "@/assets/table-bar/expand.svg?component";
|
||||
import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
|
||||
@@ -52,11 +54,12 @@ const props = {
|
||||
export default defineComponent({
|
||||
name: "PureTableBar",
|
||||
props,
|
||||
emits: ["refresh"],
|
||||
emits: ["refresh", "fullscreen"],
|
||||
setup(props, { emit, slots, attrs }) {
|
||||
const size = ref("default");
|
||||
const loading = ref(false);
|
||||
const checkAll = ref(true);
|
||||
const isFullscreen = ref(false);
|
||||
const isIndeterminate = ref(false);
|
||||
const instance = getCurrentInstance()!;
|
||||
const isExpandAll = ref(props.isExpandAll);
|
||||
@@ -114,6 +117,11 @@ export default defineComponent({
|
||||
toggleRowExpansionAll(props.tableRef.data, isExpandAll.value);
|
||||
}
|
||||
|
||||
function onFullscreen() {
|
||||
isFullscreen.value = !isFullscreen.value;
|
||||
emit("fullscreen", isFullscreen.value);
|
||||
}
|
||||
|
||||
function toggleRowExpansionAll(data, isExpansion) {
|
||||
data.forEach(item => {
|
||||
props.tableRef.toggleRowExpansion(item, isExpansion);
|
||||
@@ -244,7 +252,18 @@ export default defineComponent({
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<div {...attrs} class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
|
||||
<div
|
||||
{...attrs}
|
||||
class={[
|
||||
"w-[99/100]",
|
||||
"px-2",
|
||||
"pb-2",
|
||||
"bg-bg_color",
|
||||
isFullscreen.value
|
||||
? ["!w-full", "!h-full", "z-[2002]", "fixed", "inset-0"]
|
||||
: "mt-2"
|
||||
]}
|
||||
>
|
||||
<div class="flex justify-between w-full h-[60px] p-4">
|
||||
{slots?.title ? (
|
||||
slots.title()
|
||||
@@ -358,6 +377,14 @@ export default defineComponent({
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-popover>
|
||||
<el-divider direction="vertical" />
|
||||
|
||||
<iconifyIconOffline
|
||||
class={["w-[16px]", iconClass.value]}
|
||||
icon={isFullscreen.value ? ExitFullscreen : Fullscreen}
|
||||
v-tippy={isFullscreen.value ? "退出全屏" : "全屏"}
|
||||
onClick={() => onFullscreen()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{slots.default({
|
||||
|
||||
@@ -127,7 +127,9 @@ export default defineComponent({
|
||||
}
|
||||
);
|
||||
|
||||
watch(() => props.size, handleResizeInit);
|
||||
watch(() => props.size, handleResizeInit, {
|
||||
immediate: true
|
||||
});
|
||||
|
||||
const rendLabel = () => {
|
||||
return props.options.map((option, index) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface OptionsType {
|
||||
label?: string | (() => VNode | Component);
|
||||
/**
|
||||
* @description 图标,采用平台内置的 `useRenderIcon` 函数渲染
|
||||
* @see {@link 用法参考 https://pure-admin.github.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
|
||||
* @see {@link 用法参考 https://pure-admin.cn/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
|
||||
*/
|
||||
icon?: string | Component;
|
||||
/** 图标属性、样式配置 */
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
|
||||
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
|
||||
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
|
||||
import DragIcon from "@/assets/table-bar/drag.svg?component";
|
||||
import ExpandIcon from "@/assets/table-bar/expand.svg?component";
|
||||
import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
|
||||
@@ -50,11 +52,12 @@ const props = {
|
||||
export default defineComponent({
|
||||
name: "VxeTableBar",
|
||||
props,
|
||||
emits: ["refresh"],
|
||||
emits: ["refresh", "fullscreen"],
|
||||
setup(props, { emit, slots, attrs }) {
|
||||
const size = ref("small");
|
||||
const loading = ref(false);
|
||||
const checkAll = ref(true);
|
||||
const isFullscreen = ref(false);
|
||||
const isIndeterminate = ref(false);
|
||||
const instance = getCurrentInstance()!;
|
||||
const isExpandAll = ref(props.isExpandAll);
|
||||
@@ -110,6 +113,11 @@ export default defineComponent({
|
||||
props.vxeTableRef.refreshColumn();
|
||||
}
|
||||
|
||||
function onFullscreen() {
|
||||
isFullscreen.value = !isFullscreen.value;
|
||||
emit("fullscreen", isFullscreen.value);
|
||||
}
|
||||
|
||||
function reloadColumn() {
|
||||
const curCheckedColumns = cloneDeep(dynamicColumns.value).filter(item =>
|
||||
checkedColumns.value.includes(item.title)
|
||||
@@ -237,7 +245,18 @@ export default defineComponent({
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<div {...attrs} class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
|
||||
<div
|
||||
{...attrs}
|
||||
class={[
|
||||
"w-[99/100]",
|
||||
"px-2",
|
||||
"pb-2",
|
||||
"bg-bg_color",
|
||||
isFullscreen.value
|
||||
? ["!w-full", "!h-full", "z-[2002]", "fixed", "inset-0"]
|
||||
: "mt-2"
|
||||
]}
|
||||
>
|
||||
<div class="flex justify-between w-full h-[60px] p-4">
|
||||
{slots?.title ? (
|
||||
slots.title()
|
||||
@@ -349,6 +368,14 @@ export default defineComponent({
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-popover>
|
||||
<el-divider direction="vertical" />
|
||||
|
||||
<iconifyIconOffline
|
||||
class={["w-[16px]", iconClass.value]}
|
||||
icon={isFullscreen.value ? ExitFullscreen : Fullscreen}
|
||||
v-tippy={isFullscreen.value ? "退出全屏" : "全屏"}
|
||||
onClick={() => onFullscreen()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{slots.default({
|
||||
|
||||
@@ -2,7 +2,7 @@ import { hasAuth } from "@/router/utils";
|
||||
import type { Directive, DirectiveBinding } from "vue";
|
||||
|
||||
export const auth: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding<string | Array<string>>) {
|
||||
const { value } = binding;
|
||||
if (value) {
|
||||
!hasAuth(value) && el.parentNode?.removeChild(el);
|
||||
|
||||
@@ -3,13 +3,13 @@ import { useEventListener } from "@vueuse/core";
|
||||
import { copyTextToClipboard } from "@pureadmin/utils";
|
||||
import type { Directive, DirectiveBinding } from "vue";
|
||||
|
||||
interface CopyEl extends HTMLElement {
|
||||
export interface CopyEl extends HTMLElement {
|
||||
copyValue: string;
|
||||
}
|
||||
|
||||
/** 文本复制指令(默认双击复制) */
|
||||
export const copy: Directive = {
|
||||
mounted(el: CopyEl, binding: DirectiveBinding) {
|
||||
mounted(el: CopyEl, binding: DirectiveBinding<string>) {
|
||||
const { value } = binding;
|
||||
if (value) {
|
||||
el.copyValue = value;
|
||||
|
||||
@@ -2,4 +2,5 @@ export * from "./auth";
|
||||
export * from "./copy";
|
||||
export * from "./longpress";
|
||||
export * from "./optimize";
|
||||
export * from "./perms";
|
||||
export * from "./ripple";
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Directive, DirectiveBinding } from "vue";
|
||||
import { subBefore, subAfter, isFunction } from "@pureadmin/utils";
|
||||
|
||||
export const longpress: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding<Function>) {
|
||||
const cb = binding.value;
|
||||
if (cb && isFunction(cb)) {
|
||||
let timer = null;
|
||||
|
||||
@@ -8,9 +8,22 @@ import {
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
import type { Directive, DirectiveBinding } from "vue";
|
||||
|
||||
export interface OptimizeOptions {
|
||||
/** 事件名 */
|
||||
event: string;
|
||||
/** 事件触发的方法 */
|
||||
fn: (...params: any) => any;
|
||||
/** 是否立即执行 */
|
||||
immediate?: boolean;
|
||||
/** 防抖或节流的延迟时间(防抖默认:`200`毫秒、节流默认:`1000`毫秒) */
|
||||
timeout?: number;
|
||||
/** 传递的参数 */
|
||||
params?: any;
|
||||
}
|
||||
|
||||
/** 防抖(v-optimize或v-optimize:debounce)、节流(v-optimize:throttle)指令 */
|
||||
export const optimize: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding<OptimizeOptions>) {
|
||||
const { value } = binding;
|
||||
const optimizeType = binding.arg ?? "debounce";
|
||||
const type = ["debounce", "throttle"].find(t => t === optimizeType);
|
||||
|
||||
15
src/directives/perms/index.ts
Normal file
15
src/directives/perms/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { hasPerms } from "@/utils/auth";
|
||||
import type { Directive, DirectiveBinding } from "vue";
|
||||
|
||||
export const perms: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding<string | Array<string>>) {
|
||||
const { value } = binding;
|
||||
if (value) {
|
||||
!hasPerms(value) && el.parentNode?.removeChild(el);
|
||||
} else {
|
||||
throw new Error(
|
||||
"[Directive: perms]: need perms! Like v-perms=\"['btn.add','btn.edit']\""
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2,8 +2,10 @@ import "./index.scss";
|
||||
import { isObject } from "@pureadmin/utils";
|
||||
import type { Directive, DirectiveBinding } from "vue";
|
||||
|
||||
interface RippleOptions {
|
||||
export interface RippleOptions {
|
||||
/** 自定义`ripple`颜色,支持`tailwindcss` */
|
||||
class?: string;
|
||||
/** 是否从中心扩散 */
|
||||
center?: boolean;
|
||||
circle?: boolean;
|
||||
}
|
||||
@@ -220,13 +222,6 @@ function updated(el: HTMLElement, binding: RippleDirectiveBinding) {
|
||||
updateRipple(el, binding, wasEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 指令 v-ripple
|
||||
* @use 用法如下
|
||||
* 1. v-ripple 代表启用基本的 ripple 功能
|
||||
* 2. v-ripple="{ class: 'text-red' }" 代表自定义 ripple 颜色,支持 tailwindcss,生效样式是 color
|
||||
* 3. v-ripple.center 代表从中心扩散
|
||||
*/
|
||||
export const Ripple: Directive = {
|
||||
mounted,
|
||||
unmounted,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { useI18n } from "vue-i18n";
|
||||
import LayFrame from "../lay-frame/index.vue";
|
||||
import LayFooter from "../lay-footer/index.vue";
|
||||
import { useTags } from "@/layout/hooks/useTag";
|
||||
import { useGlobal, isNumber } from "@pureadmin/utils";
|
||||
import BackTopIcon from "@/assets/svg/back_top.svg?component";
|
||||
import { h, computed, Transition, defineComponent } from "vue";
|
||||
@@ -12,6 +13,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const { showModel } = useTags();
|
||||
const { $storage, $config } = useGlobal<GlobalPropertiesApi>();
|
||||
|
||||
const isKeepAlive = computed(() => {
|
||||
@@ -51,9 +53,17 @@ const getMainWidth = computed(() => {
|
||||
const getSectionStyle = computed(() => {
|
||||
return [
|
||||
hideTabs.value && layout ? "padding-top: 48px;" : "",
|
||||
!hideTabs.value && layout ? "padding-top: 81px;" : "",
|
||||
!hideTabs.value && layout
|
||||
? showModel.value == "chrome"
|
||||
? "padding-top: 85px;"
|
||||
: "padding-top: 81px;"
|
||||
: "",
|
||||
hideTabs.value && !layout.value ? "padding-top: 48px;" : "",
|
||||
!hideTabs.value && !layout.value ? "padding-top: 81px;" : "",
|
||||
!hideTabs.value && !layout.value
|
||||
? showModel.value == "chrome"
|
||||
? "padding-top: 85px;"
|
||||
: "padding-top: 81px;"
|
||||
: "",
|
||||
props.fixedHeader
|
||||
? ""
|
||||
: `padding-top: 0;${
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ref, PropType, nextTick } from "vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { deviceDetection } from "@pureadmin/utils";
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
noticeItem: {
|
||||
type: Object as PropType<ListItem>,
|
||||
default: () => {}
|
||||
|
||||
@@ -6,7 +6,7 @@ import EnterOutlined from "@/assets/svg/enter_outlined.svg?component";
|
||||
import ArrowUpLine from "@iconify-icons/ri/arrow-up-line";
|
||||
import ArrowDownLine from "@iconify-icons/ri/arrow-down-line";
|
||||
|
||||
const props = withDefaults(defineProps<{ total: number }>(), {
|
||||
withDefaults(defineProps<{ total: number }>(), {
|
||||
total: 0
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import { emitter } from "@/utils/mitt";
|
||||
import LayPanel from "../lay-panel/index.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { useAppStoreHook } from "@/store/modules/app";
|
||||
import { toggleTheme } from "@pureadmin/theme/dist/browser-utils";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import Segmented, { type OptionsType } from "@/components/ReSegmented";
|
||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
||||
@@ -50,9 +49,7 @@ const {
|
||||
if (unref(layoutTheme)) {
|
||||
const layout = unref(layoutTheme).layout;
|
||||
const theme = unref(layoutTheme).theme;
|
||||
toggleTheme({
|
||||
scopeName: `layout-theme-${theme}`
|
||||
});
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
setLayoutModel(layout);
|
||||
}
|
||||
|
||||
@@ -232,6 +229,11 @@ const markOptions = computed<Array<OptionsType>>(() => {
|
||||
label: t("panel.pureTagsStyleCard"),
|
||||
tip: t("panel.pureTagsStyleCardTip"),
|
||||
value: "card"
|
||||
},
|
||||
{
|
||||
label: t("panel.pureTagsStyleChrome"),
|
||||
tip: t("panel.pureTagsStyleChromeTip"),
|
||||
value: "chrome"
|
||||
}
|
||||
];
|
||||
});
|
||||
@@ -442,7 +444,7 @@ onUnmounted(() => removeMatchMedia);
|
||||
<Segmented
|
||||
resize
|
||||
class="select-none"
|
||||
:modelValue="markValue === 'smart' ? 0 : 1"
|
||||
:modelValue="markValue === 'smart' ? 0 : markValue === 'card' ? 1 : 2"
|
||||
:options="markOptions"
|
||||
@change="onChange"
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { isAllEmpty } from "@pureadmin/utils";
|
||||
import { ref, nextTick, computed } from "vue";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import LaySearch from "../lay-search/index.vue";
|
||||
import LayNotice from "../lay-notice/index.vue";
|
||||
import { responsiveStorageNameSpace } from "@/config";
|
||||
import { ref, nextTick, computed, onMounted } from "vue";
|
||||
import { storageLocal, isAllEmpty } from "@pureadmin/utils";
|
||||
import { useTranslationLang } from "../../hooks/useTranslationLang";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import LaySidebarItem from "../lay-sidebar/components/SidebarItem.vue";
|
||||
@@ -16,6 +18,11 @@ import Setting from "@iconify-icons/ri/settings-3-line";
|
||||
import Check from "@iconify-icons/ep/check";
|
||||
|
||||
const menuRef = ref();
|
||||
const showLogo = ref(
|
||||
storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}configure`
|
||||
)?.showLogo ?? true
|
||||
);
|
||||
|
||||
const { t, route, locale, translationCh, translationEn } =
|
||||
useTranslationLang(menuRef);
|
||||
@@ -40,6 +47,12 @@ const defaultActive = computed(() =>
|
||||
nextTick(() => {
|
||||
menuRef.value?.handleResize();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
emitter.on("logoChange", key => {
|
||||
showLogo.value = key;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -47,13 +60,12 @@ nextTick(() => {
|
||||
v-loading="usePermissionStoreHook().wholeMenus.length === 0"
|
||||
class="horizontal-header"
|
||||
>
|
||||
<div class="horizontal-header-left" @click="backTopMenu">
|
||||
<div v-if="showLogo" class="horizontal-header-left" @click="backTopMenu">
|
||||
<img :src="getLogo()" alt="logo" />
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
<el-menu
|
||||
ref="menuRef"
|
||||
router
|
||||
mode="horizontal"
|
||||
popper-class="pure-scrollbar"
|
||||
class="horizontal-header-menu"
|
||||
|
||||
@@ -99,7 +99,6 @@ onBeforeUnmount(() => {
|
||||
:class="[device === 'mobile' ? 'mobile' : 'pc']"
|
||||
>
|
||||
<el-menu
|
||||
router
|
||||
unique-opened
|
||||
mode="vertical"
|
||||
popper-class="pure-scrollbar"
|
||||
|
||||
@@ -10,7 +10,7 @@ interface Props {
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
withDefaults(defineProps<Props>(), {
|
||||
isActive: false
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { toRaw } from "vue";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
extraIcon: {
|
||||
type: String,
|
||||
default: ""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import path from "path";
|
||||
import { getConfig } from "@/config";
|
||||
import { posix } from "path-browserify";
|
||||
import { menuType } from "@/layout/types";
|
||||
import { ReText } from "@/components/ReText";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
@@ -99,8 +99,7 @@ function resolvePath(routePath) {
|
||||
if (httpReg.test(routePath) || httpReg.test(props.basePath)) {
|
||||
return routePath || props.basePath;
|
||||
} else {
|
||||
// 使用path.posix.resolve替代path.resolve 避免windows环境下使用electron出现盘符问题
|
||||
return path.posix.resolve(props.basePath, routePath);
|
||||
return posix.resolve(props.basePath, routePath);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -145,7 +144,7 @@ function resolvePath(routePath) {
|
||||
item?.pathList?.length === 2)
|
||||
"
|
||||
truncated
|
||||
class="!w-full !px-4 !text-inherit"
|
||||
class="!w-full !pl-4 !text-inherit"
|
||||
>
|
||||
{{ transformI18n(onlyOneChild.meta.title) }}
|
||||
</el-text>
|
||||
@@ -199,7 +198,7 @@ function resolvePath(routePath) {
|
||||
:class="{
|
||||
'!w-full': true,
|
||||
'!text-inherit': true,
|
||||
'!px-4':
|
||||
'!pl-4':
|
||||
layout !== 'horizontal' &&
|
||||
isCollapse &&
|
||||
!toRaw(item.meta.icon) &&
|
||||
|
||||
@@ -10,7 +10,7 @@ interface Props {
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
withDefaults(defineProps<Props>(), {
|
||||
isActive: false
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { getTopMenu } from "@/router/utils";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
collapse: Boolean
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ const { title, getLogo } = useNav();
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 32px;
|
||||
color: $subMenuActiveText;
|
||||
color: var(--pure-theme-sub-menu-active-text);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ interface Props {
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
withDefaults(defineProps<Props>(), {
|
||||
isActive: false
|
||||
});
|
||||
|
||||
|
||||
33
src/layout/components/lay-tag/components/TagChrome.vue
Normal file
33
src/layout/components/lay-tag/components/TagChrome.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<svg class="w-full h-full">
|
||||
<defs>
|
||||
<symbol id="geometry-left" viewBox="0 0 214 36">
|
||||
<path d="M17 0h197v36H0v-2c4.5 0 9-3.5 9-8V8c0-4.5 3.5-8 8-8z" />
|
||||
</symbol>
|
||||
<symbol id="geometry-right" viewBox="0 0 214 36">
|
||||
<use xlink:href="#geometry-left" />
|
||||
</symbol>
|
||||
<clipPath>
|
||||
<rect width="100%" height="100%" x="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg width="51%" height="100%">
|
||||
<use
|
||||
xlink:href="#geometry-left"
|
||||
width="214"
|
||||
height="36"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<g transform="scale(-1, 1)">
|
||||
<svg width="51%" height="100%" x="-100%" y="0">
|
||||
<use
|
||||
xlink:href="#geometry-right"
|
||||
width="214"
|
||||
height="36"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -41,6 +41,13 @@
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
&.chrome-item {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
margin-right: -18px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.el-icon-close {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@@ -76,6 +83,14 @@
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
&.chrome-scroll-container {
|
||||
padding-top: 4px;
|
||||
|
||||
.fixed-tag {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tab {
|
||||
position: relative;
|
||||
float: left;
|
||||
@@ -89,6 +104,12 @@
|
||||
&:nth-child(1) {
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
&.chrome-item {
|
||||
&:nth-child(1) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fixed-tag {
|
||||
@@ -173,9 +194,29 @@
|
||||
color: #fff;
|
||||
box-shadow: 0 0 0.7px #888;
|
||||
|
||||
.chrome-tab {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.chrome-tab__bg {
|
||||
color: var(--el-color-primary-light-9) !important;
|
||||
}
|
||||
|
||||
.tag-title {
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
.chrome-close-btn {
|
||||
color: var(--el-color-primary);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.chrome-tab-divider {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.arrow-left,
|
||||
@@ -262,3 +303,69 @@
|
||||
background: var(--el-color-primary);
|
||||
animation: schedule-out-width 200ms ease-in;
|
||||
}
|
||||
|
||||
/* 谷歌风格的页签 */
|
||||
.chrome-tab {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 24px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
|
||||
.tag-title {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.chrome-tab-divider {
|
||||
position: absolute;
|
||||
right: 7px;
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
background-color: #2b2d2f;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
z-index: 10;
|
||||
|
||||
.chrome-tab__bg {
|
||||
color: #dee1e6;
|
||||
}
|
||||
|
||||
.tag-title {
|
||||
color: #1f1f1f;
|
||||
}
|
||||
|
||||
.chrome-tab-divider {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chrome-tab__bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -10;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chrome-close-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: #666;
|
||||
border-radius: 50%;
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
background-color: #b1b3b8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { RouteConfigs } from "../../types";
|
||||
import { useTags } from "../../hooks/useTag";
|
||||
import { routerArrays } from "@/layout/types";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import TagChrome from "./components/TagChrome.vue";
|
||||
import { handleAliveRoute, getTopMenu } from "@/router/utils";
|
||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
@@ -36,6 +37,7 @@ const {
|
||||
buttonLeft,
|
||||
showModel,
|
||||
translateX,
|
||||
isFixedTag,
|
||||
pureSetting,
|
||||
activeIndex,
|
||||
getTabStyle,
|
||||
@@ -565,6 +567,7 @@ onBeforeUnmount(() => {
|
||||
<div
|
||||
ref="scrollbarDom"
|
||||
class="scroll-container"
|
||||
:class="showModel === 'chrome' && 'chrome-scroll-container'"
|
||||
@wheel.prevent="handleWheel"
|
||||
>
|
||||
<div ref="tabDom" class="tab select-none" :style="getTabStyle">
|
||||
@@ -575,35 +578,54 @@ onBeforeUnmount(() => {
|
||||
:class="[
|
||||
'scroll-item is-closable',
|
||||
linkIsActive(item),
|
||||
!isAllEmpty(item?.meta?.fixedTag) && 'fixed-tag'
|
||||
showModel === 'chrome' && 'chrome-item',
|
||||
isFixedTag(item) && 'fixed-tag'
|
||||
]"
|
||||
@contextmenu.prevent="openMenu(item, $event)"
|
||||
@mouseenter.prevent="onMouseenter(index)"
|
||||
@mouseleave.prevent="onMouseleave(index)"
|
||||
@click="tagOnClick(item)"
|
||||
>
|
||||
<span
|
||||
class="tag-title dark:!text-text_color_primary dark:hover:!text-primary"
|
||||
>
|
||||
{{ transformI18n(item.meta.title) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
isAllEmpty(item?.meta?.fixedTag)
|
||||
? iconIsActive(item, index) ||
|
||||
(index === activeIndex && index !== 0)
|
||||
: false
|
||||
"
|
||||
class="el-icon-close"
|
||||
@click.stop="deleteMenu(item)"
|
||||
>
|
||||
<IconifyIconOffline :icon="Close" />
|
||||
</span>
|
||||
<span
|
||||
v-if="showModel !== 'card'"
|
||||
:ref="'schedule' + index"
|
||||
:class="[scheduleIsActive(item)]"
|
||||
/>
|
||||
<template v-if="showModel !== 'chrome'">
|
||||
<span
|
||||
class="tag-title dark:!text-text_color_primary dark:hover:!text-primary"
|
||||
>
|
||||
{{ transformI18n(item.meta.title) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
isFixedTag(item)
|
||||
? false
|
||||
: iconIsActive(item, index) ||
|
||||
(index === activeIndex && index !== 0)
|
||||
"
|
||||
class="el-icon-close"
|
||||
@click.stop="deleteMenu(item)"
|
||||
>
|
||||
<IconifyIconOffline :icon="Close" />
|
||||
</span>
|
||||
<span
|
||||
v-if="showModel !== 'card'"
|
||||
:ref="'schedule' + index"
|
||||
:class="[scheduleIsActive(item)]"
|
||||
/>
|
||||
</template>
|
||||
<div v-else class="chrome-tab">
|
||||
<div class="chrome-tab__bg">
|
||||
<TagChrome />
|
||||
</div>
|
||||
<span class="tag-title">
|
||||
{{ transformI18n(item.meta.title) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="isFixedTag(item) ? false : index !== 0"
|
||||
class="chrome-close-btn"
|
||||
@click.stop="deleteMenu(item)"
|
||||
>
|
||||
<IconifyIconOffline :icon="Close" />
|
||||
</span>
|
||||
<span class="chrome-tab-divider" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,14 +6,9 @@ import { routerArrays } from "@/layout/types";
|
||||
import { router, resetRouter } from "@/router";
|
||||
import type { themeColorsType } from "../types";
|
||||
import { useAppStoreHook } from "@/store/modules/app";
|
||||
import { useGlobal, storageLocal } from "@pureadmin/utils";
|
||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import {
|
||||
darken,
|
||||
lighten,
|
||||
toggleTheme
|
||||
} from "@pureadmin/theme/dist/browser-utils";
|
||||
import { darken, lighten, useGlobal, storageLocal } from "@pureadmin/utils";
|
||||
|
||||
export function useDataThemeChange() {
|
||||
const { layoutTheme, layout } = useLayout();
|
||||
@@ -54,9 +49,7 @@ export function useDataThemeChange() {
|
||||
isClick = true
|
||||
) {
|
||||
layoutTheme.value.theme = theme;
|
||||
toggleTheme({
|
||||
scopeName: `layout-theme-${theme}`
|
||||
});
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
// 如果非isClick,保留之前的themeColor
|
||||
const storageThemeColor = $storage.layout.themeColor;
|
||||
$storage.layout = {
|
||||
|
||||
@@ -125,6 +125,12 @@ export function useTags() {
|
||||
}
|
||||
}
|
||||
|
||||
const isFixedTag = computed(() => {
|
||||
return item => {
|
||||
return isBoolean(item?.meta?.fixedTag) && item?.meta?.fixedTag === true;
|
||||
};
|
||||
});
|
||||
|
||||
const iconIsActive = computed(() => {
|
||||
return (item, index) => {
|
||||
if (index === 0) return;
|
||||
@@ -221,6 +227,7 @@ export function useTags() {
|
||||
buttonTop,
|
||||
buttonLeft,
|
||||
translateX,
|
||||
isFixedTag,
|
||||
pureSetting,
|
||||
activeIndex,
|
||||
getTabStyle,
|
||||
|
||||
@@ -224,7 +224,7 @@ const LayHeader = defineComponent({
|
||||
.app-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
z-index: 2001;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #000;
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
/**
|
||||
* @description ⚠️:此文件仅供主题插件使用,请不要在此文件中导出别的工具函数(仅在页面加载前运行)
|
||||
*/
|
||||
|
||||
import type { multipleScopeVarsOptions } from "@pureadmin/theme";
|
||||
|
||||
/** 预设主题色 */
|
||||
const themeColors = {
|
||||
/* 亮白色 */
|
||||
light: {
|
||||
subMenuActiveText: "#000000d9",
|
||||
menuBg: "#fff",
|
||||
menuHover: "#f6f6f6",
|
||||
subMenuBg: "#fff",
|
||||
subMenuActiveBg: "#e0ebf6",
|
||||
menuText: "rgb(0 0 0 / 60%)",
|
||||
sidebarLogo: "#fff",
|
||||
menuTitleHover: "#000",
|
||||
menuActiveBefore: "#4091f7"
|
||||
},
|
||||
/* 道奇蓝 */
|
||||
default: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#001529",
|
||||
menuHover: "rgb(64 145 247 / 15%)",
|
||||
subMenuBg: "#0f0303",
|
||||
subMenuActiveBg: "#4091f7",
|
||||
menuText: "rgb(254 254 254 / 65%)",
|
||||
sidebarLogo: "#002140",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#4091f7"
|
||||
},
|
||||
/* 深紫罗兰色 */
|
||||
saucePurple: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#130824",
|
||||
menuHover: "rgb(105 58 201 / 15%)",
|
||||
subMenuBg: "#000",
|
||||
subMenuActiveBg: "#693ac9",
|
||||
menuText: "#7a80b4",
|
||||
sidebarLogo: "#1f0c38",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#693ac9"
|
||||
},
|
||||
/* 深粉色 */
|
||||
pink: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#28081a",
|
||||
menuHover: "rgb(216 68 147 / 15%)",
|
||||
subMenuBg: "#000",
|
||||
subMenuActiveBg: "#d84493",
|
||||
menuText: "#7a80b4",
|
||||
sidebarLogo: "#3f0d29",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#d84493"
|
||||
},
|
||||
/* 猩红色 */
|
||||
dusk: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#2a0608",
|
||||
menuHover: "rgb(225 60 57 / 15%)",
|
||||
subMenuBg: "#000",
|
||||
subMenuActiveBg: "#e13c39",
|
||||
menuText: "rgb(254 254 254 / 65.1%)",
|
||||
sidebarLogo: "#42090c",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#e13c39"
|
||||
},
|
||||
/* 橙红色 */
|
||||
volcano: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#2b0e05",
|
||||
menuHover: "rgb(232 95 51 / 15%)",
|
||||
subMenuBg: "#0f0603",
|
||||
subMenuActiveBg: "#e85f33",
|
||||
menuText: "rgb(254 254 254 / 65%)",
|
||||
sidebarLogo: "#441708",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#e85f33"
|
||||
},
|
||||
/* 绿宝石 */
|
||||
mingQing: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#032121",
|
||||
menuHover: "rgb(89 191 193 / 15%)",
|
||||
subMenuBg: "#000",
|
||||
subMenuActiveBg: "#59bfc1",
|
||||
menuText: "#7a80b4",
|
||||
sidebarLogo: "#053434",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#59bfc1"
|
||||
},
|
||||
/* 酸橙绿 */
|
||||
auroraGreen: {
|
||||
subMenuActiveText: "#fff",
|
||||
menuBg: "#0b1e15",
|
||||
menuHover: "rgb(96 172 128 / 15%)",
|
||||
subMenuBg: "#000",
|
||||
subMenuActiveBg: "#60ac80",
|
||||
menuText: "#7a80b4",
|
||||
sidebarLogo: "#112f21",
|
||||
menuTitleHover: "#fff",
|
||||
menuActiveBefore: "#60ac80"
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 将预设主题色处理成主题插件所需格式
|
||||
*/
|
||||
export const genScssMultipleScopeVars = (): multipleScopeVarsOptions[] => {
|
||||
const result = [] as multipleScopeVarsOptions[];
|
||||
Object.keys(themeColors).forEach(key => {
|
||||
result.push({
|
||||
scopeName: `layout-theme-${key}`,
|
||||
varsContent: `
|
||||
$subMenuActiveText: ${themeColors[key].subMenuActiveText} !default;
|
||||
$menuBg: ${themeColors[key].menuBg} !default;
|
||||
$menuHover: ${themeColors[key].menuHover} !default;
|
||||
$subMenuBg: ${themeColors[key].subMenuBg} !default;
|
||||
$subMenuActiveBg: ${themeColors[key].subMenuActiveBg} !default;
|
||||
$menuText: ${themeColors[key].menuText} !default;
|
||||
$sidebarLogo: ${themeColors[key].sidebarLogo} !default;
|
||||
$menuTitleHover: ${themeColors[key].menuTitleHover} !default;
|
||||
$menuActiveBefore: ${themeColors[key].menuActiveBefore} !default;
|
||||
`
|
||||
} as multipleScopeVarsOptions);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
@@ -44,7 +44,9 @@ app.component("FontIcon", FontIcon);
|
||||
|
||||
// 全局注册按钮级别权限组件
|
||||
import { Auth } from "@/components/ReAuth";
|
||||
import { Perms } from "@/components/RePerms";
|
||||
app.component("Auth", Auth);
|
||||
app.component("Perms", Perms);
|
||||
|
||||
// 全局注册vue-tippy
|
||||
import "tippy.js/dist/tippy.css";
|
||||
|
||||
@@ -107,6 +107,7 @@ import {
|
||||
ElWatermark,
|
||||
ElTour,
|
||||
ElTourStep,
|
||||
ElSegmented,
|
||||
/**
|
||||
* 为了方便演示平台将 element-plus 导出的所有插件引入,实际使用中如果你没用到哪个插件,将其注释掉就行
|
||||
* 导出来源:https://github.com/element-plus/element-plus/blob/dev/packages/element-plus/plugin.ts#L11-L16
|
||||
@@ -221,7 +222,8 @@ const components = [
|
||||
ElUpload,
|
||||
ElWatermark,
|
||||
ElTour,
|
||||
ElTourStep
|
||||
ElTourStep,
|
||||
ElSegmented
|
||||
];
|
||||
|
||||
const plugins = [
|
||||
|
||||
@@ -50,7 +50,7 @@ function getObjectKeys(obj) {
|
||||
if (obj[k] && isObject(obj[k])) {
|
||||
stack.push({ obj: obj[k], key: newKey });
|
||||
} else {
|
||||
keys.add(newKey);
|
||||
keys.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ import {
|
||||
} from "vxe-table";
|
||||
|
||||
// 全局默认参数
|
||||
VXETable.config({
|
||||
VXETable.setConfig({
|
||||
// i18n: (key, args) => {
|
||||
// return unref(i18n.global.locale) === "zh"
|
||||
// ? XEUtils.toFormatString(XEUtils.get(zh, key), args)
|
||||
|
||||
@@ -18,6 +18,14 @@ export default {
|
||||
title: $t("menus.pureDialog")
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/components/drawer",
|
||||
name: "DrawerPage",
|
||||
component: () => import("@/views/components/drawer/index.vue"),
|
||||
meta: {
|
||||
title: $t("menus.pureDrawer")
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/components/message",
|
||||
name: "Message",
|
||||
|
||||
@@ -355,7 +355,7 @@ function getAuths(): Array<string> {
|
||||
return router.currentRoute.value.meta.auths as Array<string>;
|
||||
}
|
||||
|
||||
/** 是否有按钮级别的权限 */
|
||||
/** 是否有按钮级别的权限(根据路由`meta`中的`auths`字段进行判断)*/
|
||||
function hasAuth(value: string | Array<string>): boolean {
|
||||
if (!value) return false;
|
||||
/** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */
|
||||
|
||||
@@ -27,6 +27,9 @@ export const useUserStore = defineStore({
|
||||
nickname: storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "",
|
||||
// 页面级别权限
|
||||
roles: storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [],
|
||||
// 按钮级别权限
|
||||
permissions:
|
||||
storageLocal().getItem<DataInfo<number>>(userKey)?.permissions ?? [],
|
||||
// 前端生成的验证码(按实际需求替换)
|
||||
verifyCode: "",
|
||||
// 判断登录页面显示哪个组件(0:登录(默认)、1:手机登录、2:二维码登录、3:注册、4:忘记密码)
|
||||
@@ -53,6 +56,10 @@ export const useUserStore = defineStore({
|
||||
SET_ROLES(roles: Array<string>) {
|
||||
this.roles = roles;
|
||||
},
|
||||
/** 存储按钮级别权限 */
|
||||
SET_PERMS(permissions: Array<string>) {
|
||||
this.permissions = permissions;
|
||||
},
|
||||
/** 存储前端生成的验证码 */
|
||||
SET_VERIFYCODE(verifyCode: string) {
|
||||
this.verifyCode = verifyCode;
|
||||
@@ -86,6 +93,7 @@ export const useUserStore = defineStore({
|
||||
logOut() {
|
||||
this.username = "";
|
||||
this.roles = [];
|
||||
this.permissions = [];
|
||||
removeToken();
|
||||
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
|
||||
resetRouter();
|
||||
|
||||
@@ -42,6 +42,7 @@ export type userType = {
|
||||
username?: string;
|
||||
nickname?: string;
|
||||
roles?: Array<string>;
|
||||
permissions?: Array<string>;
|
||||
verifyCode?: string;
|
||||
currentPage?: number;
|
||||
isRemembered?: boolean;
|
||||
|
||||
@@ -115,6 +115,22 @@ html.dark {
|
||||
background-color: rgb(255 255 255 / 12%);
|
||||
}
|
||||
}
|
||||
|
||||
.chrome-tab {
|
||||
.tag-title {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.chrome-tab__bg {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.tag-title {
|
||||
color: #adadad;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -163,9 +163,10 @@
|
||||
|
||||
/* 仿 el-scrollbar 滚动条样式,支持大多数浏览器,如Chrome、Edge、Firefox、Safari等。整体暗色风格在 src/style/dark.scss 文件进行了适配 */
|
||||
.pure-scrollbar {
|
||||
scrollbar-color: rgb(221 222 224) transparent; /* 滑块颜色、轨道颜色 */
|
||||
|
||||
/* Firefox */
|
||||
scrollbar-width: thin; /* 可选值为 'auto', 'thin', 'none' */
|
||||
scrollbar-color: rgb(221 222 224) transparent; /* 滑块颜色、轨道颜色 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px; /* 滚动条宽度 */
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
@import "./transition";
|
||||
@import "./element-plus";
|
||||
@import "./sidebar";
|
||||
@import "./dark";
|
||||
@use "theme";
|
||||
@use "transition";
|
||||
@use "element-plus";
|
||||
@use "sidebar";
|
||||
@use "dark";
|
||||
|
||||
/* 自定义全局 CssVar */
|
||||
:root {
|
||||
@@ -13,6 +14,16 @@
|
||||
|
||||
/* switch关闭状态下的color 需要时可取用 */
|
||||
--pure-switch-off-color: #a6a6a6;
|
||||
|
||||
/** 主题色 */
|
||||
--pure-theme-sub-menu-active-text: initial;
|
||||
--pure-theme-menu-bg: none;
|
||||
--pure-theme-menu-hover: none;
|
||||
--pure-theme-sub-menu-bg: transparent;
|
||||
--pure-theme-menu-text: initial;
|
||||
--pure-theme-sidebar-logo: none;
|
||||
--pure-theme-menu-title-hover: initial;
|
||||
--pure-theme-menu-active-before: transparent;
|
||||
}
|
||||
|
||||
/* 灰色模式 */
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
font-size: 0;
|
||||
background: $menuBg;
|
||||
background: var(--pure-theme-menu-bg);
|
||||
border-right: 1px solid var(--pure-border-color);
|
||||
|
||||
/* 展开动画 */
|
||||
@@ -150,11 +150,11 @@
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
height: 50px;
|
||||
color: $menuText;
|
||||
color: var(--pure-theme-menu-text);
|
||||
background-color: transparent !important;
|
||||
|
||||
&:hover {
|
||||
color: $menuTitleHover !important;
|
||||
color: var(--pure-theme-menu-title-hover) !important;
|
||||
}
|
||||
|
||||
div,
|
||||
@@ -173,15 +173,15 @@
|
||||
|
||||
.is-active > .el-sub-menu__title,
|
||||
.is-active.submenu-title-noDropdown {
|
||||
color: $subMenuActiveText !important;
|
||||
color: var(--pure-theme-sub-menu-active-text) !important;
|
||||
|
||||
i {
|
||||
color: $subMenuActiveText !important;
|
||||
color: var(--pure-theme-sub-menu-active-text) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active {
|
||||
color: $subMenuActiveText !important;
|
||||
color: var(--pure-theme-sub-menu-active-text) !important;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
& .el-sub-menu .el-menu-item {
|
||||
min-width: $sideBarWidth !important;
|
||||
font-size: 14px;
|
||||
background-color: $subMenuBg !important;
|
||||
background-color: var(--pure-theme-sub-menu-bg) !important;
|
||||
}
|
||||
|
||||
/* 有子集的激活菜单左侧小竖条 */
|
||||
@@ -218,7 +218,7 @@
|
||||
height: 100%;
|
||||
clear: both;
|
||||
content: "";
|
||||
background-color: $menuActiveBefore;
|
||||
background-color: var(--pure-theme-menu-active-before);
|
||||
transition: all var(--pure-transition-duration) ease-in-out;
|
||||
transform: translateY(0);
|
||||
}
|
||||
@@ -253,7 +253,7 @@
|
||||
/* vertical 菜单折叠 */
|
||||
.el-menu--vertical {
|
||||
.el-menu--popup {
|
||||
background-color: $subMenuBg !important;
|
||||
background-color: var(--pure-theme-sub-menu-bg) !important;
|
||||
|
||||
.el-menu-item {
|
||||
span {
|
||||
@@ -271,10 +271,10 @@
|
||||
|
||||
.is-active > .el-sub-menu__title,
|
||||
.is-active.submenu-title-noDropdown {
|
||||
color: $subMenuActiveText !important;
|
||||
color: var(--pure-theme-sub-menu-active-text) !important;
|
||||
|
||||
i {
|
||||
color: $subMenuActiveText !important;
|
||||
color: var(--pure-theme-sub-menu-active-text) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,23 +282,23 @@
|
||||
.el-menu .el-sub-menu__title {
|
||||
min-width: $sideBarWidth !important;
|
||||
font-size: 14px;
|
||||
background-color: $subMenuBg !important;
|
||||
background-color: var(--pure-theme-sub-menu-bg) !important;
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
color: $menuText;
|
||||
background-color: $subMenuBg;
|
||||
color: var(--pure-theme-menu-text);
|
||||
background-color: var(--pure-theme-sub-menu-bg);
|
||||
|
||||
&:hover {
|
||||
color: $menuTitleHover !important;
|
||||
color: var(--pure-theme-menu-title-hover) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active {
|
||||
color: $subMenuActiveText !important;
|
||||
color: var(--pure-theme-sub-menu-active-text) !important;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
@@ -342,15 +342,15 @@
|
||||
}
|
||||
|
||||
.el-menu--popup {
|
||||
background-color: $subMenuBg !important;
|
||||
background-color: var(--pure-theme-sub-menu-bg) !important;
|
||||
|
||||
a > .is-active.submenu-title-noDropdown {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.el-menu-item {
|
||||
color: $menuText;
|
||||
background-color: $subMenuBg;
|
||||
color: var(--pure-theme-menu-text);
|
||||
background-color: var(--pure-theme-sub-menu-bg);
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
@@ -358,7 +358,7 @@
|
||||
}
|
||||
|
||||
.el-sub-menu__title {
|
||||
color: $menuText;
|
||||
color: var(--pure-theme-menu-text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,31 +366,31 @@
|
||||
.el-menu .el-sub-menu__title {
|
||||
min-width: $sideBarWidth !important;
|
||||
font-size: 14px;
|
||||
background-color: $subMenuBg !important;
|
||||
background-color: var(--pure-theme-sub-menu-bg) !important;
|
||||
|
||||
&:hover {
|
||||
color: $menuTitleHover !important;
|
||||
color: var(--pure-theme-menu-title-hover) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active > .el-sub-menu__title,
|
||||
.is-active.submenu-title-noDropdown {
|
||||
color: $subMenuActiveText !important;
|
||||
color: var(--pure-theme-sub-menu-active-text) !important;
|
||||
|
||||
i {
|
||||
color: $subMenuActiveText !important;
|
||||
color: var(--pure-theme-sub-menu-active-text) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
&:hover {
|
||||
color: $menuTitleHover !important;
|
||||
color: var(--pure-theme-menu-title-hover) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item.is-active {
|
||||
color: $subMenuActiveText !important;
|
||||
color: var(--pure-theme-sub-menu-active-text) !important;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
@@ -415,7 +415,7 @@
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background: $menuBg;
|
||||
background: var(--pure-theme-menu-bg);
|
||||
|
||||
.horizontal-header-left {
|
||||
display: flex;
|
||||
@@ -440,7 +440,7 @@
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 32px;
|
||||
color: $subMenuActiveText;
|
||||
color: var(--pure-theme-sub-menu-active-text);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -458,7 +458,7 @@
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 340px;
|
||||
color: $subMenuActiveText;
|
||||
color: var(--pure-theme-sub-menu-active-text);
|
||||
|
||||
/* 搜索 */
|
||||
.search-container,
|
||||
@@ -473,20 +473,20 @@
|
||||
/* 设置 */
|
||||
.set-icon {
|
||||
&:hover {
|
||||
background: $menuHover;
|
||||
background: var(--pure-theme-menu-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-badge {
|
||||
height: 48px;
|
||||
color: $subMenuActiveText;
|
||||
color: var(--pure-theme-sub-menu-active-text);
|
||||
}
|
||||
|
||||
.globalization {
|
||||
width: 40px;
|
||||
height: 48px;
|
||||
padding: 11px;
|
||||
color: $subMenuActiveText;
|
||||
color: var(--pure-theme-sub-menu-active-text);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
@@ -497,7 +497,7 @@
|
||||
justify-content: space-around;
|
||||
height: 48px;
|
||||
padding: 10px;
|
||||
color: $subMenuActiveText;
|
||||
color: var(--pure-theme-sub-menu-active-text);
|
||||
cursor: pointer;
|
||||
|
||||
p {
|
||||
@@ -522,10 +522,10 @@
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
padding-right: var(--el-menu-base-level-padding);
|
||||
color: $menuText;
|
||||
color: var(--pure-theme-menu-text);
|
||||
|
||||
&:hover {
|
||||
color: $menuTitleHover !important;
|
||||
color: var(--pure-theme-menu-title-hover) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,7 +533,7 @@
|
||||
.el-sub-menu__title {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
background: $menuBg;
|
||||
background: var(--pure-theme-menu-bg);
|
||||
|
||||
svg {
|
||||
position: static !important;
|
||||
@@ -542,15 +542,15 @@
|
||||
|
||||
.is-active > .el-sub-menu__title,
|
||||
.is-active.submenu-title-noDropdown {
|
||||
color: $subMenuActiveText !important;
|
||||
color: var(--pure-theme-sub-menu-active-text) !important;
|
||||
|
||||
i {
|
||||
color: $subMenuActiveText !important;
|
||||
color: var(--pure-theme-sub-menu-active-text) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active {
|
||||
color: $subMenuActiveText !important;
|
||||
color: var(--pure-theme-sub-menu-active-text) !important;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
}
|
||||
@@ -571,6 +571,7 @@
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
z-index: 2001;
|
||||
width: $sideBarWidth;
|
||||
transition: transform var(--pure-transition-duration);
|
||||
}
|
||||
@@ -595,7 +596,7 @@ body[layout="vertical"] {
|
||||
}
|
||||
|
||||
.sidebar-logo-container {
|
||||
background: $sidebarLogo;
|
||||
background: var(--pure-theme-sidebar-logo);
|
||||
}
|
||||
|
||||
.hideSidebar {
|
||||
|
||||
95
src/style/theme.scss
Normal file
95
src/style/theme.scss
Normal file
@@ -0,0 +1,95 @@
|
||||
/* 亮白色 */
|
||||
html[data-theme="light"] {
|
||||
--pure-theme-sub-menu-active-text: #000000d9;
|
||||
--pure-theme-menu-bg: #fff;
|
||||
--pure-theme-menu-hover: #f6f6f6;
|
||||
--pure-theme-sub-menu-bg: #fff;
|
||||
--pure-theme-menu-text: rgb(0 0 0 / 60%);
|
||||
--pure-theme-sidebar-logo: #fff;
|
||||
--pure-theme-menu-title-hover: #000;
|
||||
--pure-theme-menu-active-before: #4091f7;
|
||||
}
|
||||
|
||||
/* 道奇蓝 */
|
||||
html[data-theme="default"] {
|
||||
--pure-theme-sub-menu-active-text: #fff;
|
||||
--pure-theme-menu-bg: #001529;
|
||||
--pure-theme-menu-hover: rgb(64 145 247 / 15%);
|
||||
--pure-theme-sub-menu-bg: #0f0303;
|
||||
--pure-theme-menu-text: rgb(254 254 254 / 65%);
|
||||
--pure-theme-sidebar-logo: #002140;
|
||||
--pure-theme-menu-title-hover: #fff;
|
||||
--pure-theme-menu-active-before: #4091f7;
|
||||
}
|
||||
|
||||
/* 深紫罗兰色 */
|
||||
html[data-theme="saucePurple"] {
|
||||
--pure-theme-sub-menu-active-text: #fff;
|
||||
--pure-theme-menu-bg: #130824;
|
||||
--pure-theme-menu-hover: rgb(105 58 201 / 15%);
|
||||
--pure-theme-sub-menu-bg: #000;
|
||||
--pure-theme-menu-text: #7a80b4;
|
||||
--pure-theme-sidebar-logo: #1f0c38;
|
||||
--pure-theme-menu-title-hover: #fff;
|
||||
--pure-theme-menu-active-before: #693ac9;
|
||||
}
|
||||
|
||||
/* 深粉色 */
|
||||
html[data-theme="pink"] {
|
||||
--pure-theme-sub-menu-active-text: #fff;
|
||||
--pure-theme-menu-bg: #28081a;
|
||||
--pure-theme-menu-hover: rgb(216 68 147 / 15%);
|
||||
--pure-theme-sub-menu-bg: #000;
|
||||
--pure-theme-menu-text: #7a80b4;
|
||||
--pure-theme-sidebar-logo: #3f0d29;
|
||||
--pure-theme-menu-title-hover: #fff;
|
||||
--pure-theme-menu-active-before: #d84493;
|
||||
}
|
||||
|
||||
/* 猩红色 */
|
||||
html[data-theme="dusk"] {
|
||||
--pure-theme-sub-menu-active-text: #fff;
|
||||
--pure-theme-menu-bg: #2a0608;
|
||||
--pure-theme-menu-hover: rgb(225 60 57 / 15%);
|
||||
--pure-theme-sub-menu-bg: #000;
|
||||
--pure-theme-menu-text: rgb(254 254 254 / 65.1%);
|
||||
--pure-theme-sidebar-logo: #42090c;
|
||||
--pure-theme-menu-title-hover: #fff;
|
||||
--pure-theme-menu-active-before: #e13c39;
|
||||
}
|
||||
|
||||
/* 橙红色 */
|
||||
html[data-theme="volcano"] {
|
||||
--pure-theme-sub-menu-active-text: #fff;
|
||||
--pure-theme-menu-bg: #2b0e05;
|
||||
--pure-theme-menu-hover: rgb(232 95 51 / 15%);
|
||||
--pure-theme-sub-menu-bg: #0f0603;
|
||||
--pure-theme-menu-text: rgb(254 254 254 / 65%);
|
||||
--pure-theme-sidebar-logo: #441708;
|
||||
--pure-theme-menu-title-hover: #fff;
|
||||
--pure-theme-menu-active-before: #e85f33;
|
||||
}
|
||||
|
||||
/* 绿宝石 */
|
||||
html[data-theme="mingQing"] {
|
||||
--pure-theme-sub-menu-active-text: #fff;
|
||||
--pure-theme-menu-bg: #032121;
|
||||
--pure-theme-menu-hover: rgb(89 191 193 / 15%);
|
||||
--pure-theme-sub-menu-bg: #000;
|
||||
--pure-theme-menu-text: #7a80b4;
|
||||
--pure-theme-sidebar-logo: #053434;
|
||||
--pure-theme-menu-title-hover: #fff;
|
||||
--pure-theme-menu-active-before: #59bfc1;
|
||||
}
|
||||
|
||||
/* 酸橙绿 */
|
||||
html[data-theme="auroraGreen"] {
|
||||
--pure-theme-sub-menu-active-text: #fff;
|
||||
--pure-theme-menu-bg: #0b1e15;
|
||||
--pure-theme-menu-hover: rgb(96 172 128 / 15%);
|
||||
--pure-theme-sub-menu-bg: #000;
|
||||
--pure-theme-menu-text: #7a80b4;
|
||||
--pure-theme-sidebar-logo: #112f21;
|
||||
--pure-theme-menu-title-hover: #fff;
|
||||
--pure-theme-menu-active-before: #60ac80;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import Cookies from "js-cookie";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import { storageLocal, isString, isIncludeAllChildren } from "@pureadmin/utils";
|
||||
|
||||
export interface DataInfo<T> {
|
||||
/** token */
|
||||
@@ -17,6 +17,8 @@ export interface DataInfo<T> {
|
||||
nickname?: string;
|
||||
/** 当前登录用户的角色 */
|
||||
roles?: Array<string>;
|
||||
/** 当前登录用户的按钮级别权限 */
|
||||
permissions?: Array<string>;
|
||||
}
|
||||
|
||||
export const userKey = "user-info";
|
||||
@@ -41,7 +43,7 @@ export function getToken(): DataInfo<number> {
|
||||
* @description 设置`token`以及一些必要信息并采用无感刷新`token`方案
|
||||
* 无感刷新:后端返回`accessToken`(访问接口使用的`token`)、`refreshToken`(用于调用刷新`accessToken`的接口时所需的`token`,`refreshToken`的过期时间(比如30天)应大于`accessToken`的过期时间(比如2小时))、`expires`(`accessToken`的过期时间)
|
||||
* 将`accessToken`、`expires`、`refreshToken`这三条信息放在key值为authorized-token的cookie里(过期自动销毁)
|
||||
* 将`avatar`、`username`、`nickname`、`roles`、`refreshToken`、`expires`这六条信息放在key值为`user-info`的localStorage里(利用`multipleTabsKey`当浏览器完全关闭后自动销毁)
|
||||
* 将`avatar`、`username`、`nickname`、`roles`、`permissions`、`refreshToken`、`expires`这七条信息放在key值为`user-info`的localStorage里(利用`multipleTabsKey`当浏览器完全关闭后自动销毁)
|
||||
*/
|
||||
export function setToken(data: DataInfo<Date>) {
|
||||
let expires = 0;
|
||||
@@ -66,18 +68,20 @@ export function setToken(data: DataInfo<Date>) {
|
||||
: {}
|
||||
);
|
||||
|
||||
function setUserKey({ avatar, username, nickname, roles }) {
|
||||
function setUserKey({ avatar, username, nickname, roles, permissions }) {
|
||||
useUserStoreHook().SET_AVATAR(avatar);
|
||||
useUserStoreHook().SET_USERNAME(username);
|
||||
useUserStoreHook().SET_NICKNAME(nickname);
|
||||
useUserStoreHook().SET_ROLES(roles);
|
||||
useUserStoreHook().SET_PERMS(permissions);
|
||||
storageLocal().setItem(userKey, {
|
||||
refreshToken,
|
||||
expires,
|
||||
avatar,
|
||||
username,
|
||||
nickname,
|
||||
roles
|
||||
roles,
|
||||
permissions
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,7 +91,8 @@ export function setToken(data: DataInfo<Date>) {
|
||||
avatar: data?.avatar ?? "",
|
||||
username,
|
||||
nickname: data?.nickname ?? "",
|
||||
roles
|
||||
roles,
|
||||
permissions: data?.permissions ?? []
|
||||
});
|
||||
} else {
|
||||
const avatar =
|
||||
@@ -98,11 +103,14 @@ export function setToken(data: DataInfo<Date>) {
|
||||
storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "";
|
||||
const roles =
|
||||
storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [];
|
||||
const permissions =
|
||||
storageLocal().getItem<DataInfo<number>>(userKey)?.permissions ?? [];
|
||||
setUserKey({
|
||||
avatar,
|
||||
username,
|
||||
nickname,
|
||||
roles
|
||||
roles,
|
||||
permissions
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -118,3 +126,16 @@ export function removeToken() {
|
||||
export const formatToken = (token: string): string => {
|
||||
return "Bearer " + token;
|
||||
};
|
||||
|
||||
/** 是否有按钮级别的权限(根据登录接口返回的`permissions`字段进行判断)*/
|
||||
export const hasPerms = (value: string | Array<string>): boolean => {
|
||||
if (!value) return false;
|
||||
const allPerms = "*:*:*";
|
||||
const { permissions } = useUserStoreHook();
|
||||
if (!permissions) return false;
|
||||
if (permissions.length === 1 && permissions[0] === allPerms) return true;
|
||||
const isAuths = isString(value)
|
||||
? permissions.includes(value)
|
||||
: isIncludeAllChildren(value, permissions);
|
||||
return isAuths ? true : false;
|
||||
};
|
||||
|
||||
8
src/utils/http/types.d.ts
vendored
8
src/utils/http/types.d.ts
vendored
@@ -36,12 +36,12 @@ export default class PureHttp {
|
||||
): Promise<T>;
|
||||
post<T, P>(
|
||||
url: string,
|
||||
params?: T,
|
||||
params?: P,
|
||||
config?: PureHttpRequestConfig
|
||||
): Promise<P>;
|
||||
): Promise<T>;
|
||||
get<T, P>(
|
||||
url: string,
|
||||
params?: T,
|
||||
params?: P,
|
||||
config?: PureHttpRequestConfig
|
||||
): Promise<P>;
|
||||
): Promise<T>;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ Print.prototype = {
|
||||
if (!frameWindow.document.execCommand("print", false, null)) {
|
||||
frameWindow.print();
|
||||
}
|
||||
} catch (e) {
|
||||
} catch {
|
||||
frameWindow.print();
|
||||
}
|
||||
frameWindow.close();
|
||||
|
||||
@@ -82,7 +82,7 @@ export function useColumns() {
|
||||
className: "pure-version",
|
||||
cellRenderer: () => {
|
||||
return (
|
||||
<a href="https://pure-admin.github.io/pure-admin-doc" target="_blank">
|
||||
<a href="https://pure-admin.cn/" target="_blank">
|
||||
<span style="color: var(--el-color-primary)">文档链接</span>
|
||||
</a>
|
||||
);
|
||||
|
||||
@@ -131,7 +131,7 @@ getMine().then(res => {
|
||||
|
||||
<style lang="scss">
|
||||
.pure-account-settings {
|
||||
background: $menuBg;
|
||||
background: var(--pure-theme-menu-bg);
|
||||
}
|
||||
|
||||
.pure-account-settings-menu {
|
||||
@@ -140,12 +140,12 @@ getMine().then(res => {
|
||||
|
||||
.el-menu-item {
|
||||
height: 48px !important;
|
||||
color: $menuText;
|
||||
color: var(--pure-theme-menu-text);
|
||||
background-color: transparent !important;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: $menuTitleHover !important;
|
||||
color: var(--pure-theme-menu-title-hover) !important;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
|
||||
@@ -280,11 +280,11 @@ function onUpdateClick() {
|
||||
});
|
||||
}
|
||||
|
||||
// popconfirm 确认框
|
||||
// Popconfirm 确认框
|
||||
function onPopconfirmClick() {
|
||||
addDialog({
|
||||
width: "30%",
|
||||
title: "popconfirm确认框示例",
|
||||
title: "Popconfirm确认框示例",
|
||||
popconfirm: { title: "是否确认修改当前数据" },
|
||||
contentRenderer: () => <p>点击右下方确定按钮看看效果吧</p>
|
||||
});
|
||||
@@ -450,6 +450,19 @@ function onBeforeSureClick() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onSureBtnLoading() {
|
||||
addDialog({
|
||||
sureBtnLoading: true,
|
||||
title: "点击底部确定按钮可开启按钮动画",
|
||||
contentRenderer: () => <p>弹框内容-点击底部确定按钮可开启按钮动画</p>,
|
||||
beforeSure: (done, { closeLoading }) => {
|
||||
// closeLoading() // 关闭确定按钮动画,不关闭弹框
|
||||
// done() // 关闭确定按钮动画并关闭弹框
|
||||
setTimeout(() => done(), 800);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -506,7 +519,7 @@ function onBeforeSureClick() {
|
||||
<el-button @click="onCloseCallBackClick"> 关闭后的回调 </el-button>
|
||||
<el-button @click="onNestingClick"> 嵌套的弹框 </el-button>
|
||||
<el-button @click="onUpdateClick"> 更改弹框自身属性值 </el-button>
|
||||
<el-button @click="onPopconfirmClick">popconfirm确认框</el-button>
|
||||
<el-button @click="onPopconfirmClick">Popconfirm确认框</el-button>
|
||||
</el-space>
|
||||
<el-divider />
|
||||
<el-space wrap>
|
||||
@@ -534,6 +547,9 @@ function onBeforeSureClick() {
|
||||
<el-button @click="onBeforeSureClick">
|
||||
点击底部确定按钮的回调(会暂停弹框的关闭,经常用于新增、修改弹框内容后调用接口)
|
||||
</el-button>
|
||||
<el-button @click="onSureBtnLoading">
|
||||
点击底部确定按钮可开启按钮动画
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
47
src/views/components/drawer/form.vue
Normal file
47
src/views/components/drawer/form.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
// 声明 props 类型
|
||||
export interface FormProps {
|
||||
formInline: {
|
||||
user: string;
|
||||
region: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 声明 props 默认值
|
||||
// 推荐阅读:https://cn.vuejs.org/guide/typescript/composition-api.html#typing-component-props
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({ user: "", region: "" })
|
||||
});
|
||||
|
||||
// vue 规定所有的 prop 都遵循着单向绑定原则,直接修改 prop 时,Vue 会抛出警告。此处的写法仅仅是为了消除警告。
|
||||
// 因为对一个 reactive 对象执行 ref,返回 Ref 对象的 value 值仍为传入的 reactive 对象,
|
||||
// 即 newFormInline === props.formInline 为 true,所以此处代码的实际效果,仍是直接修改 props.formInline。
|
||||
// 但该写法仅适用于 props.formInline 是一个对象类型的情况,原始类型需抛出事件
|
||||
// 推荐阅读:https://cn.vuejs.org/guide/components/props.html#one-way-data-flow
|
||||
const newFormInline = ref(props.formInline);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form :model="newFormInline">
|
||||
<el-form-item label="姓名">
|
||||
<el-input
|
||||
v-model="newFormInline.user"
|
||||
class="!w-[220px]"
|
||||
placeholder="请输入姓名"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="城市">
|
||||
<el-select
|
||||
v-model="newFormInline.region"
|
||||
class="!w-[220px]"
|
||||
placeholder="请选择城市"
|
||||
>
|
||||
<el-option label="上海" value="上海" />
|
||||
<el-option label="浙江" value="浙江" />
|
||||
<el-option label="深圳" value="深圳" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
22
src/views/components/drawer/formPrimitive.vue
Normal file
22
src/views/components/drawer/formPrimitive.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from "@vueuse/core";
|
||||
|
||||
// 声明 props 类型
|
||||
export interface FormProps {
|
||||
data: string;
|
||||
}
|
||||
|
||||
// 声明 props 默认值
|
||||
// 推荐阅读:https://cn.vuejs.org/guide/typescript/composition-api.html#typing-component-props
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
data: () => ""
|
||||
});
|
||||
|
||||
// 使用 vueuse 的双向绑定工具
|
||||
const emit = defineEmits(["update:data"]);
|
||||
const data = useVModel(props, "data", emit);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-input v-model="data" class="!w-[220px]" placeholder="请输入内容" />
|
||||
</template>
|
||||
502
src/views/components/drawer/index.vue
Normal file
502
src/views/components/drawer/index.vue
Normal file
@@ -0,0 +1,502 @@
|
||||
<script setup lang="tsx">
|
||||
import {
|
||||
addDrawer,
|
||||
closeDrawer,
|
||||
closeAllDrawer,
|
||||
updateDrawer
|
||||
} from "@/components/ReDrawer/index";
|
||||
import { cloneDeep, debounce } from "@pureadmin/utils";
|
||||
import { message } from "@/utils/message";
|
||||
import { createVNode, h, ref } from "vue";
|
||||
import formPrimitive from "./formPrimitive.vue";
|
||||
import forms, { type FormProps } from "./form.vue";
|
||||
|
||||
function onBaseClick() {
|
||||
addDrawer({
|
||||
title: "基础用法",
|
||||
contentRenderer: () => <p>抽屉内容-基础用法</p> // jsx 语法 (注意在.vue文件启用jsx语法,需要在script开启lang="tsx")
|
||||
});
|
||||
}
|
||||
|
||||
function onModalClick() {
|
||||
addDrawer({
|
||||
title: "无背景遮罩层",
|
||||
modal: false,
|
||||
contentRenderer: () => <p>抽屉内容-无背景遮罩层</p>
|
||||
});
|
||||
}
|
||||
|
||||
// 添加 600ms 防抖
|
||||
const onoOpenDelayClick = debounce(
|
||||
() =>
|
||||
addDrawer({
|
||||
title: "延时2秒打开抽屉",
|
||||
openDelay: 2000 - 600,
|
||||
contentRenderer: () => <p>抽屉内容-延时2秒打开抽屉</p>
|
||||
}),
|
||||
600
|
||||
);
|
||||
|
||||
function onCloseDelayClick() {
|
||||
addDrawer({
|
||||
title: "延时2秒关闭抽屉",
|
||||
closeDelay: 2000,
|
||||
contentRenderer: () => <p>抽屉内容-延时2秒关闭抽屉</p>
|
||||
});
|
||||
}
|
||||
|
||||
function onShowCloseClick() {
|
||||
addDrawer({
|
||||
title: "不显示右上角关闭按钮图标",
|
||||
showClose: false,
|
||||
contentRenderer: () => <p>抽屉内容-不显示右上角关闭按钮图标</p>
|
||||
});
|
||||
}
|
||||
|
||||
function onBeforeCloseClick() {
|
||||
addDrawer({
|
||||
title: "禁止通过键盘ESC关闭",
|
||||
closeOnPressEscape: false,
|
||||
contentRenderer: () => <p>抽屉内容-禁止通过键盘ESC关闭</p>
|
||||
});
|
||||
}
|
||||
|
||||
function onCloseOnClickModalClick() {
|
||||
addDrawer({
|
||||
title: "禁止通过点击modal关闭",
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => <p>抽屉内容-禁止通过点击modal关闭</p>
|
||||
});
|
||||
}
|
||||
|
||||
function onHideFooterClick() {
|
||||
addDrawer({
|
||||
title: "隐藏底部取消、确定按钮",
|
||||
hideFooter: true,
|
||||
contentRenderer: () => <p>抽屉内容-隐藏底部取消、确定按钮</p>
|
||||
});
|
||||
}
|
||||
|
||||
function onHeaderRendererClick() {
|
||||
addDrawer({
|
||||
title: "自定义头部",
|
||||
showClose: false,
|
||||
headerRenderer: ({ close, titleId, titleClass }) => (
|
||||
// jsx 语法
|
||||
<div class="flex flex-row justify-between">
|
||||
<h4 id={titleId} class={titleClass}>
|
||||
自定义头部
|
||||
</h4>
|
||||
<el-button type="danger" onClick={close}>
|
||||
关闭
|
||||
</el-button>
|
||||
</div>
|
||||
),
|
||||
contentRenderer: () => <p>抽屉内容-自定义头部</p>
|
||||
});
|
||||
}
|
||||
|
||||
function onFooterRendererClick() {
|
||||
addDrawer({
|
||||
title: "自定义底部",
|
||||
footerRenderer: ({ options, index }) => (
|
||||
<el-button onClick={() => closeDrawer(options, index)}>
|
||||
{options.title}-{index}
|
||||
</el-button>
|
||||
),
|
||||
contentRenderer: () => <p>抽屉内容-自定义底部</p>
|
||||
});
|
||||
}
|
||||
|
||||
function onFooterButtonsClick() {
|
||||
addDrawer({
|
||||
title: "自定义底部按钮",
|
||||
footerButtons: [
|
||||
{
|
||||
label: "按钮1",
|
||||
size: "small",
|
||||
type: "success",
|
||||
btnClick: ({ drawer: { options, index }, button }) => {
|
||||
console.log(options, index, button);
|
||||
closeDrawer(options, index);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "按钮2",
|
||||
text: true,
|
||||
bg: true,
|
||||
btnClick: ({ drawer: { options, index }, button }) => {
|
||||
console.log(options, index, button);
|
||||
closeDrawer(options, index);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "按钮3",
|
||||
size: "large",
|
||||
type: "warning",
|
||||
btnClick: ({ drawer: { options, index }, button }) => {
|
||||
console.log(options, index, button);
|
||||
closeDrawer(options, index);
|
||||
}
|
||||
}
|
||||
],
|
||||
contentRenderer: () => <p>抽屉内容-自定义底部按钮</p>
|
||||
});
|
||||
}
|
||||
|
||||
function onOpenClick() {
|
||||
addDrawer({
|
||||
title: "打开后的回调",
|
||||
open: ({ options, index }) => message({ options, index } as any),
|
||||
contentRenderer: () => <p>抽屉内容-打开后的回调</p>
|
||||
});
|
||||
}
|
||||
|
||||
function onCloseCallBackClick() {
|
||||
addDrawer({
|
||||
title: "关闭后的回调",
|
||||
closeCallBack: ({ options, index, args }) => {
|
||||
console.log(options, index, args);
|
||||
let text = "";
|
||||
if (args?.command === "cancel") {
|
||||
text = "您点击了取消按钮";
|
||||
} else if (args?.command === "sure") {
|
||||
text = "您点击了确定按钮";
|
||||
} else {
|
||||
text = "您点击了右上角关闭按钮或空白页或按下了esc键";
|
||||
}
|
||||
message(text);
|
||||
},
|
||||
contentRenderer: () => <p>抽屉内容-关闭后的回调</p>
|
||||
});
|
||||
}
|
||||
|
||||
// 这里为了演示方便,使用了嵌套写法,实际情况下最好把 addDrawer 函数抽出来 套娃不可取
|
||||
function onNestingClick() {
|
||||
addDrawer({
|
||||
title: "嵌套的抽屉",
|
||||
size: "50%",
|
||||
contentRenderer: ({ index }) => (
|
||||
<el-button
|
||||
onClick={() =>
|
||||
addDrawer({
|
||||
title: `第${index + 1}个子抽屉`,
|
||||
size: "40%",
|
||||
contentRenderer: ({ index }) => (
|
||||
<el-button
|
||||
onClick={() =>
|
||||
addDrawer({
|
||||
title: `第${index + 1}个子抽屉`,
|
||||
size: "30%",
|
||||
contentRenderer: () => (
|
||||
<>
|
||||
<el-button round onClick={() => closeAllDrawer()}>
|
||||
哎呦,你干嘛,赶快关闭所有抽屉
|
||||
</el-button>
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
>
|
||||
点击打开第{index + 1}个子抽屉
|
||||
</el-button>
|
||||
)
|
||||
})
|
||||
}
|
||||
>
|
||||
点击打开第{index + 1}个子抽屉
|
||||
</el-button>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
// 满足在 contentRenderer 内容区更改抽屉自身属性值的场景
|
||||
function onUpdateClick() {
|
||||
const curPage = ref(1);
|
||||
addDrawer({
|
||||
title: `第${curPage.value}页`,
|
||||
contentRenderer: () => (
|
||||
<>
|
||||
<el-button
|
||||
disabled={curPage.value <= 1}
|
||||
onClick={() => {
|
||||
curPage.value -= 1;
|
||||
updateDrawer(`第${curPage.value}页`);
|
||||
}}
|
||||
>
|
||||
上一页
|
||||
</el-button>
|
||||
<el-button
|
||||
onClick={() => {
|
||||
curPage.value += 1;
|
||||
updateDrawer(`第${curPage.value}页`);
|
||||
}}
|
||||
>
|
||||
下一页
|
||||
</el-button>
|
||||
</>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
// Popconfirm 确认框
|
||||
function onPopConfirmClick() {
|
||||
addDrawer({
|
||||
size: "30%",
|
||||
title: "Popconfirm确认框示例",
|
||||
popConfirm: { title: "是否确认修改当前数据" },
|
||||
contentRenderer: () => <p>点击右下方确定按钮看看效果吧</p>
|
||||
});
|
||||
}
|
||||
|
||||
// 结合Form表单(第一种方式,抽屉关闭立刻恢复初始值)通过 props 属性接收子组件的 prop 并赋值
|
||||
function onFormOneClick() {
|
||||
addDrawer({
|
||||
size: "30%",
|
||||
title: "结合Form表单(第一种方式)",
|
||||
contentRenderer: () => forms,
|
||||
props: {
|
||||
// 赋默认值
|
||||
formInline: {
|
||||
user: "菜虚鲲",
|
||||
region: "浙江"
|
||||
}
|
||||
},
|
||||
closeCallBack: ({ options, args }) => {
|
||||
// options.props 是响应式的
|
||||
const { formInline } = options.props as FormProps;
|
||||
const text = `姓名:${formInline.user} 城市:${formInline.region}`;
|
||||
if (args?.command === "cancel") {
|
||||
// 您点击了取消按钮
|
||||
message(`您点击了取消按钮,当前表单数据为 ${text}`);
|
||||
} else if (args?.command === "sure") {
|
||||
message(`您点击了确定按钮,当前表单数据为 ${text}`);
|
||||
} else {
|
||||
message(
|
||||
`您点击了右上角关闭按钮或空白页或按下了esc键,当前表单数据为 ${text}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 结合Form表单(第二种方式)h 渲染函数 https://cn.vuejs.org/api/render-function.html#h
|
||||
const formInline = ref({
|
||||
user: "菜虚鲲",
|
||||
region: "浙江"
|
||||
});
|
||||
const resetFormInline = cloneDeep(formInline.value);
|
||||
function onFormTwoClick() {
|
||||
addDrawer({
|
||||
size: "30%",
|
||||
title: "结合Form表单(第二种方式)",
|
||||
contentRenderer: () =>
|
||||
h(forms, {
|
||||
formInline: formInline.value
|
||||
}),
|
||||
closeCallBack: () => {
|
||||
message(
|
||||
`当前表单数据为 姓名:${formInline.value.user} 城市:${formInline.value.region}`
|
||||
);
|
||||
// 重置表单数据
|
||||
formInline.value = cloneDeep(resetFormInline);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 结合Form表单(第三种方式)createVNode 渲染函数 https://cn.vuejs.org/guide/extras/render-function.html#creating-vnodes
|
||||
const formThreeInline = ref({
|
||||
user: "菜虚鲲",
|
||||
region: "浙江"
|
||||
});
|
||||
const resetFormThreeInline = cloneDeep(formThreeInline.value);
|
||||
function onFormThreeClick() {
|
||||
addDrawer({
|
||||
size: "30%",
|
||||
title: "结合Form表单(第三种方式)",
|
||||
contentRenderer: () =>
|
||||
createVNode(forms, {
|
||||
formInline: formThreeInline.value
|
||||
}),
|
||||
closeCallBack: () => {
|
||||
message(
|
||||
`当前表单数据为 姓名:${formThreeInline.value.user} 城市:${formThreeInline.value.region}`
|
||||
);
|
||||
// 重置表单数据
|
||||
formThreeInline.value = cloneDeep(resetFormThreeInline);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 结合Form表单(第四种方式)使用jsx语法
|
||||
// 需要注意的是如果 forms 没注册,这里 forms 注册了是因为上面 contentRenderer: () => forms、h(forms) 、createVNode(createVNode) 间接给注册了
|
||||
// 如果只使用了jsx语法,如下 `contentRenderer: () => <forms formInline={formFourInline.value} />` 是不会给 forms 组件进行注册的,需要在 `script` 中任意位置(最好是末尾)写上 forms 即可
|
||||
// 同理如果在 tsx 文件中,这么使用 `contentRenderer: () => <forms formInline={formFourInline.value} />`,也是不会给 forms 组件进行注册,需要在 return 中写上 forms
|
||||
const formFourInline = ref({
|
||||
user: "菜虚鲲",
|
||||
region: "浙江"
|
||||
});
|
||||
const resetFormFourInline = cloneDeep(formFourInline.value);
|
||||
function onFormFourClick() {
|
||||
addDrawer({
|
||||
size: "30%",
|
||||
title: "结合Form表单(第四种方式)",
|
||||
contentRenderer: () => <forms formInline={formFourInline.value} />,
|
||||
closeCallBack: () => {
|
||||
message(
|
||||
`当前表单数据为 姓名:${formFourInline.value.user} 城市:${formFourInline.value.region}`
|
||||
);
|
||||
// 重置表单数据
|
||||
formFourInline.value = cloneDeep(resetFormFourInline);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 子组件 prop 为 primitive 类型的 demo
|
||||
const formPrimitiveParam = ref("Hello World");
|
||||
const resetFormPrimitiveParam = ref(formPrimitiveParam.value);
|
||||
function onFormPrimitiveFormClick() {
|
||||
addDrawer({
|
||||
size: "30%",
|
||||
title: "子组件 prop 为 primitive 类型 demo",
|
||||
contentRenderer: () =>
|
||||
h(formPrimitive, {
|
||||
data: formPrimitiveParam.value,
|
||||
"onUpdate:data": val => (formPrimitiveParam.value = val)
|
||||
}),
|
||||
closeCallBack: () => {
|
||||
message(`当前表单内容:${formPrimitiveParam.value}`);
|
||||
// 重置表单数据
|
||||
formPrimitiveParam.value = resetFormPrimitiveParam.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onBeforeCancelClick() {
|
||||
addDrawer({
|
||||
title: "点击底部取消按钮的回调",
|
||||
contentRenderer: () => (
|
||||
<p>抽屉内容-点击底部取消按钮的回调(会暂停抽屉的关闭)</p>
|
||||
),
|
||||
beforeCancel: (done, { options, index }) => {
|
||||
console.log(
|
||||
"%coptions, index===>>>: ",
|
||||
"color: MidnightBlue; background: Aquamarine; font-size: 20px;",
|
||||
options,
|
||||
index
|
||||
);
|
||||
// done(); // 需要关闭把注释解开即可
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onBeforeSureClick() {
|
||||
addDrawer({
|
||||
title: "点击底部确定按钮的回调",
|
||||
contentRenderer: () => (
|
||||
<p>
|
||||
抽屉内容-点击底部确定按钮的回调(会暂停抽屉的关闭,经常用于新增、修改抽屉内容后调用接口)
|
||||
</p>
|
||||
),
|
||||
beforeSure: (done, { options, index }) => {
|
||||
console.log(
|
||||
"%coptions, index===>>>: ",
|
||||
"color: MidnightBlue; background: Aquamarine; font-size: 20px;",
|
||||
options,
|
||||
index
|
||||
);
|
||||
// done(); // 需要关闭把注释解开即可
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onSureBtnLoading() {
|
||||
addDrawer({
|
||||
sureBtnLoading: true,
|
||||
title: "点击底部确定按钮可开启按钮动画",
|
||||
contentRenderer: () => <p>抽屉内容-点击底部确定按钮可开启按钮动画</p>,
|
||||
beforeSure: (done, { closeLoading }) => {
|
||||
// closeLoading(); // 关闭确定按钮动画,不关闭抽屉
|
||||
// done() // 关闭确定按钮动画并关闭抽屉
|
||||
setTimeout(() => done(), 800);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="font-medium">
|
||||
二次封装 Element Plus 的
|
||||
<el-link
|
||||
href="https://element-plus.org/zh-CN/component/drawer.html"
|
||||
target="_blank"
|
||||
style="margin: 0 4px 5px; font-size: 16px"
|
||||
>
|
||||
Drawer
|
||||
</el-link>
|
||||
</span>
|
||||
</div>
|
||||
<el-link
|
||||
href="https://github.com/pure-admin/vue-pure-admin/tree/main/src/views/components/drawer"
|
||||
target="_blank"
|
||||
>
|
||||
代码位置 src/views/components/drawer
|
||||
</el-link>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button @click="onBaseClick">基础用法</el-button>
|
||||
<el-button @click="onModalClick"> 无背景遮罩层 </el-button>
|
||||
<el-button @click="onoOpenDelayClick"> 延时2秒打开抽屉 </el-button>
|
||||
<el-button @click="onCloseDelayClick"> 延时2秒关闭抽屉 </el-button>
|
||||
<el-button @click="onShowCloseClick">
|
||||
不显示右上角关闭按钮图标
|
||||
</el-button>
|
||||
<el-button @click="onBeforeCloseClick"> 禁止通过键盘ESC关闭 </el-button>
|
||||
<el-button @click="onCloseOnClickModalClick">
|
||||
禁止通过点击modal关闭
|
||||
</el-button>
|
||||
<el-button @click="onHideFooterClick"> 隐藏底部取消、确定按钮 </el-button>
|
||||
<el-button @click="onHeaderRendererClick"> 自定义头部 </el-button>
|
||||
<el-button @click="onFooterRendererClick"> 自定义底部 </el-button>
|
||||
<el-button @click="onFooterButtonsClick"> 自定义底部按钮 </el-button>
|
||||
<el-button @click="onOpenClick"> 打开后的回调 </el-button>
|
||||
<el-button @click="onCloseCallBackClick"> 关闭后的回调 </el-button>
|
||||
<el-button @click="onNestingClick"> 嵌套的抽屉 </el-button>
|
||||
<el-button @click="onUpdateClick"> 更改抽屉自身属性值 </el-button>
|
||||
<el-button @click="onPopConfirmClick">Popconfirm确认框</el-button>
|
||||
</el-space>
|
||||
<el-divider />
|
||||
<el-space wrap>
|
||||
<el-button @click="onFormOneClick">
|
||||
结合Form表单(第一种方式)
|
||||
</el-button>
|
||||
<el-button @click="onFormTwoClick">
|
||||
结合Form表单(第二种方式)
|
||||
</el-button>
|
||||
<el-button @click="onFormThreeClick">
|
||||
结合Form表单(第三种方式)
|
||||
</el-button>
|
||||
<el-button @click="onFormFourClick">
|
||||
结合Form表单(第四种方式)
|
||||
</el-button>
|
||||
<el-button @click="onFormPrimitiveFormClick">
|
||||
子组件 prop 为 primitive 类型
|
||||
</el-button>
|
||||
</el-space>
|
||||
<el-divider />
|
||||
<el-space wrap>
|
||||
<el-button @click="onBeforeCancelClick">
|
||||
点击底部取消按钮的回调(会暂停抽屉的关闭)
|
||||
</el-button>
|
||||
<el-button @click="onBeforeSureClick">
|
||||
点击底部确定按钮的回调(会暂停抽屉的关闭,经常用于新增、修改抽屉内容后调用接口)
|
||||
</el-button>
|
||||
<el-button @click="onSureBtnLoading">
|
||||
点击底部确定按钮可开启按钮动画
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
</template>
|
||||
@@ -99,7 +99,7 @@ watch(size, val =>
|
||||
<el-button
|
||||
v-for="(button, index) in buttonList"
|
||||
:key="index"
|
||||
:type="button.type"
|
||||
:type="button.type as any"
|
||||
:size="dynamicSize"
|
||||
:disabled="size === 'disabled'"
|
||||
:plain="baseRadio === 'plain'"
|
||||
|
||||
@@ -97,7 +97,11 @@ const immediateDebounce: any = debounce(
|
||||
);
|
||||
|
||||
useEventListener(document, "keypress", ({ code }) => {
|
||||
if (code === "Enter" && !disabled.value && !loading.value)
|
||||
if (
|
||||
["Enter", "NumpadEnter"].includes(code) &&
|
||||
!disabled.value &&
|
||||
!loading.value
|
||||
)
|
||||
immediateDebounce(ruleFormRef.value);
|
||||
});
|
||||
|
||||
|
||||
@@ -133,8 +133,7 @@ const {
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="pagination"
|
||||
:paginationSmall="size === 'small' ? true : false"
|
||||
:pagination="{ ...pagination, size }"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
|
||||
@@ -133,8 +133,7 @@ const {
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="pagination"
|
||||
:paginationSmall="size === 'small' ? true : false"
|
||||
:pagination="{ ...pagination, size }"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
|
||||
@@ -125,8 +125,7 @@ const {
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="pagination"
|
||||
:paginationSmall="size === 'small' ? true : false"
|
||||
:pagination="{ ...pagination, size }"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
|
||||
@@ -74,8 +74,7 @@ const {
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="pagination"
|
||||
:paginationSmall="size === 'small' ? true : false"
|
||||
:pagination="{ ...pagination, size }"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { hasAuth, getAuths } from "@/router/utils";
|
||||
|
||||
defineOptions({
|
||||
name: "PermissionButton"
|
||||
name: "PermissionButtonRouter"
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
116
src/views/permission/button/perms.vue
Normal file
116
src/views/permission/button/perms.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<script setup lang="ts">
|
||||
import { hasPerms } from "@/utils/auth";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
|
||||
const { permissions } = useUserStoreHook();
|
||||
|
||||
defineOptions({
|
||||
name: "PermissionButtonLogin"
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p class="mb-2">当前拥有的code列表:{{ permissions }}</p>
|
||||
<p v-show="permissions?.[0] === '*:*:*'" class="mb-2">
|
||||
*:*:* 代表拥有全部按钮级别权限
|
||||
</p>
|
||||
|
||||
<el-card shadow="never" class="mb-2">
|
||||
<template #header>
|
||||
<div class="card-header">组件方式判断权限</div>
|
||||
<el-link
|
||||
class="mt-2"
|
||||
href="https://github.com/pure-admin/vue-pure-admin/blob/main/src/views/permission/button/perms.vue"
|
||||
target="_blank"
|
||||
>
|
||||
代码位置 src/views/permission/button/perms.vue
|
||||
</el-link>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<Perms value="permission:btn:add">
|
||||
<el-button plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
</Perms>
|
||||
<Perms :value="['permission:btn:edit']">
|
||||
<el-button plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
</Perms>
|
||||
<Perms
|
||||
:value="[
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
]"
|
||||
>
|
||||
<el-button plain type="danger">
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</Perms>
|
||||
</el-space>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mb-2">
|
||||
<template #header>
|
||||
<div class="card-header">函数方式判断权限</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button v-if="hasPerms('permission:btn:add')" plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasPerms(['permission:btn:edit'])"
|
||||
plain
|
||||
type="primary"
|
||||
>
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="
|
||||
hasPerms([
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
])
|
||||
"
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
指令方式判断权限(该方式不能动态修改权限)
|
||||
</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button v-perms="'permission:btn:add'" plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
<el-button v-perms="['permission:btn:edit']" plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="[
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
]"
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -127,7 +127,6 @@ const columns: PlusColumn[] = [
|
||||
fieldProps: {
|
||||
maxlength: 10,
|
||||
showWordLimit: true,
|
||||
// @ts-expect-error
|
||||
autosize: { minRows: 2, maxRows: 4 }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,6 @@ const columns: PlusColumn[] = [
|
||||
fieldProps: {
|
||||
maxlength: 10,
|
||||
showWordLimit: true,
|
||||
// @ts-expect-error
|
||||
autosize: { minRows: 2, maxRows: 4 }
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user