mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-11-03 13:44:47 +08:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7e799bfc7 | ||
|
|
79e91b7b13 | ||
|
|
58cafbc73f | ||
|
|
5d86b714a4 | ||
|
|
aec2a35424 | ||
|
|
3fd9b15698 | ||
|
|
d850496601 | ||
|
|
c06ce94746 | ||
|
|
ba2ec8aca2 | ||
|
|
f971cd5b30 | ||
|
|
39833ce917 | ||
|
|
56368c1163 | ||
|
|
3471e4a7e2 | ||
|
|
f613a79def | ||
|
|
04611d8b24 | ||
|
|
da6c2628d5 | ||
|
|
88a44f29d0 | ||
|
|
315f78a825 | ||
|
|
585adefbdd | ||
|
|
abf076c9c6 |
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@@ -3,6 +3,7 @@
|
||||
"christian-kohler.path-intellisense",
|
||||
"vscode-icons-team.vscode-icons",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"stylelint.vscode-stylelint",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"dbaeumer.vscode-eslint",
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
# 4.4.0 (2023-06-14)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Route `meta` adds `activePath` attribute, which can activate a menu (mainly used for routes that pass parameters through `query` or `params`, when they are not displayed in the menu after configuring `showLink: false`, they will be There will be no menu highlighting, but you can get highlighting by setting `activePath` to specify the active menu, `activePath` is the `path` of the specified active menu [View details](https://github.com/pure-admin/vue-pure-admin/commit/58cafbc73ffa27253446ee93077e1e382519ce8a#commitcomment-117834411))
|
||||
- Example of advanced usage of `pure-admin-table` to add adaptive content area height
|
||||
- Add anti-shake, throttling and text copy instructions and standardize the prompts when custom instructions are used incorrectly and add usage examples
|
||||
- Add `el-empty` component when the `notice` message prompts the component to have empty data
|
||||
- Example code of functional popup window adding subcomponent `prop` as `primitive` type example
|
||||
- Add `vscode-docker` plugin
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fix internationalization switch to English mode and refresh will return to Chinese mode
|
||||
- Fixed the problem that the pop-up mask of the search menu function did not cover the left menu
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Page switching performance optimization, regardless of the network, the speed of page switching logic is almost `3-4` times faster than before [View optimization details](https://github.com/pure-admin/vue-pure-admin/pull/600#issuecomment-1586094078)
|
||||
-Optimized tab page operation-routing parameter transfer mode usage
|
||||
- All tables in the system management are changed to adaptive content area height, need to upgrade `@pureadmin/table` to the latest version
|
||||
- Use the `useResizeObserver` function of `vueuse` to replace the `v-resize` custom directive, and the performance will be better from the performance after testing
|
||||
- For unbound public events, unbind when the page is destroyed
|
||||
|
||||
# 4.3.0 (2023-06-04)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,3 +1,27 @@
|
||||
# 4.4.0 (2023-06-14)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Route `meta` adds `activePath` attribute, which can activate a menu (mainly used for routes that pass parameters through `query` or `params`, when they are not displayed in the menu after configuring `showLink: false`, they will be There will be no menu highlighting, but you can get highlighting by setting `activePath` to specify the active menu, `activePath` is the `path` of the specified active menu [View details](https://github.com/pure-admin/vue-pure-admin/commit/58cafbc73ffa27253446ee93077e1e382519ce8a#commitcomment-117834411))
|
||||
- Example of advanced usage of `pure-admin-table` to add adaptive content area height
|
||||
- Add anti-shake, throttling and text copy instructions and standardize the prompts when custom instructions are used incorrectly and add usage examples
|
||||
- Add `el-empty` component when the `notice` message prompts the component to have empty data
|
||||
- Example code of functional popup window adding subcomponent `prop` as `primitive` type example
|
||||
- Add `vscode-docker` plugin
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fix internationalization switch to English mode and refresh will return to Chinese mode
|
||||
- Fixed the problem that the pop-up mask of the search menu function did not cover the left menu
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Page switching performance optimization, regardless of the network, the speed of page switching logic is almost `3-4` times faster than before [View optimization details](https://github.com/pure-admin/vue-pure-admin/pull/600#issuecomment-1586094078)
|
||||
-Optimized tab page operation-routing parameter transfer mode usage
|
||||
- All tables in the system management are changed to adaptive content area height, need to upgrade `@pureadmin/table` to the latest version
|
||||
- Use the `useResizeObserver` function of `vueuse` to replace the `v-resize` custom directive, and the performance will be better from the performance after testing
|
||||
- For unbound public events, unbind when the page is destroyed
|
||||
|
||||
# 4.3.0 (2023-06-04)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
# 4.4.0 (2023-06-14)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- 路由 `meta` 添加 `activePath` 属性,可将某个菜单激活(主要用于通过 `query` 或 `params` 传参的路由,当它们通过配置 `showLink: false` 后不在菜单中显示,就不会有任何菜单高亮,而通过设置 `activePath` 指定激活菜单即可获得高亮,`activePath` 为指定激活菜单的 `path` [查看详情](https://github.com/pure-admin/vue-pure-admin/commit/58cafbc73ffa27253446ee93077e1e382519ce8a#commitcomment-117834411))
|
||||
- `pure-admin-table` 高级用法添加自适应内容区高度示例
|
||||
- 添加防抖、节流和文本复制指令并规范自定义指令用法错误时的提示以及添加使用示例
|
||||
- `notice` 消息提示组件空数据时添加 `el-empty` 组件
|
||||
- 函数式弹窗示例代码添加子组件 `prop` 为 `primitive` 类型的示例
|
||||
- 添加 `vscode-docker` 插件
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- 修复国际化切换到英文模式刷新会回到中文模式
|
||||
- 修复搜索菜单功能的弹框遮罩未覆盖左侧菜单的问题
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- 页面切换性能优化,不考虑网络的情况下,页面切换逻辑的速度差不多比之前快 `3-4` 倍 [查看优化详情](https://github.com/pure-admin/vue-pure-admin/pull/600#issuecomment-1586094078)
|
||||
- 优化标签页操作-路由传参模式用法
|
||||
- 系统管理中表格均改为自适应内容区高度,需将 `@pureadmin/table` 升级到最新版
|
||||
- 使用 `vueuse` 的 `useResizeObserver` 函数替换 `v-resize` 自定义指令,从测试后的表现来看性能会更好
|
||||
- 对未解绑的公共事件,在页面销毁时解绑
|
||||
|
||||
# 4.3.0 (2023-06-04)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
@@ -33,7 +33,6 @@ const include = [
|
||||
"@howdyjs/mouse-menu",
|
||||
"@logicflow/extension",
|
||||
"vue-virtual-scroller",
|
||||
"element-resize-detector",
|
||||
"@amap/amap-jsapi-loader",
|
||||
"el-table-infinite-scroll",
|
||||
"vue-waterfall-plugin-next",
|
||||
|
||||
@@ -68,6 +68,7 @@ menus:
|
||||
hsguide: Guide
|
||||
hsAble: Able
|
||||
hsMenuTree: Menu Tree
|
||||
hsOptimize: Debounce、Throttle、Copy Directives
|
||||
hsWatermark: Water Mark
|
||||
hsPrint: Print
|
||||
hsDownload: Download
|
||||
|
||||
@@ -68,6 +68,7 @@ menus:
|
||||
hsguide: 引导页
|
||||
hsAble: 功能
|
||||
hsMenuTree: 菜单树结构
|
||||
hsOptimize: 防抖、截流、复制指令
|
||||
hsWatermark: 水印
|
||||
hsPrint: 打印
|
||||
hsDownload: 下载
|
||||
@@ -101,7 +102,7 @@ menus:
|
||||
hsInfiniteScroll: 表格无限滚动
|
||||
hsdanmaku: 弹幕组件
|
||||
hsPureTableBase: 基础用法(23个示例)
|
||||
hsPureTableHigh: 高级用法(10个示例)
|
||||
hsPureTableHigh: 高级用法(11个示例)
|
||||
hsTree: 大数据树业务组件
|
||||
hsMenuoverflow: 目录超出显示 Tooltip 文字提示
|
||||
hsChildMenuoverflow: 菜单超出显示 Tooltip 文字提示
|
||||
|
||||
@@ -179,6 +179,7 @@ const tabsRouter = {
|
||||
meta: {
|
||||
// 不在menu菜单中显示
|
||||
showLink: false,
|
||||
activePath: "/tabs/index",
|
||||
roles: ["admin", "common"]
|
||||
}
|
||||
},
|
||||
@@ -190,6 +191,7 @@ const tabsRouter = {
|
||||
meta: {
|
||||
// 不在menu菜单中显示
|
||||
showLink: false,
|
||||
activePath: "/tabs/index",
|
||||
roles: ["admin", "common"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,6 +453,7 @@ export default [
|
||||
}
|
||||
},
|
||||
{
|
||||
// https://api.github.com/repos/pure-admin/vue-pure-admin/releases?per_page=100
|
||||
url: "/releases",
|
||||
method: "get",
|
||||
response: () => {
|
||||
@@ -460,6 +461,11 @@ export default [
|
||||
success: true,
|
||||
data: {
|
||||
list: [
|
||||
{
|
||||
created_at: "2023-06-04T04:11:51Z",
|
||||
published_at: "2023-06-04T04:13:24Z",
|
||||
body: "# 4.3.0 (2023-06-04)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加 `docker` 支持\r\n- 添加项目版本实时更新检测功能\r\n- 完善系统管理-角色管理页面\r\n- 瀑布流组件添加无限滚动\r\n- 函数式弹框添加 `updateDialog` 更改弹框自身属性值方法\r\n- `wangeditor` 富文本添加多个富文本和自定义图片上传示例\r\n- `pure-table` 表格高级用法添加保留已选中的 `CheckBox` 选项示例\r\n- `RePureTableBar` 组件添加 `title` 插槽\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复获取验证码倒计时会有 `1s` 延时禁用的问题\r\n- 修复图标选择器未正确初始化预览问题\r\n- 修复动态路由重定向造成标签页出现重复内容\r\n- 修复强制刷新页面 `getTopMenu()` 函数获取不到 `path` 报错的问题\r\n- 修复左侧菜单折叠后突然拉升造成左侧菜单整体不显示的问题\r\n- 修复 `RePureTableBar` 关闭列设置后在 `windows` 出现滚动条的问题\r\n\r\n### 🍏 Perf\r\n\r\n- 优化标签页操作-路由传参模式用法\r\n- 优化菜单搜索功能和样式\r\n- 更新 `vscode` 代码片段\r\n- 优化 `dataThemeChange` 主题设置的初始化调用时机"
|
||||
},
|
||||
{
|
||||
created_at: "2023-05-15T07:03:57Z",
|
||||
published_at: "2023-05-15T07:04:54Z",
|
||||
|
||||
68
package.json
68
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vue-pure-admin",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "NODE_OPTIONS=--max-old-space-size=4096 vite",
|
||||
@@ -31,11 +31,11 @@
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@howdyjs/mouse-menu": "^2.0.7",
|
||||
"@logicflow/core": "^1.2.7",
|
||||
"@logicflow/extension": "^1.2.7",
|
||||
"@logicflow/core": "^1.2.8",
|
||||
"@logicflow/extension": "^1.2.8",
|
||||
"@pureadmin/descriptions": "^1.1.1",
|
||||
"@pureadmin/table": "^2.2.0",
|
||||
"@pureadmin/utils": "^1.9.2",
|
||||
"@pureadmin/table": "^2.3.2",
|
||||
"@pureadmin/utils": "^1.9.3",
|
||||
"@vueuse/core": "^10.1.2",
|
||||
"@vueuse/motion": "^2.0.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
@@ -44,11 +44,10 @@
|
||||
"axios": "^1.4.0",
|
||||
"china-area-data": "^5.0.1",
|
||||
"cropperjs": "^1.5.13",
|
||||
"dayjs": "^1.11.7",
|
||||
"dayjs": "^1.11.8",
|
||||
"echarts": "^5.4.2",
|
||||
"el-table-infinite-scroll": "^3.0.1",
|
||||
"element-plus": "^2.3.5",
|
||||
"element-resize-detector": "^1.2.4",
|
||||
"element-plus": "^2.3.6",
|
||||
"intro.js": "^7.0.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsbarcode": "^3.11.5",
|
||||
@@ -59,46 +58,45 @@
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^2.1.3",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.11.1",
|
||||
"qs": "^6.11.2",
|
||||
"responsive-storage": "^2.2.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"swiper": "^9.3.2",
|
||||
"swiper": "^9.4.0",
|
||||
"typeit": "^8.7.1",
|
||||
"v-contextmenu": "3.0.0",
|
||||
"v3-infinite-loading": "^1.2.2",
|
||||
"version-rocket": "^1.6.2",
|
||||
"version-rocket": "^1.6.7",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-json-pretty": "^2.2.4",
|
||||
"vue-pdf-embed": "^1.1.6",
|
||||
"vue-router": "^4.2.1",
|
||||
"vue-router": "^4.2.2",
|
||||
"vue-types": "^5.0.3",
|
||||
"vue-virtual-scroller": "2.0.0-beta.7",
|
||||
"vue-waterfall-plugin-next": "^2.2.1",
|
||||
"vue3-danmaku": "^1.4.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"xgplayer": "^3.0.2",
|
||||
"xgplayer": "^3.0.4",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.6.3",
|
||||
"@commitlint/config-conventional": "^17.6.3",
|
||||
"@commitlint/cli": "^17.6.5",
|
||||
"@commitlint/config-conventional": "^17.6.5",
|
||||
"@iconify-icons/ep": "^1.2.11",
|
||||
"@iconify-icons/ri": "^1.2.7",
|
||||
"@iconify-icons/ri": "^1.2.8",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@intlify/unplugin-vue-i18n": "^0.10.0",
|
||||
"@intlify/unplugin-vue-i18n": "^0.11.0",
|
||||
"@pureadmin/theme": "^3.0.0",
|
||||
"@types/element-resize-detector": "1.1.3",
|
||||
"@types/intro.js": "^5.1.1",
|
||||
"@types/js-cookie": "^3.0.3",
|
||||
"@types/mockjs": "^1.0.7",
|
||||
"@types/node": "^18.15.12",
|
||||
"@types/node": "^18.16.18",
|
||||
"@types/nprogress": "0.2.0",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/sortablejs": "^1.15.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.7",
|
||||
"@typescript-eslint/parser": "^5.59.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.11",
|
||||
"@typescript-eslint/parser": "^5.59.11",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"@vue/eslint-config-prettier": "^7.1.0",
|
||||
@@ -106,44 +104,44 @@
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cloc": "^2.11.0",
|
||||
"cssnano": "^6.0.1",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.14.0",
|
||||
"eslint-plugin-vue": "^9.14.1",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^13.2.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss": "^8.4.24",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-scss": "^4.0.6",
|
||||
"prettier": "^2.8.8",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"rimraf": "^5.0.1",
|
||||
"rollup-plugin-visualizer": "^5.9.0",
|
||||
"sass": "^1.62.1",
|
||||
"sass-loader": "^13.3.0",
|
||||
"stylelint": "^15.6.2",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"sass": "^1.63.3",
|
||||
"sass-loader": "^13.3.2",
|
||||
"stylelint": "^15.7.0",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-recess-order": "^4.0.0",
|
||||
"stylelint-config-recess-order": "^4.2.0",
|
||||
"stylelint-config-recommended": "^12.0.0",
|
||||
"stylelint-config-recommended-scss": "^11.0.0",
|
||||
"stylelint-config-recommended-scss": "^12.0.0",
|
||||
"stylelint-config-recommended-vue": "^1.4.0",
|
||||
"stylelint-config-standard": "^33.0.0",
|
||||
"stylelint-config-standard-scss": "^9.0.0",
|
||||
"stylelint-order": "^6.0.3",
|
||||
"stylelint-prettier": "^3.0.0",
|
||||
"stylelint-scss": "^5.0.0",
|
||||
"stylelint-scss": "^5.0.1",
|
||||
"svgo": "^3.0.2",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"terser": "^5.17.6",
|
||||
"typescript": "^5.0.4",
|
||||
"terser": "^5.18.0",
|
||||
"typescript": "5.0.4",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-cdn-import": "^0.3.5",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-mock": "2.9.6",
|
||||
"vite-plugin-remove-console": "^2.1.1",
|
||||
"vite-svg-loader": "^4.0.0",
|
||||
"vue-eslint-parser": "^9.3.0",
|
||||
"vue-eslint-parser": "^9.3.1",
|
||||
"vue-tsc": "^1.6.5"
|
||||
},
|
||||
"pnpm": {
|
||||
|
||||
2448
pnpm-lock.yaml
generated
2448
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Version": "4.3.0",
|
||||
"Version": "4.4.0",
|
||||
"Title": "PureAdmin",
|
||||
"FixedHeader": true,
|
||||
"HiddenSideBar": false,
|
||||
|
||||
@@ -7,7 +7,9 @@ export const auth: Directive = {
|
||||
if (value) {
|
||||
!hasAuth(value) && el.parentNode?.removeChild(el);
|
||||
} else {
|
||||
throw new Error("need auths! Like v-auth=\"['btn.add','btn.edit']\"");
|
||||
throw new Error(
|
||||
"[Directive: auth]: need auths! Like v-auth=\"['btn.add','btn.edit']\""
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
33
src/directives/copy/index.ts
Normal file
33
src/directives/copy/index.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { message } from "@/utils/message";
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
import { copyTextToClipboard } from "@pureadmin/utils";
|
||||
import { Directive, type DirectiveBinding } from "vue";
|
||||
|
||||
interface CopyEl extends HTMLElement {
|
||||
copyValue: string;
|
||||
}
|
||||
|
||||
/** 文本复制指令(默认双击复制) */
|
||||
export const copy: Directive = {
|
||||
mounted(el: CopyEl, binding: DirectiveBinding) {
|
||||
const { value } = binding;
|
||||
if (value) {
|
||||
el.copyValue = value;
|
||||
const arg = binding.arg ?? "dblclick";
|
||||
// Register using addEventListener on mounted, and removeEventListener automatically on unmounted
|
||||
useEventListener(el, arg, () => {
|
||||
const success = copyTextToClipboard(el.copyValue);
|
||||
success
|
||||
? message("复制成功", { type: "success" })
|
||||
: message("复制失败", { type: "error" });
|
||||
});
|
||||
} else {
|
||||
throw new Error(
|
||||
'[Directive: copy]: need value! Like v-copy="modelValue"'
|
||||
);
|
||||
}
|
||||
},
|
||||
updated(el: CopyEl, binding: DirectiveBinding) {
|
||||
el.copyValue = binding.value;
|
||||
}
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Directive, type DirectiveBinding, type VNode } from "vue";
|
||||
import elementResizeDetectorMaker from "element-resize-detector";
|
||||
import type { Erd } from "element-resize-detector";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
|
||||
const erd: Erd = elementResizeDetectorMaker({
|
||||
strategy: "scroll"
|
||||
});
|
||||
|
||||
export const resize: Directive = {
|
||||
mounted(el: HTMLElement, binding?: DirectiveBinding, vnode?: VNode) {
|
||||
erd.listenTo(el, elem => {
|
||||
const width = elem.offsetWidth;
|
||||
const height = elem.offsetHeight;
|
||||
if (binding?.instance) {
|
||||
emitter.emit("resize", { detail: { width, height } });
|
||||
} else {
|
||||
vnode.el.dispatchEvent(
|
||||
new CustomEvent("resize", { detail: { width, height } })
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
unmounted(el: HTMLElement) {
|
||||
erd.uninstall(el);
|
||||
}
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./auth";
|
||||
export * from "./elResizeDetector";
|
||||
export * from "./copy";
|
||||
export * from "./optimize";
|
||||
|
||||
55
src/directives/optimize/index.ts
Normal file
55
src/directives/optimize/index.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
isFunction,
|
||||
isObject,
|
||||
isArray,
|
||||
debounce,
|
||||
throttle
|
||||
} from "@pureadmin/utils";
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
import { Directive, type DirectiveBinding } from "vue";
|
||||
|
||||
/** 防抖(v-optimize或v-optimize:debounce)、节流(v-optimize:throttle)指令 */
|
||||
export const optimize: Directive = {
|
||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||
const { value } = binding;
|
||||
const optimizeType = binding.arg ?? "debounce";
|
||||
const type = ["debounce", "throttle"].find(t => t === optimizeType);
|
||||
if (type) {
|
||||
if (value && value.event && isFunction(value.fn)) {
|
||||
let params = value?.params;
|
||||
if (params) {
|
||||
if (isArray(params) || isObject(params)) {
|
||||
params = isObject(params) ? Array.of(params) : params;
|
||||
} else {
|
||||
throw new Error(
|
||||
"[Directive: optimize]: `params` must be an array or object"
|
||||
);
|
||||
}
|
||||
}
|
||||
// Register using addEventListener on mounted, and removeEventListener automatically on unmounted
|
||||
useEventListener(
|
||||
el,
|
||||
value.event,
|
||||
type === "debounce"
|
||||
? debounce(
|
||||
params ? () => value.fn(...params) : value.fn,
|
||||
value?.timeout ?? 200,
|
||||
value?.immediate ?? false
|
||||
)
|
||||
: throttle(
|
||||
params ? () => value.fn(...params) : value.fn,
|
||||
value?.timeout ?? 1000
|
||||
)
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
"[Directive: optimize]: `event` and `fn` are required, and `fn` must be a function"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
"[Directive: optimize]: only `debounce` and `throttle` are supported"
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -22,19 +22,31 @@ notices.value.map(v => (noticesNum.value += v.list.length));
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-tabs :stretch="true" v-model="activeKey" class="dropdown-tabs">
|
||||
<template v-for="item in notices" :key="item.key">
|
||||
<el-tab-pane
|
||||
:label="`${item.name}(${item.list.length})`"
|
||||
:name="`${item.key}`"
|
||||
>
|
||||
<el-scrollbar max-height="330px">
|
||||
<div class="noticeList-container">
|
||||
<NoticeList :list="item.list" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
<el-tabs
|
||||
:stretch="true"
|
||||
v-model="activeKey"
|
||||
class="dropdown-tabs"
|
||||
:style="{ width: notices.length === 0 ? '200px' : '330px' }"
|
||||
>
|
||||
<el-empty
|
||||
v-if="notices.length === 0"
|
||||
description="暂无消息"
|
||||
:image-size="60"
|
||||
/>
|
||||
<span v-else>
|
||||
<template v-for="item in notices" :key="item.key">
|
||||
<el-tab-pane
|
||||
:label="`${item.name}(${item.list.length})`"
|
||||
:name="`${item.key}`"
|
||||
>
|
||||
<el-scrollbar max-height="330px">
|
||||
<div class="noticeList-container">
|
||||
<NoticeList :list="item.list" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</span>
|
||||
</el-tabs>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
@@ -57,8 +69,6 @@ notices.value.map(v => (noticesNum.value += v.list.length));
|
||||
}
|
||||
|
||||
.dropdown-tabs {
|
||||
width: 330px;
|
||||
|
||||
.noticeList-container {
|
||||
padding: 15px 24px 0;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
||||
import Close from "@iconify-icons/ep/close";
|
||||
|
||||
const target = ref(null);
|
||||
@@ -27,8 +27,15 @@ onClickOutside(target, (event: any) => {
|
||||
show.value = false;
|
||||
});
|
||||
|
||||
emitter.on("openPanel", () => {
|
||||
show.value = true;
|
||||
onMounted(() => {
|
||||
emitter.on("openPanel", () => {
|
||||
show.value = true;
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 解绑`openPanel`公共事件,防止多次触发
|
||||
emitter.off("openPanel");
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -145,6 +145,7 @@ onKeyStroke("ArrowDown", handleDown);
|
||||
:style="{
|
||||
borderRadius: '6px'
|
||||
}"
|
||||
append-to-body
|
||||
@opened="inputRef.focus()"
|
||||
@closed="inputRef.blur()"
|
||||
>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import Search from "../search/index.vue";
|
||||
import Notice from "../notice/index.vue";
|
||||
import { ref, watch, nextTick } from "vue";
|
||||
import SidebarItem from "./sidebarItem.vue";
|
||||
import { isAllEmpty } from "@pureadmin/utils";
|
||||
import { ref, nextTick, computed } from "vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { useTranslationLang } from "../../hooks/useTranslationLang";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
@@ -17,11 +18,9 @@ const { t, route, locale, translationCh, translationEn } =
|
||||
useTranslationLang(menuRef);
|
||||
const {
|
||||
title,
|
||||
routers,
|
||||
logout,
|
||||
backTopMenu,
|
||||
onPanel,
|
||||
menuSelect,
|
||||
username,
|
||||
userAvatar,
|
||||
avatarsStyle,
|
||||
@@ -29,16 +28,13 @@ const {
|
||||
getDropdownItemClass
|
||||
} = useNav();
|
||||
|
||||
const defaultActive = computed(() =>
|
||||
!isAllEmpty(route.meta?.activePath) ? route.meta.activePath : route.path
|
||||
);
|
||||
|
||||
nextTick(() => {
|
||||
menuRef.value?.handleResize();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
menuSelect(route.path, routers);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -55,8 +51,7 @@ watch(
|
||||
ref="menuRef"
|
||||
mode="horizontal"
|
||||
class="horizontal-header-menu"
|
||||
:default-active="route.path"
|
||||
@select="indexPath => menuSelect(indexPath, routers)"
|
||||
:default-active="defaultActive"
|
||||
>
|
||||
<sidebar-item
|
||||
v-for="route in usePermissionStoreHook().wholeMenus"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import extraIcon from "./extraIcon.vue";
|
||||
import Search from "../search/index.vue";
|
||||
import Notice from "../notice/index.vue";
|
||||
import { isAllEmpty } from "@pureadmin/utils";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { ref, toRaw, watch, onMounted, nextTick } from "vue";
|
||||
@@ -21,10 +22,8 @@ const { t, route, locale, translationCh, translationEn } =
|
||||
useTranslationLang(menuRef);
|
||||
const {
|
||||
device,
|
||||
routers,
|
||||
logout,
|
||||
onPanel,
|
||||
menuSelect,
|
||||
resolvePath,
|
||||
username,
|
||||
userAvatar,
|
||||
@@ -38,10 +37,9 @@ function getDefaultActive(routePath) {
|
||||
const wholeMenus = usePermissionStoreHook().wholeMenus;
|
||||
/** 当前路由的父级路径 */
|
||||
const parentRoutes = getParentPaths(routePath, wholeMenus)[0];
|
||||
defaultActive.value = findRouteByPath(
|
||||
parentRoutes,
|
||||
wholeMenus
|
||||
)?.children[0]?.path;
|
||||
defaultActive.value = !isAllEmpty(route.meta?.activePath)
|
||||
? route.meta.activePath
|
||||
: findRouteByPath(parentRoutes, wholeMenus)?.children[0]?.path;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -72,7 +70,6 @@ watch(
|
||||
mode="horizontal"
|
||||
class="horizontal-header-menu"
|
||||
:default-active="defaultActive"
|
||||
@select="indexPath => menuSelect(indexPath, routers)"
|
||||
>
|
||||
<el-menu-item
|
||||
v-for="route in usePermissionStoreHook().wholeMenus"
|
||||
|
||||
@@ -5,11 +5,11 @@ import { emitter } from "@/utils/mitt";
|
||||
import SidebarItem from "./sidebarItem.vue";
|
||||
import leftCollapse from "./leftCollapse.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import { responsiveStorageNameSpace } from "@/config";
|
||||
import { ref, computed, watch, onBeforeMount } from "vue";
|
||||
import { storageLocal, isAllEmpty } from "@pureadmin/utils";
|
||||
import { findRouteByPath, getParentPaths } from "@/router/utils";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
|
||||
|
||||
const route = useRoute();
|
||||
const showLogo = ref(
|
||||
@@ -18,8 +18,7 @@ const showLogo = ref(
|
||||
)?.showLogo ?? true
|
||||
);
|
||||
|
||||
const { routers, device, pureApp, isCollapse, menuSelect, toggleSideBar } =
|
||||
useNav();
|
||||
const { device, pureApp, isCollapse, menuSelect, toggleSideBar } = useNav();
|
||||
|
||||
const subMenuData = ref([]);
|
||||
|
||||
@@ -33,7 +32,13 @@ const loading = computed(() =>
|
||||
pureApp.layout === "mix" ? false : menuData.value.length === 0 ? true : false
|
||||
);
|
||||
|
||||
function getSubMenuData(path: string) {
|
||||
const defaultActive = computed(() =>
|
||||
!isAllEmpty(route.meta?.activePath) ? route.meta.activePath : route.path
|
||||
);
|
||||
|
||||
function getSubMenuData() {
|
||||
let path = "";
|
||||
path = defaultActive.value;
|
||||
subMenuData.value = [];
|
||||
// path的上级路由组成的数组
|
||||
const parentPathArr = getParentPaths(
|
||||
@@ -49,22 +54,27 @@ function getSubMenuData(path: string) {
|
||||
subMenuData.value = parenetRoute?.children;
|
||||
}
|
||||
|
||||
getSubMenuData(route.path);
|
||||
watch(
|
||||
() => [route.path, usePermissionStoreHook().wholeMenus],
|
||||
() => {
|
||||
if (route.path.includes("/redirect")) return;
|
||||
getSubMenuData();
|
||||
menuSelect(route.path);
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
getSubMenuData();
|
||||
|
||||
onBeforeMount(() => {
|
||||
emitter.on("logoChange", key => {
|
||||
showLogo.value = key;
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [route.path, usePermissionStoreHook().wholeMenus],
|
||||
() => {
|
||||
if (route.path.includes("/redirect")) return;
|
||||
getSubMenuData(route.path);
|
||||
menuSelect(route.path, routers);
|
||||
}
|
||||
);
|
||||
onBeforeUnmount(() => {
|
||||
// 解绑`logoChange`公共事件,防止多次触发
|
||||
emitter.off("logoChange");
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -83,9 +93,8 @@ watch(
|
||||
mode="vertical"
|
||||
class="outer-most select-none"
|
||||
:collapse="isCollapse"
|
||||
:default-active="route.path"
|
||||
:default-active="defaultActive"
|
||||
:collapse-transition="false"
|
||||
@select="indexPath => menuSelect(indexPath, routers)"
|
||||
>
|
||||
<sidebar-item
|
||||
v-for="routes in menuData"
|
||||
|
||||
@@ -4,12 +4,12 @@ import { emitter } from "@/utils/mitt";
|
||||
import { RouteConfigs } from "../../types";
|
||||
import { useTags } from "../../hooks/useTag";
|
||||
import { routerArrays } from "@/layout/types";
|
||||
import { isEqual, isAllEmpty } from "@pureadmin/utils";
|
||||
import { handleAliveRoute, getTopMenu } from "@/router/utils";
|
||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
||||
import { useResizeObserver, useFullscreen } from "@vueuse/core";
|
||||
import { isEqual, isAllEmpty, debounce } from "@pureadmin/utils";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { ref, watch, unref, toRaw, nextTick, onBeforeMount } from "vue";
|
||||
import { useResizeObserver, useDebounceFn, useFullscreen } from "@vueuse/core";
|
||||
import { ref, watch, unref, toRaw, nextTick, onBeforeUnmount } from "vue";
|
||||
|
||||
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
|
||||
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
|
||||
@@ -54,20 +54,22 @@ const topPath = getTopMenu()?.path;
|
||||
const { VITE_HIDE_HOME } = import.meta.env;
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
|
||||
const dynamicTagView = () => {
|
||||
const dynamicTagView = async () => {
|
||||
await nextTick();
|
||||
const index = multiTags.value.findIndex(item => {
|
||||
if (item.query) {
|
||||
if (!isAllEmpty(route.query)) {
|
||||
return isEqual(route.query, item.query);
|
||||
} else if (item.params) {
|
||||
} else if (!isAllEmpty(route.params)) {
|
||||
return isEqual(route.params, item.params);
|
||||
} else {
|
||||
return item.path === route.path;
|
||||
return route.path === item.path;
|
||||
}
|
||||
});
|
||||
moveToView(index);
|
||||
};
|
||||
|
||||
const moveToView = async (index: number): Promise<void> => {
|
||||
await nextTick();
|
||||
const tabNavPadding = 10;
|
||||
if (!instance.refs["dynamic" + index]) return;
|
||||
const tabItemEl = instance.refs["dynamic" + index][0];
|
||||
@@ -78,9 +80,6 @@ const moveToView = async (index: number): Promise<void> => {
|
||||
? scrollbarDom.value?.offsetWidth
|
||||
: 0;
|
||||
|
||||
// 获取视图更新后dom
|
||||
await nextTick();
|
||||
|
||||
// 已有标签页总长度(包含溢出部分)
|
||||
const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
|
||||
|
||||
@@ -135,31 +134,29 @@ const handleScroll = (offset: number): void => {
|
||||
}
|
||||
};
|
||||
|
||||
function dynamicRouteTag(value: string, parentPath: string): void {
|
||||
function dynamicRouteTag(value: string): void {
|
||||
const hasValue = multiTags.value.some(item => {
|
||||
return item.path === value;
|
||||
});
|
||||
|
||||
function concatPath(arr: object[], value: string, parentPath: string) {
|
||||
function concatPath(arr: object[], value: string) {
|
||||
if (!hasValue) {
|
||||
arr.forEach((arrItem: any) => {
|
||||
const pathConcat = parentPath + arrItem.path;
|
||||
if (arrItem.path === value || pathConcat === value) {
|
||||
if (arrItem.path === value || arrItem.path === value) {
|
||||
useMultiTagsStoreHook().handleTags("push", {
|
||||
path: value,
|
||||
parentPath: `/${parentPath.split("/")[1]}`,
|
||||
meta: arrItem.meta,
|
||||
name: arrItem.name
|
||||
});
|
||||
} else {
|
||||
if (arrItem.children && arrItem.children.length > 0) {
|
||||
concatPath(arrItem.children, value, parentPath);
|
||||
concatPath(arrItem.children, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
concatPath(router.options.routes as any, value, parentPath);
|
||||
concatPath(router.options.routes as any, value);
|
||||
}
|
||||
|
||||
/** 刷新路由 */
|
||||
@@ -169,7 +166,7 @@ function onFresh() {
|
||||
path: "/redirect" + fullPath,
|
||||
query
|
||||
});
|
||||
handleAliveRoute(route as toRouteType, "refresh");
|
||||
handleAliveRoute(route as ToRouteType, "refresh");
|
||||
}
|
||||
|
||||
function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
||||
@@ -242,7 +239,7 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
||||
|
||||
function deleteMenu(item, tag?: string) {
|
||||
deleteDynamicTag(item, item.path, tag);
|
||||
handleAliveRoute(route as toRouteType);
|
||||
handleAliveRoute(route as ToRouteType);
|
||||
}
|
||||
|
||||
function onClickDrop(key, item, selectRoute?: RouteConfigs) {
|
||||
@@ -290,7 +287,7 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
|
||||
length: multiTags.value.length
|
||||
});
|
||||
router.push(topPath);
|
||||
handleAliveRoute(route as toRouteType);
|
||||
handleAliveRoute(route as ToRouteType);
|
||||
break;
|
||||
case 6:
|
||||
// 整体页面全屏
|
||||
@@ -465,7 +462,17 @@ function tagOnClick(item) {
|
||||
// showMenuModel(item?.path, item?.query);
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
watch(route, () => {
|
||||
activeIndex.value = -1;
|
||||
dynamicTagView();
|
||||
});
|
||||
|
||||
watch(isFullscreen, () => {
|
||||
tagsViews[6].icon = Fullscreen;
|
||||
tagsViews[6].text = $t("buttons.hswholeFullScreen");
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (!instance) return;
|
||||
|
||||
// 根据当前路由初始化操作标签页的禁用状态
|
||||
@@ -483,32 +490,25 @@ onBeforeMount(() => {
|
||||
});
|
||||
|
||||
// 接收侧边栏切换传递过来的参数
|
||||
emitter.on("changLayoutRoute", ({ indexPath, parentPath }) => {
|
||||
dynamicRouteTag(indexPath, parentPath);
|
||||
emitter.on("changLayoutRoute", indexPath => {
|
||||
dynamicRouteTag(indexPath);
|
||||
setTimeout(() => {
|
||||
showMenuModel(indexPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
watch([route], () => {
|
||||
activeIndex.value = -1;
|
||||
dynamicTagView();
|
||||
});
|
||||
|
||||
watch(isFullscreen, () => {
|
||||
tagsViews[6].icon = Fullscreen;
|
||||
tagsViews[6].text = $t("buttons.hswholeFullScreen");
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
useResizeObserver(
|
||||
scrollbarDom,
|
||||
useDebounceFn(() => {
|
||||
dynamicTagView();
|
||||
}, 200)
|
||||
debounce(() => dynamicTagView())
|
||||
);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 解绑`tagViewsChange`、`tagViewsShowModel`、`changLayoutRoute`公共事件,防止多次触发
|
||||
emitter.off("tagViewsChange");
|
||||
emitter.off("tagViewsShowModel");
|
||||
emitter.off("changLayoutRoute");
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -114,38 +114,13 @@ export function useNav() {
|
||||
}
|
||||
}
|
||||
|
||||
function menuSelect(indexPath: string, routers): void {
|
||||
if (wholeMenus.value.length === 0) return;
|
||||
if (isRemaining(indexPath)) return;
|
||||
let parentPath = "";
|
||||
const parentPathIndex = indexPath.lastIndexOf("/");
|
||||
if (parentPathIndex > 0) {
|
||||
parentPath = indexPath.slice(0, parentPathIndex);
|
||||
}
|
||||
/** 找到当前路由的信息 */
|
||||
function findCurrentRoute(indexPath: string, routes) {
|
||||
if (!routes) return console.error(errorInfo);
|
||||
return routes.map(item => {
|
||||
if (item.path === indexPath) {
|
||||
if (item.redirect) {
|
||||
findCurrentRoute(item.redirect, item.children);
|
||||
} else {
|
||||
/** 切换左侧菜单 通知标签页 */
|
||||
emitter.emit("changLayoutRoute", {
|
||||
indexPath,
|
||||
parentPath
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (item.children) findCurrentRoute(indexPath, item.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
findCurrentRoute(indexPath, routers);
|
||||
function menuSelect(indexPath: string) {
|
||||
if (wholeMenus.value.length === 0 || isRemaining(indexPath)) return;
|
||||
emitter.emit("changLayoutRoute", indexPath);
|
||||
}
|
||||
|
||||
/** 判断路径是否参与菜单 */
|
||||
function isRemaining(path: string): boolean {
|
||||
function isRemaining(path: string) {
|
||||
return remainingPaths.includes(path);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useNav } from "./useNav";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { watch, type Ref } from "vue";
|
||||
import { watch, onBeforeMount, type Ref } from "vue";
|
||||
|
||||
export function useTranslationLang(ref?: Ref) {
|
||||
const { $storage, changeTitle, handleResize } = useNav();
|
||||
@@ -27,6 +27,10 @@ export function useTranslationLang(ref?: Ref) {
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeMount(() => {
|
||||
locale.value = $storage.locale?.locale ?? "zh";
|
||||
});
|
||||
|
||||
return {
|
||||
t,
|
||||
route,
|
||||
|
||||
@@ -3,14 +3,15 @@ import "animate.css";
|
||||
// 引入 src/components/ReIcon/src/offlineIcon.ts 文件中所有使用addIcon添加过的本地图标
|
||||
import "@/components/ReIcon/src/offlineIcon";
|
||||
import { setType } from "./types";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import { useLayout } from "./hooks/useLayout";
|
||||
import { useResizeObserver } from "@vueuse/core";
|
||||
import { useAppStoreHook } from "@/store/modules/app";
|
||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
||||
import { deviceDetection, useDark, useGlobal } from "@pureadmin/utils";
|
||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
||||
import {
|
||||
h,
|
||||
ref,
|
||||
reactive,
|
||||
computed,
|
||||
onMounted,
|
||||
@@ -26,6 +27,7 @@ import Vertical from "./components/sidebar/vertical.vue";
|
||||
import Horizontal from "./components/sidebar/horizontal.vue";
|
||||
import backTop from "@/assets/svg/back_top.svg?component";
|
||||
|
||||
const appWrapperRef = ref();
|
||||
const { isDark } = useDark();
|
||||
const { layout } = useLayout();
|
||||
const isMobile = deviceDetection();
|
||||
@@ -78,10 +80,10 @@ function toggle(device: string, bool: boolean) {
|
||||
// 判断是否可自动关闭菜单栏
|
||||
let isAutoCloseSidebar = true;
|
||||
|
||||
// 监听容器
|
||||
emitter.on("resize", ({ detail }) => {
|
||||
useResizeObserver(appWrapperRef, entries => {
|
||||
if (isMobile) return;
|
||||
const { width } = detail;
|
||||
const entry = entries[0];
|
||||
const { width } = entry.contentRect;
|
||||
width <= 760 ? setTheme("vertical") : setTheme(useAppStoreHook().layout);
|
||||
/** width app-wrapper类容器宽度
|
||||
* 0 < width <= 760 隐藏侧边栏
|
||||
@@ -147,7 +149,7 @@ const layoutHeader = defineComponent({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['app-wrapper', set.classes]" v-resize>
|
||||
<div ref="appWrapperRef" :class="['app-wrapper', set.classes]">
|
||||
<div
|
||||
v-show="
|
||||
set.device === 'mobile' &&
|
||||
|
||||
@@ -6,7 +6,6 @@ export const routerArrays: Array<RouteConfigs> =
|
||||
? [
|
||||
{
|
||||
path: "/welcome",
|
||||
parentPath: "/",
|
||||
meta: {
|
||||
title: "menus.hshome",
|
||||
icon: "homeFilled"
|
||||
@@ -25,7 +24,6 @@ export type routeMetaType = {
|
||||
|
||||
export type RouteConfigs = {
|
||||
path?: string;
|
||||
parentPath?: string;
|
||||
query?: object;
|
||||
params?: object;
|
||||
meta?: routeMetaType;
|
||||
|
||||
@@ -101,7 +101,7 @@ const whiteList = ["/login"];
|
||||
|
||||
const { VITE_HIDE_HOME } = import.meta.env;
|
||||
|
||||
router.beforeEach((to: toRouteType, _from, next) => {
|
||||
router.beforeEach((to: ToRouteType, _from, next) => {
|
||||
if (to.meta?.keepAlive) {
|
||||
handleAliveRoute(to, "add");
|
||||
// 页面整体刷新和点击标签页刷新
|
||||
@@ -176,7 +176,8 @@ router.beforeEach((to: toRouteType, _from, next) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
router.push(to.fullPath);
|
||||
// 确保动态路由完全加入路由列表并且不影响静态路由(注意:动态路由刷新时router.beforeEach可能会触发两次,第一次触发动态路由还未完全添加,第二次动态路由才完全添加到路由列表,如果需要在router.beforeEach做一些判断可以在to.name存在的条件下去判断,这样就只会触发一次)
|
||||
if (isAllEmpty(to.name)) router.push(to.fullPath);
|
||||
});
|
||||
}
|
||||
toCorrectRoute();
|
||||
|
||||
@@ -10,6 +10,15 @@ export default {
|
||||
rank: able
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/able/directives",
|
||||
name: "Directives",
|
||||
component: () => import("@/views/able/directives.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsOptimize"),
|
||||
extraIcon: "IF-pure-iconfont-new svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/able/watermark",
|
||||
name: "WaterMark",
|
||||
|
||||
@@ -256,7 +256,7 @@ function formatTwoStageRoutes(routesList: RouteRecordRaw[]) {
|
||||
}
|
||||
|
||||
/** 处理缓存路由(添加、删除、刷新) */
|
||||
function handleAliveRoute({ name }: toRouteType, mode?: string) {
|
||||
function handleAliveRoute({ name }: ToRouteType, mode?: string) {
|
||||
switch (mode) {
|
||||
case "add":
|
||||
usePermissionStoreHook().cacheOperate({
|
||||
|
||||
@@ -24,7 +24,6 @@ export type appType = {
|
||||
|
||||
export type multiType = {
|
||||
path: string;
|
||||
parentPath: string;
|
||||
name: string;
|
||||
meta: any;
|
||||
query?: object;
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
import type { Emitter } from "mitt";
|
||||
import mitt from "mitt";
|
||||
|
||||
/** 全局公共事件需要在此处添加类型 */
|
||||
type Events = {
|
||||
resize: {
|
||||
detail: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
};
|
||||
openPanel: string;
|
||||
tagViewsChange: string;
|
||||
tagViewsShowModel: string;
|
||||
logoChange: boolean;
|
||||
changLayoutRoute: {
|
||||
indexPath: string;
|
||||
parentPath: string;
|
||||
};
|
||||
changLayoutRoute: string;
|
||||
};
|
||||
|
||||
export const emitter: Emitter<Events> = mitt<Events>();
|
||||
|
||||
117
src/views/able/directives.vue
Normal file
117
src/views/able/directives.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { message } from "@/utils/message";
|
||||
|
||||
defineOptions({
|
||||
name: "Directives"
|
||||
});
|
||||
|
||||
const search = ref("");
|
||||
const searchTwo = ref("");
|
||||
const searchThree = ref("");
|
||||
const searchFour = ref("");
|
||||
const searchFive = ref("");
|
||||
const searchSix = ref("copy");
|
||||
const text = ref("可复制的文本");
|
||||
|
||||
function onInput() {
|
||||
message(search.value);
|
||||
}
|
||||
function onInputTwo() {
|
||||
message(searchTwo.value);
|
||||
}
|
||||
function onInputThree({ name, sex }) {
|
||||
message(`${name}${sex}${searchThree.value}`);
|
||||
}
|
||||
|
||||
function onInputFour() {
|
||||
message(searchFour.value);
|
||||
}
|
||||
function onInputFive({ name, sex }) {
|
||||
message(`${name}${sex}${searchFive.value}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="font-medium">自定义防抖、截流、文本复制指令</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="mb-2">
|
||||
防抖指令(连续输入,只会执行第一次点击事件,立即执行)
|
||||
<el-input
|
||||
v-optimize="{
|
||||
event: 'input',
|
||||
fn: onInput,
|
||||
immediate: true,
|
||||
timeout: 1000
|
||||
}"
|
||||
v-model="search"
|
||||
class="!w-[200px]"
|
||||
clearable
|
||||
@clear="onInput"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
防抖指令(连续输入,只会执行最后一次事件,延后执行)
|
||||
<el-input
|
||||
v-optimize="{ event: 'input', fn: onInputTwo, timeout: 400 }"
|
||||
v-model="searchTwo"
|
||||
class="!w-[200px]"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
防抖指令(连续输入,只会执行最后一次事件,延后执行,传参用法)
|
||||
<el-input
|
||||
v-optimize="{
|
||||
event: 'input',
|
||||
fn: onInputThree,
|
||||
timeout: 400,
|
||||
params: { name: '小明', sex: '男' }
|
||||
}"
|
||||
v-model="searchThree"
|
||||
class="!w-[200px]"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<div class="mb-2">
|
||||
节流指令(连续输入,每一秒只会执行一次事件)
|
||||
<el-input
|
||||
v-optimize:throttle="{ event: 'input', fn: onInputFour, timeout: 1000 }"
|
||||
v-model="searchFour"
|
||||
class="!w-[200px]"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
节流指令(连续输入,每一秒只会执行一次事件,传参用法)
|
||||
<el-input
|
||||
v-optimize:throttle="{
|
||||
event: 'input',
|
||||
fn: onInputFive,
|
||||
params: { name: '小明', sex: '男' }
|
||||
}"
|
||||
v-model="searchFive"
|
||||
class="!w-[200px]"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<div class="mb-2">
|
||||
文本复制指令(双击输入框内容即可复制)
|
||||
<el-input v-copy="searchSix" v-model="searchSix" class="!w-[200px]" />
|
||||
</div>
|
||||
<div>
|
||||
文本复制指令(自定义触发事件,单击复制)
|
||||
<span v-copy:click="text" class="text-sky-500">{{ text }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
@@ -15,8 +15,10 @@ const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({ user: "", region: "" })
|
||||
});
|
||||
|
||||
// vue 规定所有的 prop 都遵循着单向绑定原则,不能在子组件中更改 prop 值,该 form.vue 文件为子组件
|
||||
// 如果需要拿到初始化的 prop 值并使得组件变量可修改,则需要在子组件定义一个新的变量接受这个子组件的 prop
|
||||
// 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>
|
||||
|
||||
22
src/views/components/dialog/formPrimitive.vue
Normal file
22
src/views/components/dialog/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 class="!w-[220px]" v-model="data" placeholder="请输入内容" />
|
||||
</template>
|
||||
@@ -3,6 +3,7 @@ import { useRouter } from "vue-router";
|
||||
import { h, createVNode, ref } from "vue";
|
||||
import { message } from "@/utils/message";
|
||||
import forms, { type FormProps } from "./form.vue";
|
||||
import formPrimitive from "./formPrimitive.vue";
|
||||
import { cloneDeep, debounce } from "@pureadmin/utils";
|
||||
import {
|
||||
addDialog,
|
||||
@@ -316,7 +317,10 @@ function onFormTwoClick() {
|
||||
addDialog({
|
||||
width: "30%",
|
||||
title: "结合Form表单(第二种方式)",
|
||||
contentRenderer: () => h(forms, { formInline: formInline.value }),
|
||||
contentRenderer: () =>
|
||||
h(forms, {
|
||||
formInline: formInline.value
|
||||
}),
|
||||
closeCallBack: () => {
|
||||
message(
|
||||
`当前表单数据为 姓名:${formInline.value.user} 城市:${formInline.value.region}`
|
||||
@@ -338,7 +342,9 @@ function onFormThreeClick() {
|
||||
width: "30%",
|
||||
title: "结合Form表单(第三种方式)",
|
||||
contentRenderer: () =>
|
||||
createVNode(forms, { formInline: formThreeInline.value }),
|
||||
createVNode(forms, {
|
||||
formInline: formThreeInline.value
|
||||
}),
|
||||
closeCallBack: () => {
|
||||
message(
|
||||
`当前表单数据为 姓名:${formThreeInline.value.user} 城市:${formThreeInline.value.region}`
|
||||
@@ -373,6 +379,26 @@ function onFormFourClick() {
|
||||
});
|
||||
}
|
||||
|
||||
// 子组件 prop 为 primitive 类型的 demo
|
||||
const formPrimitiveParam = ref("Hello World");
|
||||
const resetFormPrimitiveParam = ref(formPrimitiveParam.value);
|
||||
function onFormPrimitiveFormClick() {
|
||||
addDialog({
|
||||
width: "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() {
|
||||
addDialog({
|
||||
title: "点击底部取消按钮的回调",
|
||||
@@ -474,6 +500,9 @@ function onBeforeSureClick() {
|
||||
<el-button @click="onFormFourClick">
|
||||
结合Form表单(第四种方式)
|
||||
</el-button>
|
||||
<el-button @click="onFormPrimitiveFormClick">
|
||||
子组件 prop 为 primitive 类型
|
||||
</el-button>
|
||||
</el-space>
|
||||
<el-divider />
|
||||
<el-space wrap>
|
||||
|
||||
@@ -12,7 +12,7 @@ export const REGEXP_PWD =
|
||||
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
|
||||
|
||||
/** 登录校验 */
|
||||
const loginRules = reactive(<FormRules>{
|
||||
const loginRules = reactive<FormRules>({
|
||||
password: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
@@ -44,7 +44,7 @@ const loginRules = reactive(<FormRules>{
|
||||
});
|
||||
|
||||
/** 手机登录校验 */
|
||||
const phoneRules = reactive(<FormRules>{
|
||||
const phoneRules = reactive<FormRules>({
|
||||
phone: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
@@ -76,7 +76,7 @@ const phoneRules = reactive(<FormRules>{
|
||||
});
|
||||
|
||||
/** 忘记密码校验 */
|
||||
const updateRules = reactive(<FormRules>{
|
||||
const updateRules = reactive<FormRules>({
|
||||
phone: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
|
||||
105
src/views/pure-table/high/adaptive/columns.tsx
Normal file
105
src/views/pure-table/high/adaptive/columns.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import type {
|
||||
LoadingConfig,
|
||||
AdaptiveConfig,
|
||||
PaginationProps
|
||||
} from "@pureadmin/table";
|
||||
import { tableData } from "../data";
|
||||
import { ref, onMounted, reactive } from "vue";
|
||||
import { clone, delay } from "@pureadmin/utils";
|
||||
|
||||
export function useColumns() {
|
||||
const dataList = ref([]);
|
||||
const loading = ref(true);
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "日期",
|
||||
prop: "date"
|
||||
},
|
||||
{
|
||||
label: "姓名",
|
||||
prop: "name"
|
||||
},
|
||||
{
|
||||
label: "地址",
|
||||
prop: "address"
|
||||
}
|
||||
];
|
||||
|
||||
/** 分页配置 */
|
||||
const pagination = reactive<PaginationProps>({
|
||||
pageSize: 20,
|
||||
currentPage: 1,
|
||||
pageSizes: [20, 40, 60],
|
||||
total: 0,
|
||||
align: "right",
|
||||
background: true,
|
||||
small: false
|
||||
});
|
||||
|
||||
/** 加载动画配置 */
|
||||
const loadingConfig = reactive<LoadingConfig>({
|
||||
text: "正在加载第一页...",
|
||||
viewBox: "-10, -10, 50, 50",
|
||||
spinner: `
|
||||
<path class="path" d="
|
||||
M 30 15
|
||||
L 28 17
|
||||
M 25.61 25.61
|
||||
A 15 15, 0, 0, 1, 15 30
|
||||
A 15 15, 0, 1, 1, 27.99 7.5
|
||||
L 15 15
|
||||
" style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"/>
|
||||
`
|
||||
// svg: "",
|
||||
// background: rgba()
|
||||
});
|
||||
|
||||
/** 撑满内容区自适应高度相关配置 */
|
||||
const adaptiveConfig: AdaptiveConfig = {
|
||||
/** 表格距离页面底部的偏移量,默认值为 `96` */
|
||||
offsetBottom: 110
|
||||
/** 是否固定表头,默认值为 `true`(如果不想固定表头,fixHeader设置为false并且表格要设置table-layout="auto") */
|
||||
// fixHeader: true
|
||||
/** 页面 `resize` 时的防抖时间,默认值为 `60` ms */
|
||||
// timeout: 60
|
||||
/** 表头的 `z-index`,默认值为 `100` */
|
||||
// zIndex: 100
|
||||
};
|
||||
|
||||
function onSizeChange(val) {
|
||||
console.log("onSizeChange", val);
|
||||
}
|
||||
|
||||
function onCurrentChange(val) {
|
||||
loadingConfig.text = `正在加载第${val}页...`;
|
||||
loading.value = true;
|
||||
delay(600).then(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
delay(600).then(() => {
|
||||
const newList = [];
|
||||
Array.from({ length: 6 }).forEach(() => {
|
||||
newList.push(clone(tableData, true));
|
||||
});
|
||||
newList.flat(Infinity).forEach((item, index) => {
|
||||
dataList.value.push({ id: index, ...item });
|
||||
});
|
||||
pagination.total = dataList.value.length;
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
pagination,
|
||||
loadingConfig,
|
||||
adaptiveConfig,
|
||||
onSizeChange,
|
||||
onCurrentChange
|
||||
};
|
||||
}
|
||||
41
src/views/pure-table/high/adaptive/index.vue
Normal file
41
src/views/pure-table/high/adaptive/index.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useColumns } from "./columns";
|
||||
|
||||
const tableRef = ref();
|
||||
|
||||
const {
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
pagination,
|
||||
loadingConfig,
|
||||
adaptiveConfig,
|
||||
onSizeChange,
|
||||
onCurrentChange
|
||||
} = useColumns();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
border
|
||||
adaptive
|
||||
:adaptiveConfig="adaptiveConfig"
|
||||
row-key="id"
|
||||
alignWhole="center"
|
||||
showOverflowTooltip
|
||||
:loading="loading"
|
||||
:loading-config="loadingConfig"
|
||||
:data="
|
||||
dataList.slice(
|
||||
(pagination.currentPage - 1) * pagination.pageSize,
|
||||
pagination.currentPage * pagination.pageSize
|
||||
)
|
||||
"
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
@page-size-change="onSizeChange"
|
||||
@page-current-change="onCurrentChange"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,3 +1,4 @@
|
||||
import Adaptive from "./adaptive/index.vue";
|
||||
import Page from "./page/index.vue";
|
||||
import RowDrag from "./drag/row/index.vue";
|
||||
import ColumnDrag from "./drag/column/index.vue";
|
||||
@@ -13,6 +14,12 @@ const rendContent = (val: string) =>
|
||||
`代码位置:src/views/pure-table/high/${val}/index.vue`;
|
||||
|
||||
export const list = [
|
||||
{
|
||||
key: "adaptive",
|
||||
content: rendContent("adaptive"),
|
||||
title: "自适应内容区高度",
|
||||
component: Adaptive
|
||||
},
|
||||
{
|
||||
key: "page",
|
||||
content: rendContent("page"),
|
||||
|
||||
@@ -90,6 +90,8 @@ const {
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
border
|
||||
adaptive
|
||||
:adaptiveConfig="{ offsetBottom: 32 }"
|
||||
align-whole="center"
|
||||
row-key="id"
|
||||
showOverflowTooltip
|
||||
|
||||
@@ -109,6 +109,7 @@ const {
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
adaptive
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="pagination"
|
||||
|
||||
@@ -39,7 +39,7 @@ const {
|
||||
<template>
|
||||
<div class="main">
|
||||
<tree class="w-[17%] float-left" />
|
||||
<div class="float-right w-[81%]">
|
||||
<div class="float-right w-[82%]">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
@@ -97,6 +97,7 @@ const {
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<pure-table
|
||||
border
|
||||
adaptive
|
||||
align-whole="center"
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
|
||||
@@ -87,7 +87,10 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full min-h-[780px] bg-bg_color overflow-auto">
|
||||
<div
|
||||
class="h-full bg-bg_color overflow-auto"
|
||||
:style="{ minHeight: `calc(100vh - 133px)` }"
|
||||
>
|
||||
<div class="flex items-center h-[34px]">
|
||||
<p class="flex-1 ml-2 font-bold text-base truncate" title="部门列表">
|
||||
部门列表
|
||||
|
||||
@@ -145,7 +145,9 @@ getReleases().then(({ data }) => {
|
||||
</template>
|
||||
<el-skeleton animated :rows="7" :loading="loading">
|
||||
<template #default>
|
||||
<Github />
|
||||
<el-scrollbar :height="`calc(${height}px - 35vh - 340px)`">
|
||||
<Github />
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-card>
|
||||
|
||||
@@ -28,8 +28,7 @@
|
||||
"element-plus/global",
|
||||
"@pureadmin/table/volar",
|
||||
"@pureadmin/descriptions/volar"
|
||||
],
|
||||
"typeRoots": ["./types", "./node_modules/@types/"]
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"mock/*.ts",
|
||||
|
||||
93
types/global.d.ts
vendored
93
types/global.d.ts
vendored
@@ -7,7 +7,6 @@ import type {
|
||||
import type { ECharts } from "echarts";
|
||||
import type { IconifyIcon } from "@iconify/vue";
|
||||
import type { TableColumns } from "@pureadmin/table";
|
||||
import { type RouteComponent, type RouteLocationNormalized } from "vue-router";
|
||||
|
||||
/**
|
||||
* 全局类型声明,无需引入直接在 `.vue` 、`.ts` 、`.tsx` 文件使用即可获得类型提示
|
||||
@@ -166,98 +165,6 @@ declare global {
|
||||
tags?: Array<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* `src/router` 文件夹里的类型声明
|
||||
*/
|
||||
interface toRouteType extends RouteLocationNormalized {
|
||||
meta: {
|
||||
roles: Array<string>;
|
||||
keepAlive?: boolean;
|
||||
dynamicLevel?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 完整子路由配置表
|
||||
*/
|
||||
interface RouteChildrenConfigsTable {
|
||||
/** 子路由地址 `必填` */
|
||||
path: string;
|
||||
/** 路由名字(对应不要重复,和当前组件的`name`保持一致)`必填` */
|
||||
name?: string;
|
||||
/** 路由重定向 `可选` */
|
||||
redirect?: string;
|
||||
/** 按需加载组件 `可选` */
|
||||
component?: RouteComponent;
|
||||
meta?: {
|
||||
/** 菜单名称(兼容国际化、非国际化,如何用国际化的写法就必须在根目录的`locales`文件夹下对应添加) `必填` */
|
||||
title: string;
|
||||
/** 菜单图标 `可选` */
|
||||
icon?: string | FunctionalComponent | IconifyIcon;
|
||||
/** 菜单名称右侧的额外图标 */
|
||||
extraIcon?: string | FunctionalComponent | IconifyIcon;
|
||||
/** 是否在菜单中显示(默认`true`)`可选` */
|
||||
showLink?: boolean;
|
||||
/** 是否显示父级菜单 `可选` */
|
||||
showParent?: boolean;
|
||||
/** 页面级别权限设置 `可选` */
|
||||
roles?: Array<string>;
|
||||
/** 按钮级别权限设置 `可选` */
|
||||
auths?: Array<string>;
|
||||
/** 路由组件缓存(开启 `true`、关闭 `false`)`可选` */
|
||||
keepAlive?: boolean;
|
||||
/** 内嵌的`iframe`链接 `可选` */
|
||||
frameSrc?: string;
|
||||
/** `iframe`页是否开启首次加载动画(默认`true`)`可选` */
|
||||
frameLoading?: boolean;
|
||||
/** 页面加载动画(有两种形式,一种直接采用vue内置的`transitions`动画,另一种是使用`animate.css`写进、离场动画)`可选` */
|
||||
transition?: {
|
||||
/**
|
||||
* @description 当前路由动画效果
|
||||
* @see {@link https://next.router.vuejs.org/guide/advanced/transitions.html#transitions}
|
||||
* @see animate.css {@link https://animate.style}
|
||||
*/
|
||||
name?: string;
|
||||
/** 进场动画 */
|
||||
enterTransition?: string;
|
||||
/** 离场动画 */
|
||||
leaveTransition?: string;
|
||||
};
|
||||
// 是否不添加信息到标签页,(默认`false`)
|
||||
hiddenTag?: boolean;
|
||||
/** 动态路由可打开的最大数量 `可选` */
|
||||
dynamicLevel?: number;
|
||||
};
|
||||
/** 子路由配置项 */
|
||||
children?: Array<RouteChildrenConfigsTable>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 整体路由配置表(包括完整子路由)
|
||||
*/
|
||||
interface RouteConfigsTable {
|
||||
/** 路由地址 `必填` */
|
||||
path: string;
|
||||
/** 路由名字(保持唯一)`可选` */
|
||||
name?: string;
|
||||
/** `Layout`组件 `可选` */
|
||||
component?: RouteComponent;
|
||||
/** 路由重定向 `可选` */
|
||||
redirect?: string;
|
||||
meta?: {
|
||||
/** 菜单名称(兼容国际化、非国际化,如何用国际化的写法就必须在根目录的`locales`文件夹下对应添加)`必填` */
|
||||
title: string;
|
||||
/** 菜单图标 `可选` */
|
||||
icon?: string | FunctionalComponent | IconifyIcon;
|
||||
/** 是否在菜单中显示(默认`true`)`可选` */
|
||||
showLink?: boolean;
|
||||
/** 菜单升序排序,值越高排的越后(只针对顶级路由)`可选` */
|
||||
rank?: number;
|
||||
};
|
||||
/** 子路由配置项 */
|
||||
children?: Array<RouteChildrenConfigsTable>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台里所有组件实例都能访问到的全局属性对象的类型声明
|
||||
*/
|
||||
|
||||
105
types/router.d.ts
vendored
Normal file
105
types/router.d.ts
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
// 全局路由类型声明
|
||||
|
||||
import { type RouteComponent, type RouteLocationNormalized } from "vue-router";
|
||||
|
||||
declare global {
|
||||
interface ToRouteType extends RouteLocationNormalized {
|
||||
meta: CustomizeRouteMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 完整子路由的`meta`配置表
|
||||
*/
|
||||
interface CustomizeRouteMeta {
|
||||
/** 菜单名称(兼容国际化、非国际化,如何用国际化的写法就必须在根目录的`locales`文件夹下对应添加) `必填` */
|
||||
title: string;
|
||||
/** 菜单图标 `可选` */
|
||||
icon?: string | FunctionalComponent | IconifyIcon;
|
||||
/** 菜单名称右侧的额外图标 */
|
||||
extraIcon?: string | FunctionalComponent | IconifyIcon;
|
||||
/** 是否在菜单中显示(默认`true`)`可选` */
|
||||
showLink?: boolean;
|
||||
/** 是否显示父级菜单 `可选` */
|
||||
showParent?: boolean;
|
||||
/** 页面级别权限设置 `可选` */
|
||||
roles?: Array<string>;
|
||||
/** 按钮级别权限设置 `可选` */
|
||||
auths?: Array<string>;
|
||||
/** 路由组件缓存(开启 `true`、关闭 `false`)`可选` */
|
||||
keepAlive?: boolean;
|
||||
/** 内嵌的`iframe`链接 `可选` */
|
||||
frameSrc?: string;
|
||||
/** `iframe`页是否开启首次加载动画(默认`true`)`可选` */
|
||||
frameLoading?: boolean;
|
||||
/** 页面加载动画(有两种形式,一种直接采用vue内置的`transitions`动画,另一种是使用`animate.css`写进、离场动画)`可选` */
|
||||
transition?: {
|
||||
/**
|
||||
* @description 当前路由动画效果
|
||||
* @see {@link https://next.router.vuejs.org/guide/advanced/transitions.html#transitions}
|
||||
* @see animate.css {@link https://animate.style}
|
||||
*/
|
||||
name?: string;
|
||||
/** 进场动画 */
|
||||
enterTransition?: string;
|
||||
/** 离场动画 */
|
||||
leaveTransition?: string;
|
||||
};
|
||||
// 是否不添加信息到标签页,(默认`false`)
|
||||
hiddenTag?: boolean;
|
||||
/** 动态路由可打开的最大数量 `可选` */
|
||||
dynamicLevel?: number;
|
||||
/** 将某个菜单激活
|
||||
* (主要用于通过`query`或`params`传参的路由,当它们通过配置`showLink: false`后不在菜单中显示,就不会有任何菜单高亮,
|
||||
* 而通过设置`activePath`指定激活菜单即可获得高亮,`activePath`为指定激活菜单的`path`)
|
||||
*/
|
||||
activePath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 完整子路由配置表
|
||||
*/
|
||||
interface RouteChildrenConfigsTable {
|
||||
/** 子路由地址 `必填` */
|
||||
path: string;
|
||||
/** 路由名字(对应不要重复,和当前组件的`name`保持一致)`必填` */
|
||||
name?: string;
|
||||
/** 路由重定向 `可选` */
|
||||
redirect?: string;
|
||||
/** 按需加载组件 `可选` */
|
||||
component?: RouteComponent;
|
||||
meta?: CustomizeRouteMeta;
|
||||
/** 子路由配置项 */
|
||||
children?: Array<RouteChildrenConfigsTable>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 整体路由配置表(包括完整子路由)
|
||||
*/
|
||||
interface RouteConfigsTable {
|
||||
/** 路由地址 `必填` */
|
||||
path: string;
|
||||
/** 路由名字(保持唯一)`可选` */
|
||||
name?: string;
|
||||
/** `Layout`组件 `可选` */
|
||||
component?: RouteComponent;
|
||||
/** 路由重定向 `可选` */
|
||||
redirect?: string;
|
||||
meta?: {
|
||||
/** 菜单名称(兼容国际化、非国际化,如何用国际化的写法就必须在根目录的`locales`文件夹下对应添加)`必填` */
|
||||
title: string;
|
||||
/** 菜单图标 `可选` */
|
||||
icon?: string | FunctionalComponent | IconifyIcon;
|
||||
/** 是否在菜单中显示(默认`true`)`可选` */
|
||||
showLink?: boolean;
|
||||
/** 菜单升序排序,值越高排的越后(只针对顶级路由)`可选` */
|
||||
rank?: number;
|
||||
};
|
||||
/** 子路由配置项 */
|
||||
children?: Array<RouteChildrenConfigsTable>;
|
||||
}
|
||||
}
|
||||
|
||||
// https://router.vuejs.org/zh/guide/advanced/meta.html#typescript
|
||||
declare module "vue-router" {
|
||||
interface RouteMeta extends CustomizeRouteMeta {}
|
||||
}
|
||||
Reference in New Issue
Block a user