Compare commits

..

21 Commits

Author SHA1 Message Date
xiaoxian521
cd21f1e050 release: update 5.8.0 2024-08-19 13:53:33 +08:00
xiaoxian521
5fbb664da7 chore: 升级依赖,element-plus最新版兼容处理 2024-08-12 14:51:34 +08:00
xiaoming
244ab7f990 feat: 新增第二种按钮权限指令(根据登录接口返回的permissions字段进行判断) (#1177)
* feat: 新增第二种按钮权限指令(根据登录接口返回的`permissions`字段进行判断)
2024-08-12 13:32:04 +08:00
xiaoxian521
96152ed134 feat: ReDialog组件的确定按钮提供关闭按钮动画closeLoading功能 2024-08-05 13:15:10 +08:00
xiaoxian521
37ab40f188 feat: VxeTableBar组件添加全屏和退出全屏功能 2024-08-03 15:13:20 +08:00
xiaoxian521
91ae63a8c5 feat: PureTableBar组件添加全屏和退出全屏功能 2024-08-03 14:39:21 +08:00
xiaoxian521
6d7e92fed1 feat: 函数式弹框ReDialog添加点击确认按钮后是否开启loading加载动画功能 2024-07-30 14:56:47 +08:00
xiaoxian521
0706f37254 fix: 修复顶部菜单模式下logo不可隐藏的问题 2024-07-29 10:52:12 +08:00
xiaoxian521
8a9695cf7c chore(deps): update 2024-07-29 09:49:38 +08:00
xiaoxian521
d395c9c6ba chore(deps): update 2024-07-21 17:09:05 +08:00
xiaoxian521
3dec3c002f perf: 优化系统管理-角色管理的权限功能样式 2024-07-04 11:14:22 +08:00
xiaoxian521
bae1122e58 feat: 添加vite-plugin-checker插件,更严格的类型和eslint校验 2024-07-03 11:31:37 +08:00
xiaoxian521
37e9d8a1ac chore: 升级依赖,相关兼容处理 2024-07-02 20:57:35 +08:00
sea
dea9664677 perf: 手机端层级展示 (#1169) 2024-07-02 15:30:27 +08:00
sea
775d7a2d32 perf: 优化登录页 (#1168) 2024-06-27 09:55:47 +08:00
xiaoxian521
13e7a13e9d chore(deps): update 2024-06-14 14:01:18 +08:00
xiaoxian521
47afa9209e fix: 修复配置路由属性fixedTagfalse后当前标签页不可关闭的问题 2024-06-12 14:56:09 +08:00
xiaoxian521
933ced4ac4 docs: update 2024-06-11 22:28:57 +08:00
Fifteen
8816e61e3a chore: update plugins/elementPlus.ts (#1164) 2024-06-07 10:38:19 +08:00
sea
edf82ea727 feat: 添加开发环境代码调试vite-plugin-vue-inspector插件,提升开发体验 (#1162) 2024-06-06 17:32:25 +08:00
sea
a75cf8394e types: 优化自定义指令的类型提示 (#1161) 2024-06-06 16:26:32 +08:00
62 changed files with 2835 additions and 2006 deletions

2
.nvmrc
View File

@@ -1 +1 @@
v20.13.1 v20.15.0

View File

@@ -1,6 +1,7 @@
{ {
"recommendations": [ "recommendations": [
"christian-kohler.path-intellisense", "christian-kohler.path-intellisense",
"warmthsea.vscode-custom-code-color",
"vscode-icons-team.vscode-icons", "vscode-icons-team.vscode-icons",
"davidanson.vscode-markdownlint", "davidanson.vscode-markdownlint",
"ms-azuretools.vscode-docker", "ms-azuretools.vscode-docker",

25
.vscode/settings.json vendored
View File

@@ -31,11 +31,18 @@
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true, "i18n-ally.sortKeys": true,
"i18n-ally.namespace": true, "i18n-ally.namespace": true,
"i18n-ally.enabledParsers": ["yaml", "js"], "i18n-ally.enabledParsers": [
"yaml",
"js"
],
"i18n-ally.sourceLanguage": "en", "i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN", "i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue"], "i18n-ally.enabledFrameworks": [
"iconify.excludes": ["el"], "vue"
],
"iconify.excludes": [
"el"
],
"vsmqtt.brokerProfiles": [ "vsmqtt.brokerProfiles": [
{ {
"name": "broker.emqx.io", "name": "broker.emqx.io",
@@ -43,5 +50,15 @@
"port": 1883, "port": 1883,
"clientId": "vsmqtt_client_db34" "clientId": "vsmqtt_client_db34"
} }
] ],
"vscodeCustomCodeColor.highlightValue": [
"v-loading",
"v-auth",
"v-copy",
"v-longpress",
"v-optimize",
"v-perms",
"v-ripple"
],
"vscodeCustomCodeColor.highlightValueColor": "#b392f0",
} }

View File

@@ -1,3 +1,28 @@
# 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) # 5.7.0 (2024-06-04)
### 🎫 Feat ### 🎫 Feat

View File

@@ -1,3 +1,28 @@
# 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) # 5.7.0 (2024-06-04)
### 🎫 Feat ### 🎫 Feat

View File

@@ -1,3 +1,28 @@
# 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) # 5.7.0 (2024-06-04)
### 🎫 Feat ### 🎫 Feat

View File

@@ -35,6 +35,14 @@ The simplified version is based on the shelf extracted from [vue-pure-admin](htt
[Click me for details](https://pure-admin.github.io/pure-admin-doc/pages/service/) [Click me for details](https://pure-admin.github.io/pure-admin-doc/pages/service/)
## `js` version
[Click me to view js version](https://pure-admin.github.io/pure-admin-doc/pages/js/)
## `max` version
[Click me to view the max version](https://github.com/pure-admin/vue-pure-admin-max)
## Tauri ## Tauri
[Click Watch Tauri](https://github.com/pure-admin/tauri-pure-admin) [Click Watch Tauri](https://github.com/pure-admin/tauri-pure-admin)

View File

@@ -36,6 +36,10 @@
[点我查看详情](https://pure-admin.github.io/pure-admin-doc/pages/service/) [点我查看详情](https://pure-admin.github.io/pure-admin-doc/pages/service/)
## `js` 版本
[点我查看 js 版本](https://pure-admin.github.io/pure-admin-doc/pages/js/)
## `max` 版本 ## `max` 版本
[点我查看 max 版本](https://github.com/pure-admin/vue-pure-admin-max) [点我查看 max 版本](https://github.com/pure-admin/vue-pure-admin-max)

View File

@@ -4,7 +4,9 @@ import { pathResolve } from "./utils";
import { viteBuildInfo } from "./info"; import { viteBuildInfo } from "./info";
import svgLoader from "vite-svg-loader"; import svgLoader from "vite-svg-loader";
import type { PluginOption } from "vite"; import type { PluginOption } from "vite";
import checker from "vite-plugin-checker";
import vueJsx from "@vitejs/plugin-vue-jsx"; import vueJsx from "@vitejs/plugin-vue-jsx";
import Inspector from "vite-plugin-vue-inspector";
import { configCompressPlugin } from "./compress"; import { configCompressPlugin } from "./compress";
import removeNoMatch from "vite-plugin-router-warn"; import removeNoMatch from "vite-plugin-router-warn";
import { visualizer } from "rollup-plugin-visualizer"; import { visualizer } from "rollup-plugin-visualizer";
@@ -27,6 +29,18 @@ export function getPluginsList(
jitCompilation: false, jitCompilation: false,
include: [pathResolve("../locales/**")] include: [pathResolve("../locales/**")]
}), }),
checker({
typescript: true,
vueTsc: true,
eslint: {
lintCommand: `eslint ${pathResolve("../{src,mock,build}/**/*.{vue,js,ts,tsx}")}`,
useFlatConfig: true
},
terminal: false,
enableBuild: false
}),
// 按下Command(⌘)+Shift(⇧)然后点击页面元素会自动打开本地IDE并跳转到对应的代码位置
Inspector(),
viteBuildInfo(), viteBuildInfo(),
/** /**
* 开发环境下移除非必要的vue-router动态路由警告No match found for location with path * 开发环境下移除非必要的vue-router动态路由警告No match found for location with path

View File

@@ -125,6 +125,8 @@ menus:
purePermission: Permission Manage purePermission: Permission Manage
purePermissionPage: Page Permission purePermissionPage: Page Permission
purePermissionButton: Button Permission purePermissionButton: Button Permission
purePermissionButtonRouter: Route return button permission
purePermissionButtonLogin: Login interface return button permission
pureTabs: Tabs Operate pureTabs: Tabs Operate
pureGuide: Guide pureGuide: Guide
pureAble: Able pureAble: Able

View File

@@ -125,6 +125,8 @@ menus:
purePermission: 权限管理 purePermission: 权限管理
purePermissionPage: 页面权限 purePermissionPage: 页面权限
purePermissionButton: 按钮权限 purePermissionButton: 按钮权限
purePermissionButtonRouter: 路由返回按钮权限
purePermissionButtonLogin: 登录接口返回按钮权限
pureTabs: 标签页操作 pureTabs: 标签页操作
pureGuide: 引导页 pureGuide: 引导页
pureAble: 功能 pureAble: 功能

View File

@@ -123,17 +123,34 @@ const permissionRouter = {
} }
}, },
{ {
path: "/permission/button/index", path: "/permission/button",
name: "PermissionButton",
meta: { meta: {
title: "menus.purePermissionButton", title: "menus.purePermissionButton",
roles: ["admin", "common"], roles: ["admin", "common"]
},
children: [
{
path: "/permission/button/router",
component: "permission/button/index",
name: "PermissionButtonRouter",
meta: {
title: "menus.purePermissionButtonRouter",
auths: [ auths: [
"permission:btn:add", "permission:btn:add",
"permission:btn:edit", "permission:btn:edit",
"permission:btn:delete" "permission:btn:delete"
] ]
} }
},
{
path: "/permission/button/login",
component: "permission/button/perms",
name: "PermissionButtonLogin",
meta: {
title: "menus.purePermissionButtonLogin"
}
}
]
} }
] ]
}; };

View File

@@ -15,6 +15,8 @@ export default defineFakeRoute([
nickname: "小铭", nickname: "小铭",
// 一个用户可能有多个角色 // 一个用户可能有多个角色
roles: ["admin"], roles: ["admin"],
// 按钮级别权限
permissions: ["*:*:*"],
accessToken: "eyJhbGciOiJIUzUxMiJ9.admin", accessToken: "eyJhbGciOiJIUzUxMiJ9.admin",
refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh", refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh",
expires: "2030/10/30 00:00:00" expires: "2030/10/30 00:00:00"
@@ -28,6 +30,7 @@ export default defineFakeRoute([
username: "common", username: "common",
nickname: "小林", nickname: "小林",
roles: ["common"], roles: ["common"],
permissions: ["permission:btn:add", "permission:btn:edit"],
accessToken: "eyJhbGciOiJIUzUxMiJ9.common", accessToken: "eyJhbGciOiJIUzUxMiJ9.common",
refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh", refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh",
expires: "2030/10/30 00:00:00" expires: "2030/10/30 00:00:00"

View File

@@ -696,7 +696,7 @@ export default defineFakeRoute([
menuType: 0, menuType: 0,
title: "menus.purePermissionButton", title: "menus.purePermissionButton",
name: "PermissionButton", name: "PermissionButton",
path: "/permission/button/index", path: "/permission/button",
component: "", component: "",
rank: null, rank: null,
redirect: "", redirect: "",
@@ -717,6 +717,30 @@ export default defineFakeRoute([
{ {
parentId: 202, parentId: 202,
id: 203, 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, menuType: 3,
title: "添加", title: "添加",
name: "", name: "",
@@ -739,8 +763,8 @@ export default defineFakeRoute([
showParent: false showParent: false
}, },
{ {
parentId: 202, parentId: 203,
id: 204, id: 211,
menuType: 3, menuType: 3,
title: "修改", title: "修改",
name: "", name: "",
@@ -762,9 +786,105 @@ export default defineFakeRoute([
showLink: true, showLink: true,
showParent: false 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, 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, menuType: 3,
title: "删除", title: "删除",
name: "", name: "",

View File

@@ -1,6 +1,6 @@
{ {
"name": "vue-pure-admin", "name": "vue-pure-admin",
"version": "5.7.0", "version": "5.8.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -50,26 +50,26 @@
"@amap/amap-jsapi-loader": "^1.0.1", "@amap/amap-jsapi-loader": "^1.0.1",
"@howdyjs/mouse-menu": "^2.1.3", "@howdyjs/mouse-menu": "^2.1.3",
"@infectoone/vue-ganttastic": "^2.3.2", "@infectoone/vue-ganttastic": "^2.3.2",
"@logicflow/core": "^1.2.27", "@logicflow/core": "^1.2.28",
"@logicflow/extension": "^1.2.27", "@logicflow/extension": "^1.2.28",
"@pureadmin/descriptions": "^1.2.1", "@pureadmin/descriptions": "^1.2.1",
"@pureadmin/table": "^3.1.2", "@pureadmin/table": "^3.2.0",
"@pureadmin/utils": "^2.4.7", "@pureadmin/utils": "^2.4.8",
"@vue-flow/background": "^1.3.0", "@vue-flow/background": "^1.3.0",
"@vue-flow/core": "^1.34.0", "@vue-flow/core": "^1.39.3",
"@vueuse/core": "^10.10.0", "@vueuse/core": "^10.11.1",
"@vueuse/motion": "^2.2.0", "@vueuse/motion": "^2.2.3",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12", "@wangeditor/editor-for-vue": "^5.1.12",
"@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.7.2", "axios": "^1.7.3",
"china-area-data": "^5.0.1", "china-area-data": "^5.0.1",
"cropperjs": "^1.6.2", "cropperjs": "^1.6.2",
"dayjs": "^1.11.11", "dayjs": "^1.11.12",
"echarts": "^5.5.0", "echarts": "^5.5.1",
"el-table-infinite-scroll": "^3.0.5", "el-table-infinite-scroll": "^3.0.6",
"element-plus": "^2.7.3", "element-plus": "^2.8.0",
"intro.js": "^7.2.0", "intro.js": "^7.2.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jsbarcode": "^3.11.6", "jsbarcode": "^3.11.6",
@@ -79,40 +79,40 @@
"mqtt": "4.3.7", "mqtt": "4.3.7",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path": "^0.12.7", "path": "^0.12.7",
"pinia": "^2.1.7", "pinia": "^2.2.1",
"pinyin-pro": "^3.21.1", "pinyin-pro": "^3.24.2",
"plus-pro-components": "^0.1.7", "plus-pro-components": "^0.1.14",
"qrcode": "^1.5.3", "qrcode": "^1.5.4",
"qs": "^6.12.1", "qs": "^6.13.0",
"responsive-storage": "^2.2.0", "responsive-storage": "^2.2.0",
"sortablejs": "^1.15.2", "sortablejs": "^1.15.2",
"swiper": "^11.1.4", "swiper": "^11.1.9",
"typeit": "^8.8.3", "typeit": "^8.8.4",
"v-contextmenu": "^3.2.0", "v-contextmenu": "^3.2.0",
"v3-infinite-loading": "^1.3.1", "v3-infinite-loading": "^1.3.1",
"version-rocket": "^1.7.1", "version-rocket": "^1.7.2",
"vue": "^3.4.27", "vue": "^3.4.37",
"vue-i18n": "^9.13.1", "vue-i18n": "^9.13.1",
"vue-json-pretty": "^2.4.0", "vue-json-pretty": "^2.4.0",
"vue-pdf-embed": "^2.0.4", "vue-pdf-embed": "^2.1.0",
"vue-router": "^4.3.2", "vue-router": "^4.4.3",
"vue-tippy": "^6.4.1", "vue-tippy": "^6.4.4",
"vue-types": "^5.1.2", "vue-types": "^5.1.3",
"vue-virtual-scroller": "2.0.0-beta.8", "vue-virtual-scroller": "2.0.0-beta.8",
"vue-waterfall-plugin-next": "^2.4.3", "vue-waterfall-plugin-next": "^2.4.3",
"vue3-danmaku": "^1.6.0", "vue3-danmaku": "^1.6.1",
"vue3-puzzle-vcode": "^1.1.7", "vue3-puzzle-vcode": "^1.1.7",
"vuedraggable": "^4.1.0", "vuedraggable": "^4.1.0",
"vxe-table": "^4.6.17", "vxe-table": "4.6.17",
"wavesurfer.js": "^7.7.15", "wavesurfer.js": "^7.8.3",
"xgplayer": "^3.0.18", "xgplayer": "^3.0.19",
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^19.3.0", "@commitlint/cli": "^19.4.0",
"@commitlint/config-conventional": "^19.2.2", "@commitlint/config-conventional": "^19.2.2",
"@commitlint/types": "^19.0.3", "@commitlint/types": "^19.0.3",
"@eslint/js": "^9.3.0", "@eslint/js": "^9.9.0",
"@faker-js/faker": "^8.4.1", "@faker-js/faker": "^8.4.1",
"@iconify-icons/ep": "^1.2.12", "@iconify-icons/ep": "^1.2.12",
"@iconify-icons/ri": "^1.2.10", "@iconify-icons/ri": "^1.2.10",
@@ -123,52 +123,54 @@
"@types/gradient-string": "^1.1.6", "@types/gradient-string": "^1.1.6",
"@types/intro.js": "^5.1.5", "@types/intro.js": "^5.1.5",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/node": "^20.12.13", "@types/node": "^20.14.15",
"@types/nprogress": "^0.2.3", "@types/nprogress": "^0.2.3",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@types/qs": "^6.9.15", "@types/qs": "^6.9.15",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^7.11.0", "@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.11.0", "@typescript-eslint/parser": "^7.18.0",
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.1.2",
"@vitejs/plugin-vue-jsx": "^3.1.0", "@vitejs/plugin-vue-jsx": "^4.0.0",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.20",
"boxen": "^7.1.1", "boxen": "^7.1.1",
"cssnano": "^7.0.1", "cssnano": "^7.0.5",
"dagre": "^0.8.5", "dagre": "^0.8.5",
"eslint": "^9.3.0", "eslint": "^9.9.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0", "eslint-define-config": "^2.1.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.26.0", "eslint-plugin-vue": "^9.27.0",
"gradient-string": "^2.0.2", "gradient-string": "^2.0.2",
"husky": "^9.0.11", "husky": "^9.1.4",
"lint-staged": "^15.2.5", "lint-staged": "^15.2.8",
"postcss": "^8.4.38", "postcss": "^8.4.41",
"postcss-html": "^1.7.0", "postcss-html": "^1.7.0",
"postcss-import": "^16.1.0", "postcss-import": "^16.1.0",
"postcss-scss": "^4.0.9", "postcss-scss": "^4.0.9",
"prettier": "^3.2.5", "prettier": "^3.3.3",
"rimraf": "^5.0.7", "rimraf": "^5.0.10",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.77.4", "sass": "^1.77.8",
"stylelint": "^16.6.1", "stylelint": "^16.8.1",
"stylelint-config-recess-order": "^5.0.1", "stylelint-config-recess-order": "^5.0.1",
"stylelint-config-recommended-vue": "^1.5.0", "stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard-scss": "^13.1.0", "stylelint-config-standard-scss": "^13.1.0",
"stylelint-prettier": "^5.0.0", "stylelint-prettier": "^5.0.2",
"svgo": "^3.3.2", "svgo": "^3.3.2",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.9",
"typescript": "^5.4.5", "typescript": "^5.5.4",
"vite": "^5.2.12", "vite": "^5.4.0",
"vite-plugin-cdn-import": "^1.0.1", "vite-plugin-cdn-import": "^1.0.1",
"vite-plugin-checker": "^0.7.2",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-fake-server": "^2.1.1", "vite-plugin-fake-server": "^2.1.1",
"vite-plugin-remove-console": "^2.2.0", "vite-plugin-remove-console": "^2.2.0",
"vite-plugin-router-warn": "^1.0.0", "vite-plugin-router-warn": "^1.0.0",
"vite-plugin-vue-inspector": "^5.1.3",
"vite-svg-loader": "^5.1.0", "vite-svg-loader": "^5.1.0",
"vue-eslint-parser": "^9.4.2", "vue-eslint-parser": "^9.4.3",
"vue-tsc": "^1.8.27" "vue-tsc": "^2.0.29"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0", "node": "^18.18.0 || ^20.9.0 || >=21.1.0",
@@ -176,11 +178,17 @@
}, },
"pnpm": { "pnpm": {
"allowedDeprecatedVersions": { "allowedDeprecatedVersions": {
"are-we-there-yet": "*",
"sourcemap-codec": "*", "sourcemap-codec": "*",
"domexception": "*", "domexception": "*",
"w3c-hr-time": "*", "w3c-hr-time": "*",
"inflight": "*",
"npmlog": "*",
"rimraf": "*",
"stable": "*", "stable": "*",
"abab": "*" "gauge": "*",
"abab": "*",
"glob": "*"
}, },
"peerDependencyRules": { "peerDependencyRules": {
"allowedVersions": { "allowedVersions": {

3946
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -11,6 +11,8 @@ export type UserResult = {
nickname: string; nickname: string;
/** 当前登录用户的角色 */ /** 当前登录用户的角色 */
roles: Array<string>; roles: Array<string>;
/** 按钮级别权限 */
permissions: Array<string>;
/** `token` */ /** `token` */
accessToken: string; accessToken: string;
/** 用于调用刷新`accessToken`的接口时所需的`token` */ /** 用于调用刷新`accessToken`的接口时所需的`token` */

View File

@@ -15,6 +15,7 @@ defineOptions({
name: "ReDialog" name: "ReDialog"
}); });
const sureBtnMap = ref({});
const fullscreen = ref(false); const fullscreen = ref(false);
const footerButtons = computed(() => { const footerButtons = computed(() => {
@@ -43,10 +44,26 @@ const footerButtons = computed(() => {
bg: true, bg: true,
popconfirm: options?.popconfirm, popconfirm: options?.popconfirm,
btnClick: ({ dialog: { options, index } }) => { 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" }); closeDialog(options, index, { command: "sure" });
};
if (options?.beforeSure && isFunction(options?.beforeSure)) { if (options?.beforeSure && isFunction(options?.beforeSure)) {
options.beforeSure(done, { options, index }); options.beforeSure(done, { options, index, closeLoading });
} else { } else {
done(); done();
} }
@@ -172,6 +189,7 @@ function handleClose(
<el-button <el-button
v-else v-else
v-bind="btn" v-bind="btn"
:loading="key === 1 && sureBtnMap[index]?.loading"
@click=" @click="
btn.btnClick({ btn.btnClick({
dialog: { options, index }, dialog: { options, index },

View File

@@ -69,11 +69,11 @@ type DialogProps = {
type Popconfirm = { type Popconfirm = {
/** 标题 */ /** 标题 */
title?: string; title?: string;
/** 确按钮文字 */ /** 确按钮文字 */
confirmButtonText?: string; confirmButtonText?: string;
/** 取消按钮文字 */ /** 取消按钮文字 */
cancelButtonText?: string; cancelButtonText?: string;
/** 确按钮类型,默认 `primary` */ /** 确按钮类型,默认 `primary` */
confirmButtonType?: ButtonType; confirmButtonType?: ButtonType;
/** 取消按钮类型,默认 `text` */ /** 取消按钮类型,默认 `text` */
cancelButtonType?: ButtonType; cancelButtonType?: ButtonType;
@@ -121,7 +121,7 @@ type ButtonProps = {
round?: boolean; round?: boolean;
/** 是否为圆形按钮,默认 `false` */ /** 是否为圆形按钮,默认 `false` */
circle?: boolean; circle?: boolean;
/** 确按钮的 `Popconfirm` 气泡确认框相关配置 */ /** 确按钮的 `Popconfirm` 气泡确认框相关配置 */
popconfirm?: Popconfirm; popconfirm?: Popconfirm;
/** 是否为加载中状态,默认 `false` */ /** 是否为加载中状态,默认 `false` */
loading?: boolean; loading?: boolean;
@@ -160,8 +160,10 @@ interface DialogOptions extends DialogProps {
props?: any; props?: any;
/** 是否隐藏 `Dialog` 按钮操作区的内容 */ /** 是否隐藏 `Dialog` 按钮操作区的内容 */
hideFooter?: boolean; hideFooter?: boolean;
/** 确按钮的 `Popconfirm` 气泡确认框相关配置 */ /** 确按钮的 `Popconfirm` 气泡确认框相关配置 */
popconfirm?: Popconfirm; popconfirm?: Popconfirm;
/** 点击确定按钮后是否开启 `loading` 加载动画 */
sureBtnLoading?: boolean;
/** /**
* @description 自定义对话框标题的内容渲染器 * @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} * @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, done: Function,
{ {
options, options,
index index,
closeLoading
}: { }: {
options: DialogOptions; options: DialogOptions;
index: number; index: number;
/** 关闭确定按钮的 `loading` 加载动画 */
closeLoading: Function;
} }
) => void; ) => void;
} }

View File

@@ -194,7 +194,7 @@ watch(
:pager-count="5" :pager-count="5"
layout="pager" layout="pager"
background background
small size="small"
@current-change="onCurrentChange" @current-change="onCurrentChange"
/> />
<el-button <el-button

View File

@@ -0,0 +1,5 @@
import perms from "./src/perms";
const Perms = perms;
export { Perms };

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

View File

@@ -18,6 +18,8 @@ import {
getKeyList getKeyList
} from "@pureadmin/utils"; } 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 DragIcon from "@/assets/table-bar/drag.svg?component";
import ExpandIcon from "@/assets/table-bar/expand.svg?component"; import ExpandIcon from "@/assets/table-bar/expand.svg?component";
import RefreshIcon from "@/assets/table-bar/refresh.svg?component"; import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
@@ -57,6 +59,7 @@ export default defineComponent({
const size = ref("default"); const size = ref("default");
const loading = ref(false); const loading = ref(false);
const checkAll = ref(true); const checkAll = ref(true);
const isFullscreen = ref(false);
const isIndeterminate = ref(false); const isIndeterminate = ref(false);
const instance = getCurrentInstance()!; const instance = getCurrentInstance()!;
const isExpandAll = ref(props.isExpandAll); const isExpandAll = ref(props.isExpandAll);
@@ -244,7 +247,18 @@ export default defineComponent({
return () => ( 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"> <div class="flex justify-between w-full h-[60px] p-4">
{slots?.title ? ( {slots?.title ? (
slots.title() slots.title()
@@ -358,6 +372,14 @@ export default defineComponent({
</el-scrollbar> </el-scrollbar>
</div> </div>
</el-popover> </el-popover>
<el-divider direction="vertical" />
<iconifyIconOffline
class={["w-[16px]", iconClass.value]}
icon={isFullscreen.value ? ExitFullscreen : Fullscreen}
v-tippy={isFullscreen.value ? "退出全屏" : "全屏"}
onClick={() => (isFullscreen.value = !isFullscreen.value)}
/>
</div> </div>
</div> </div>
{slots.default({ {slots.default({

View File

@@ -12,6 +12,8 @@ import {
getCurrentInstance getCurrentInstance
} from "vue"; } 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 DragIcon from "@/assets/table-bar/drag.svg?component";
import ExpandIcon from "@/assets/table-bar/expand.svg?component"; import ExpandIcon from "@/assets/table-bar/expand.svg?component";
import RefreshIcon from "@/assets/table-bar/refresh.svg?component"; import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
@@ -55,6 +57,7 @@ export default defineComponent({
const size = ref("small"); const size = ref("small");
const loading = ref(false); const loading = ref(false);
const checkAll = ref(true); const checkAll = ref(true);
const isFullscreen = ref(false);
const isIndeterminate = ref(false); const isIndeterminate = ref(false);
const instance = getCurrentInstance()!; const instance = getCurrentInstance()!;
const isExpandAll = ref(props.isExpandAll); const isExpandAll = ref(props.isExpandAll);
@@ -237,7 +240,18 @@ export default defineComponent({
return () => ( 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"> <div class="flex justify-between w-full h-[60px] p-4">
{slots?.title ? ( {slots?.title ? (
slots.title() slots.title()
@@ -349,6 +363,14 @@ export default defineComponent({
</el-scrollbar> </el-scrollbar>
</div> </div>
</el-popover> </el-popover>
<el-divider direction="vertical" />
<iconifyIconOffline
class={["w-[16px]", iconClass.value]}
icon={isFullscreen.value ? ExitFullscreen : Fullscreen}
v-tippy={isFullscreen.value ? "退出全屏" : "全屏"}
onClick={() => (isFullscreen.value = !isFullscreen.value)}
/>
</div> </div>
</div> </div>
{slots.default({ {slots.default({

View File

@@ -2,7 +2,7 @@ import { hasAuth } from "@/router/utils";
import type { Directive, DirectiveBinding } from "vue"; import type { Directive, DirectiveBinding } from "vue";
export const auth: Directive = { export const auth: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) { mounted(el: HTMLElement, binding: DirectiveBinding<string | Array<string>>) {
const { value } = binding; const { value } = binding;
if (value) { if (value) {
!hasAuth(value) && el.parentNode?.removeChild(el); !hasAuth(value) && el.parentNode?.removeChild(el);

View File

@@ -3,13 +3,13 @@ import { useEventListener } from "@vueuse/core";
import { copyTextToClipboard } from "@pureadmin/utils"; import { copyTextToClipboard } from "@pureadmin/utils";
import type { Directive, DirectiveBinding } from "vue"; import type { Directive, DirectiveBinding } from "vue";
interface CopyEl extends HTMLElement { export interface CopyEl extends HTMLElement {
copyValue: string; copyValue: string;
} }
/** 文本复制指令(默认双击复制) */ /** 文本复制指令(默认双击复制) */
export const copy: Directive = { export const copy: Directive = {
mounted(el: CopyEl, binding: DirectiveBinding) { mounted(el: CopyEl, binding: DirectiveBinding<string>) {
const { value } = binding; const { value } = binding;
if (value) { if (value) {
el.copyValue = value; el.copyValue = value;

View File

@@ -2,4 +2,5 @@ export * from "./auth";
export * from "./copy"; export * from "./copy";
export * from "./longpress"; export * from "./longpress";
export * from "./optimize"; export * from "./optimize";
export * from "./perms";
export * from "./ripple"; export * from "./ripple";

View File

@@ -3,7 +3,7 @@ import type { Directive, DirectiveBinding } from "vue";
import { subBefore, subAfter, isFunction } from "@pureadmin/utils"; import { subBefore, subAfter, isFunction } from "@pureadmin/utils";
export const longpress: Directive = { export const longpress: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) { mounted(el: HTMLElement, binding: DirectiveBinding<Function>) {
const cb = binding.value; const cb = binding.value;
if (cb && isFunction(cb)) { if (cb && isFunction(cb)) {
let timer = null; let timer = null;

View File

@@ -8,9 +8,22 @@ import {
import { useEventListener } from "@vueuse/core"; import { useEventListener } from "@vueuse/core";
import type { Directive, DirectiveBinding } from "vue"; 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指令 */ /** 防抖v-optimize或v-optimize:debounce、节流v-optimize:throttle指令 */
export const optimize: Directive = { export const optimize: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) { mounted(el: HTMLElement, binding: DirectiveBinding<OptimizeOptions>) {
const { value } = binding; const { value } = binding;
const optimizeType = binding.arg ?? "debounce"; const optimizeType = binding.arg ?? "debounce";
const type = ["debounce", "throttle"].find(t => t === optimizeType); const type = ["debounce", "throttle"].find(t => t === optimizeType);

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

View File

@@ -2,8 +2,10 @@ import "./index.scss";
import { isObject } from "@pureadmin/utils"; import { isObject } from "@pureadmin/utils";
import type { Directive, DirectiveBinding } from "vue"; import type { Directive, DirectiveBinding } from "vue";
interface RippleOptions { export interface RippleOptions {
/** 自定义`ripple`颜色,支持`tailwindcss` */
class?: string; class?: string;
/** 是否从中心扩散 */
center?: boolean; center?: boolean;
circle?: boolean; circle?: boolean;
} }
@@ -220,13 +222,6 @@ function updated(el: HTMLElement, binding: RippleDirectiveBinding) {
updateRipple(el, binding, wasEnabled); 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 = { export const Ripple: Directive = {
mounted, mounted,
unmounted, unmounted,

View File

@@ -1,9 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { isAllEmpty } from "@pureadmin/utils"; import { emitter } from "@/utils/mitt";
import { ref, nextTick, computed } from "vue";
import { useNav } from "@/layout/hooks/useNav"; import { useNav } from "@/layout/hooks/useNav";
import LaySearch from "../lay-search/index.vue"; import LaySearch from "../lay-search/index.vue";
import LayNotice from "../lay-notice/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 { useTranslationLang } from "../../hooks/useTranslationLang";
import { usePermissionStoreHook } from "@/store/modules/permission"; import { usePermissionStoreHook } from "@/store/modules/permission";
import LaySidebarItem from "../lay-sidebar/components/SidebarItem.vue"; 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"; import Check from "@iconify-icons/ep/check";
const menuRef = ref(); const menuRef = ref();
const showLogo = ref(
storageLocal().getItem<StorageConfigs>(
`${responsiveStorageNameSpace()}configure`
)?.showLogo ?? true
);
const { t, route, locale, translationCh, translationEn } = const { t, route, locale, translationCh, translationEn } =
useTranslationLang(menuRef); useTranslationLang(menuRef);
@@ -40,6 +47,12 @@ const defaultActive = computed(() =>
nextTick(() => { nextTick(() => {
menuRef.value?.handleResize(); menuRef.value?.handleResize();
}); });
onMounted(() => {
emitter.on("logoChange", key => {
showLogo.value = key;
});
});
</script> </script>
<template> <template>
@@ -47,7 +60,7 @@ nextTick(() => {
v-loading="usePermissionStoreHook().wholeMenus.length === 0" v-loading="usePermissionStoreHook().wholeMenus.length === 0"
class="horizontal-header" 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" /> <img :src="getLogo()" alt="logo" />
<span>{{ title }}</span> <span>{{ title }}</span>
</div> </div>

View File

@@ -37,6 +37,7 @@ const {
buttonLeft, buttonLeft,
showModel, showModel,
translateX, translateX,
isFixedTag,
pureSetting, pureSetting,
activeIndex, activeIndex,
getTabStyle, getTabStyle,
@@ -578,7 +579,7 @@ onBeforeUnmount(() => {
'scroll-item is-closable', 'scroll-item is-closable',
linkIsActive(item), linkIsActive(item),
showModel === 'chrome' && 'chrome-item', showModel === 'chrome' && 'chrome-item',
!isAllEmpty(item?.meta?.fixedTag) && 'fixed-tag' isFixedTag(item) && 'fixed-tag'
]" ]"
@contextmenu.prevent="openMenu(item, $event)" @contextmenu.prevent="openMenu(item, $event)"
@mouseenter.prevent="onMouseenter(index)" @mouseenter.prevent="onMouseenter(index)"
@@ -593,10 +594,10 @@ onBeforeUnmount(() => {
</span> </span>
<span <span
v-if=" v-if="
isAllEmpty(item?.meta?.fixedTag) isFixedTag(item)
? iconIsActive(item, index) || ? false
: iconIsActive(item, index) ||
(index === activeIndex && index !== 0) (index === activeIndex && index !== 0)
: false
" "
class="el-icon-close" class="el-icon-close"
@click.stop="deleteMenu(item)" @click.stop="deleteMenu(item)"
@@ -617,7 +618,7 @@ onBeforeUnmount(() => {
{{ transformI18n(item.meta.title) }} {{ transformI18n(item.meta.title) }}
</span> </span>
<span <span
v-if="isAllEmpty(item?.meta?.fixedTag) ? index !== 0 : false" v-if="isFixedTag(item) ? false : index !== 0"
class="chrome-close-btn" class="chrome-close-btn"
@click.stop="deleteMenu(item)" @click.stop="deleteMenu(item)"
> >

View File

@@ -125,6 +125,12 @@ export function useTags() {
} }
} }
const isFixedTag = computed(() => {
return item => {
return isBoolean(item?.meta?.fixedTag) && item?.meta?.fixedTag === true;
};
});
const iconIsActive = computed(() => { const iconIsActive = computed(() => {
return (item, index) => { return (item, index) => {
if (index === 0) return; if (index === 0) return;
@@ -221,6 +227,7 @@ export function useTags() {
buttonTop, buttonTop,
buttonLeft, buttonLeft,
translateX, translateX,
isFixedTag,
pureSetting, pureSetting,
activeIndex, activeIndex,
getTabStyle, getTabStyle,

View File

@@ -224,7 +224,7 @@ const LayHeader = defineComponent({
.app-mask { .app-mask {
position: absolute; position: absolute;
top: 0; top: 0;
z-index: 999; z-index: 2001;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #000; background: #000;

View File

@@ -44,7 +44,9 @@ app.component("FontIcon", FontIcon);
// 全局注册按钮级别权限组件 // 全局注册按钮级别权限组件
import { Auth } from "@/components/ReAuth"; import { Auth } from "@/components/ReAuth";
import { Perms } from "@/components/RePerms";
app.component("Auth", Auth); app.component("Auth", Auth);
app.component("Perms", Perms);
// 全局注册vue-tippy // 全局注册vue-tippy
import "tippy.js/dist/tippy.css"; import "tippy.js/dist/tippy.css";

View File

@@ -107,6 +107,7 @@ import {
ElWatermark, ElWatermark,
ElTour, ElTour,
ElTourStep, ElTourStep,
ElSegmented,
/** /**
* 为了方便演示平台将 element-plus 导出的所有插件引入,实际使用中如果你没用到哪个插件,将其注释掉就行 * 为了方便演示平台将 element-plus 导出的所有插件引入,实际使用中如果你没用到哪个插件,将其注释掉就行
* 导出来源https://github.com/element-plus/element-plus/blob/dev/packages/element-plus/plugin.ts#L11-L16 * 导出来源https://github.com/element-plus/element-plus/blob/dev/packages/element-plus/plugin.ts#L11-L16
@@ -221,7 +222,8 @@ const components = [
ElUpload, ElUpload,
ElWatermark, ElWatermark,
ElTour, ElTour,
ElTourStep ElTourStep,
ElSegmented
]; ];
const plugins = [ const plugins = [

View File

@@ -355,7 +355,7 @@ function getAuths(): Array<string> {
return router.currentRoute.value.meta.auths as Array<string>; return router.currentRoute.value.meta.auths as Array<string>;
} }
/** 是否有按钮级别的权限 */ /** 是否有按钮级别的权限(根据路由`meta`中的`auths`字段进行判断)*/
function hasAuth(value: string | Array<string>): boolean { function hasAuth(value: string | Array<string>): boolean {
if (!value) return false; if (!value) return false;
/** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */ /** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */

View File

@@ -27,6 +27,9 @@ export const useUserStore = defineStore({
nickname: storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "", nickname: storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "",
// 页面级别权限 // 页面级别权限
roles: storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [], roles: storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [],
// 按钮级别权限
permissions:
storageLocal().getItem<DataInfo<number>>(userKey)?.permissions ?? [],
// 前端生成的验证码(按实际需求替换) // 前端生成的验证码(按实际需求替换)
verifyCode: "", verifyCode: "",
// 判断登录页面显示哪个组件0登录默认、1手机登录、2二维码登录、3注册、4忘记密码 // 判断登录页面显示哪个组件0登录默认、1手机登录、2二维码登录、3注册、4忘记密码
@@ -53,6 +56,10 @@ export const useUserStore = defineStore({
SET_ROLES(roles: Array<string>) { SET_ROLES(roles: Array<string>) {
this.roles = roles; this.roles = roles;
}, },
/** 存储按钮级别权限 */
SET_PERMS(permissions: Array<string>) {
this.permissions = permissions;
},
/** 存储前端生成的验证码 */ /** 存储前端生成的验证码 */
SET_VERIFYCODE(verifyCode: string) { SET_VERIFYCODE(verifyCode: string) {
this.verifyCode = verifyCode; this.verifyCode = verifyCode;
@@ -86,6 +93,7 @@ export const useUserStore = defineStore({
logOut() { logOut() {
this.username = ""; this.username = "";
this.roles = []; this.roles = [];
this.permissions = [];
removeToken(); removeToken();
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]); useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
resetRouter(); resetRouter();

View File

@@ -42,6 +42,7 @@ export type userType = {
username?: string; username?: string;
nickname?: string; nickname?: string;
roles?: Array<string>; roles?: Array<string>;
permissions?: Array<string>;
verifyCode?: string; verifyCode?: string;
currentPage?: number; currentPage?: number;
isRemembered?: boolean; isRemembered?: boolean;

View File

@@ -571,6 +571,7 @@
} }
.sidebar-container { .sidebar-container {
z-index: 2001;
width: $sideBarWidth; width: $sideBarWidth;
transition: transform var(--pure-transition-duration); transition: transform var(--pure-transition-duration);
} }

View File

@@ -1,6 +1,6 @@
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { storageLocal } from "@pureadmin/utils";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
import { storageLocal, isString, isIncludeAllChildren } from "@pureadmin/utils";
export interface DataInfo<T> { export interface DataInfo<T> {
/** token */ /** token */
@@ -17,6 +17,8 @@ export interface DataInfo<T> {
nickname?: string; nickname?: string;
/** 当前登录用户的角色 */ /** 当前登录用户的角色 */
roles?: Array<string>; roles?: Array<string>;
/** 当前登录用户的按钮级别权限 */
permissions?: Array<string>;
} }
export const userKey = "user-info"; export const userKey = "user-info";
@@ -41,7 +43,7 @@ export function getToken(): DataInfo<number> {
* @description 设置`token`以及一些必要信息并采用无感刷新`token`方案 * @description 设置`token`以及一些必要信息并采用无感刷新`token`方案
* 无感刷新:后端返回`accessToken`(访问接口使用的`token`)、`refreshToken`(用于调用刷新`accessToken`的接口时所需的`token``refreshToken`的过期时间比如30天应大于`accessToken`的过期时间比如2小时、`expires``accessToken`的过期时间) * 无感刷新:后端返回`accessToken`(访问接口使用的`token`)、`refreshToken`(用于调用刷新`accessToken`的接口时所需的`token``refreshToken`的过期时间比如30天应大于`accessToken`的过期时间比如2小时、`expires``accessToken`的过期时间)
* 将`accessToken`、`expires`、`refreshToken`这三条信息放在key值为authorized-token的cookie里过期自动销毁 * 将`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>) { export function setToken(data: DataInfo<Date>) {
let expires = 0; 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_AVATAR(avatar);
useUserStoreHook().SET_USERNAME(username); useUserStoreHook().SET_USERNAME(username);
useUserStoreHook().SET_NICKNAME(nickname); useUserStoreHook().SET_NICKNAME(nickname);
useUserStoreHook().SET_ROLES(roles); useUserStoreHook().SET_ROLES(roles);
useUserStoreHook().SET_PERMS(permissions);
storageLocal().setItem(userKey, { storageLocal().setItem(userKey, {
refreshToken, refreshToken,
expires, expires,
avatar, avatar,
username, username,
nickname, nickname,
roles roles,
permissions
}); });
} }
@@ -87,7 +91,8 @@ export function setToken(data: DataInfo<Date>) {
avatar: data?.avatar ?? "", avatar: data?.avatar ?? "",
username, username,
nickname: data?.nickname ?? "", nickname: data?.nickname ?? "",
roles roles,
permissions: data?.permissions ?? []
}); });
} else { } else {
const avatar = const avatar =
@@ -98,11 +103,14 @@ export function setToken(data: DataInfo<Date>) {
storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? ""; storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "";
const roles = const roles =
storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? []; storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [];
const permissions =
storageLocal().getItem<DataInfo<number>>(userKey)?.permissions ?? [];
setUserKey({ setUserKey({
avatar, avatar,
username, username,
nickname, nickname,
roles roles,
permissions
}); });
} }
} }
@@ -118,3 +126,16 @@ export function removeToken() {
export const formatToken = (token: string): string => { export const formatToken = (token: string): string => {
return "Bearer " + token; 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;
};

View File

@@ -450,6 +450,19 @@ function onBeforeSureClick() {
} }
}); });
} }
function onSureBtnLoading() {
addDialog({
sureBtnLoading: true,
title: "点击底部确定按钮可开启按钮动画",
contentRenderer: () => <p>弹框内容-点击底部确定按钮可开启按钮动画</p>,
beforeSure: (done, { closeLoading }) => {
// closeLoading() // 关闭确定按钮动画,不关闭弹框
// done() // 关闭确定按钮动画并关闭弹框
setTimeout(() => done(), 800);
}
});
}
</script> </script>
<template> <template>
@@ -534,6 +547,9 @@ function onBeforeSureClick() {
<el-button @click="onBeforeSureClick"> <el-button @click="onBeforeSureClick">
点击底部确定按钮的回调会暂停弹框的关闭经常用于新增修改弹框内容后调用接口 点击底部确定按钮的回调会暂停弹框的关闭经常用于新增修改弹框内容后调用接口
</el-button> </el-button>
<el-button @click="onSureBtnLoading">
点击底部确定按钮可开启按钮动画
</el-button>
</el-space> </el-space>
</el-card> </el-card>
</template> </template>

View File

@@ -99,7 +99,7 @@ watch(size, val =>
<el-button <el-button
v-for="(button, index) in buttonList" v-for="(button, index) in buttonList"
:key="index" :key="index"
:type="button.type" :type="button.type as any"
:size="dynamicSize" :size="dynamicSize"
:disabled="size === 'disabled'" :disabled="size === 'disabled'"
:plain="baseRadio === 'plain'" :plain="baseRadio === 'plain'"

View File

@@ -97,7 +97,11 @@ const immediateDebounce: any = debounce(
); );
useEventListener(document, "keypress", ({ code }) => { useEventListener(document, "keypress", ({ code }) => {
if (code === "Enter" && !disabled.value && !loading.value) if (
["Enter", "NumpadEnter"].includes(code) &&
!disabled.value &&
!loading.value
)
immediateDebounce(ruleFormRef.value); immediateDebounce(ruleFormRef.value);
}); });

View File

@@ -133,8 +133,7 @@ const {
:adaptiveConfig="{ offsetBottom: 108 }" :adaptiveConfig="{ offsetBottom: 108 }"
:data="dataList" :data="dataList"
:columns="dynamicColumns" :columns="dynamicColumns"
:pagination="pagination" :pagination="{ ...pagination, size }"
:paginationSmall="size === 'small' ? true : false"
:header-cell-style="{ :header-cell-style="{
background: 'var(--el-fill-color-light)', background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)' color: 'var(--el-text-color-primary)'

View File

@@ -133,8 +133,7 @@ const {
:adaptiveConfig="{ offsetBottom: 108 }" :adaptiveConfig="{ offsetBottom: 108 }"
:data="dataList" :data="dataList"
:columns="dynamicColumns" :columns="dynamicColumns"
:pagination="pagination" :pagination="{ ...pagination, size }"
:paginationSmall="size === 'small' ? true : false"
:header-cell-style="{ :header-cell-style="{
background: 'var(--el-fill-color-light)', background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)' color: 'var(--el-text-color-primary)'

View File

@@ -125,8 +125,7 @@ const {
:adaptiveConfig="{ offsetBottom: 108 }" :adaptiveConfig="{ offsetBottom: 108 }"
:data="dataList" :data="dataList"
:columns="dynamicColumns" :columns="dynamicColumns"
:pagination="pagination" :pagination="{ ...pagination, size }"
:paginationSmall="size === 'small' ? true : false"
:header-cell-style="{ :header-cell-style="{
background: 'var(--el-fill-color-light)', background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)' color: 'var(--el-text-color-primary)'

View File

@@ -74,8 +74,7 @@ const {
:adaptiveConfig="{ offsetBottom: 108 }" :adaptiveConfig="{ offsetBottom: 108 }"
:data="dataList" :data="dataList"
:columns="dynamicColumns" :columns="dynamicColumns"
:pagination="pagination" :pagination="{ ...pagination, size }"
:paginationSmall="size === 'small' ? true : false"
:header-cell-style="{ :header-cell-style="{
background: 'var(--el-fill-color-light)', background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)' color: 'var(--el-text-color-primary)'

View File

@@ -2,7 +2,7 @@
import { hasAuth, getAuths } from "@/router/utils"; import { hasAuth, getAuths } from "@/router/utils";
defineOptions({ defineOptions({
name: "PermissionButton" name: "PermissionButtonRouter"
}); });
</script> </script>

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

View File

@@ -173,8 +173,7 @@ onMounted(() => {
:adaptiveConfig="{ offsetBottom: 108 }" :adaptiveConfig="{ offsetBottom: 108 }"
:data="dataList" :data="dataList"
:columns="dynamicColumns" :columns="dynamicColumns"
:pagination="pagination" :pagination="{ ...pagination, size }"
:paginationSmall="size === 'small' ? true : false"
:header-cell-style="{ :header-cell-style="{
background: 'var(--el-fill-color-light)', background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)' color: 'var(--el-text-color-primary)'
@@ -264,7 +263,7 @@ onMounted(() => {
<div <div
v-if="isShow" v-if="isShow"
class="!min-w-[calc(100vw-60vw-268px)] mt-2 px-2 pb-2 bg-bg_color ml-2 overflow-auto" class="!min-w-[calc(100vw-60vw-268px)] w-full mt-2 px-2 pb-2 bg-bg_color ml-2 overflow-auto"
> >
<div class="flex justify-between w-full px-3 pt-5 pb-4"> <div class="flex justify-between w-full px-3 pt-5 pb-4">
<div class="flex"> <div class="flex">

View File

@@ -160,8 +160,7 @@ const {
:size="size" :size="size"
:data="dataList" :data="dataList"
:columns="dynamicColumns" :columns="dynamicColumns"
:pagination="pagination" :pagination="{ ...pagination, size }"
:paginationSmall="size === 'small' ? true : false"
:header-cell-style="{ :header-cell-style="{
background: 'var(--el-fill-color-light)', background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)' color: 'var(--el-text-color-primary)'

View File

@@ -116,7 +116,11 @@ const columns: TableColumnList = [
border border
lazy lazy
:load="load" :load="load"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }" :tree-props="{
hasChildren: 'hasChildren',
children: 'children',
checkStrictly: false
}"
/> />
</div> </div>
</template> </template>

View File

@@ -33,7 +33,7 @@ export function useColumns() {
total: 0, total: 0,
align: "right", align: "right",
background: true, background: true,
small: false size: "default"
}); });
/** 加载动画配置 */ /** 加载动画配置 */

View File

@@ -9,7 +9,6 @@ export function useColumns() {
const select = ref("no"); const select = ref("no");
const hideVal = ref("nohide"); const hideVal = ref("nohide");
const tableSize = ref("default"); const tableSize = ref("default");
const paginationSmall = ref(false);
const paginationAlign = ref("right"); const paginationAlign = ref("right");
const columns: TableColumnList = [ const columns: TableColumnList = [
{ {
@@ -43,7 +42,7 @@ export function useColumns() {
total: 0, total: 0,
align: "right", align: "right",
background: true, background: true,
small: false size: "default"
}); });
/** 加载动画配置 */ /** 加载动画配置 */
@@ -65,7 +64,7 @@ export function useColumns() {
}); });
function onChange(val) { function onChange(val) {
pagination.small = val; pagination.size = val;
} }
function onSizeChange(val) { function onSizeChange(val) {
@@ -108,7 +107,6 @@ export function useColumns() {
pagination, pagination,
loadingConfig, loadingConfig,
paginationAlign, paginationAlign,
paginationSmall,
onChange, onChange,
onSizeChange, onSizeChange,
onCurrentChange onCurrentChange

View File

@@ -11,7 +11,6 @@ const {
pagination, pagination,
loadingConfig, loadingConfig,
paginationAlign, paginationAlign,
paginationSmall,
onChange, onChange,
onSizeChange, onSizeChange,
onCurrentChange onCurrentChange
@@ -43,9 +42,10 @@ const {
</el-radio-group> </el-radio-group>
<el-divider direction="vertical" /> <el-divider direction="vertical" />
<p class="text-sm">分页大小</p> <p class="text-sm">分页大小</p>
<el-radio-group v-model="paginationSmall" size="small" @change="onChange"> <el-radio-group v-model="pagination.size" size="small" @change="onChange">
<el-radio-button :value="false">no small</el-radio-button> <el-radio-button value="large">large</el-radio-button>
<el-radio-button :value="true">small</el-radio-button> <el-radio-button value="default">default</el-radio-button>
<el-radio-button value="small">small</el-radio-button>
</el-radio-group> </el-radio-group>
<el-divider direction="vertical" /> <el-divider direction="vertical" />
<p class="text-sm">分页的对齐方式</p> <p class="text-sm">分页的对齐方式</p>

View File

@@ -59,7 +59,7 @@ export function useColumns(selectRef: Ref, formRef: Ref, tableRef: Ref) {
layout: "prev, pager, next", layout: "prev, pager, next",
total: tableData.value.length, total: tableData.value.length,
background: true, background: true,
small: true size: "small"
}); });
const handleSelectionChange = val => { const handleSelectionChange = val => {

View File

@@ -33,7 +33,7 @@ export function useColumns(selectRef: Ref) {
layout: "prev, pager, next", layout: "prev, pager, next",
total: tableDataEdit.length, total: tableDataEdit.length,
background: true, background: true,
small: true size: "small"
}); });
/** 高亮当前选中行 */ /** 高亮当前选中行 */

28
types/directives.d.ts vendored Normal file
View File

@@ -0,0 +1,28 @@
import type { Directive } from "vue";
import type { CopyEl, OptimizeOptions, RippleOptions } from "@/directives";
declare module "vue" {
export interface ComponentCustomProperties {
/** `Loading` 动画加载指令具体看https://element-plus.org/zh-CN/component/loading.html#%E6%8C%87%E4%BB%A4 */
vLoading: Directive<Element, boolean>;
/** 按钮权限指令(根据路由`meta`中的`auths`字段进行判断)*/
vAuth: Directive<HTMLElement, string | Array<string>>;
/** 文本复制指令(默认双击复制) */
vCopy: Directive<CopyEl, string>;
/** 长按指令 */
vLongpress: Directive<HTMLElement, Function>;
/** 防抖、节流指令 */
vOptimize: Directive<HTMLElement, OptimizeOptions>;
/** 按钮权限指令(根据登录接口返回的`permissions`字段进行判断)*/
vPerms: Directive<HTMLElement, string | Array<string>>;
/**
* `v-ripple`指令,用法如下:
* 1. `v-ripple`代表启用基本的`ripple`功能
* 2. `v-ripple="{ class: 'text-red' }"`代表自定义`ripple`颜色,支持`tailwindcss`,生效样式是`color`
* 3. `v-ripple.center`代表从中心扩散
*/
vRipple: Directive<HTMLElement, RippleOptions>;
}
}
export {};

View File

@@ -7,6 +7,7 @@ declare module "vue" {
IconifyIconOnline: (typeof import("../src/components/ReIcon"))["IconifyIconOnline"]; IconifyIconOnline: (typeof import("../src/components/ReIcon"))["IconifyIconOnline"];
FontIcon: (typeof import("../src/components/ReIcon"))["FontIcon"]; FontIcon: (typeof import("../src/components/ReIcon"))["FontIcon"];
Auth: (typeof import("../src/components/ReAuth"))["Auth"]; Auth: (typeof import("../src/components/ReAuth"))["Auth"];
Perms: (typeof import("../src/components/RePerms"))["Perms"];
} }
} }
@@ -115,6 +116,7 @@ declare module "vue" {
ElWatermark: (typeof import("element-plus"))["ElWatermark"]; ElWatermark: (typeof import("element-plus"))["ElWatermark"];
ElTour: (typeof import("element-plus"))["ElTour"]; ElTour: (typeof import("element-plus"))["ElTour"];
ElTourStep: (typeof import("element-plus"))["ElTourStep"]; ElTourStep: (typeof import("element-plus"))["ElTourStep"];
ElSegmented: (typeof import("element-plus"))["ElSegmented"];
} }
interface ComponentCustomProperties { interface ComponentCustomProperties {