fix:完善字典管理功能优化全局组件
This commit is contained in:
@@ -40,7 +40,6 @@
|
|||||||
[x] i18n注入 语言全局化实现
|
[x] i18n注入 语言全局化实现
|
||||||
[x] 状态管理工具Pinia实现
|
[x] 状态管理工具Pinia实现
|
||||||
[x] ElementPlus 组件引入 并且实现i18n
|
[x] ElementPlus 组件引入 并且实现i18n
|
||||||
[] 引入unocss 样式
|
[x] 路由由后端控制实现动态路由
|
||||||
[] 路由由后端控制实现动态路由
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
6
components.d.ts
vendored
6
components.d.ts
vendored
@@ -27,6 +27,7 @@ declare module 'vue' {
|
|||||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
|
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||||
@@ -49,6 +50,8 @@ declare module 'vue' {
|
|||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
|
ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
|
||||||
|
ElSkeletonItem: typeof import('element-plus/es')['ElSkeletonItem']
|
||||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||||
ElTable: typeof import('element-plus/es')['ElTable']
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||||
@@ -59,8 +62,10 @@ declare module 'vue' {
|
|||||||
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
|
||||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
ElTree: typeof import('element-plus/es')['ElTree']
|
ElTree: typeof import('element-plus/es')['ElTree']
|
||||||
|
EmojiPicker: typeof import('./src/components/comment/emojiPicker.vue')['default']
|
||||||
GlobaIcon: typeof import('./src/components/globaIcon/index.vue')['default']
|
GlobaIcon: typeof import('./src/components/globaIcon/index.vue')['default']
|
||||||
GlobalIcon: typeof import('./src/components/GlobalIcon/index.vue')['default']
|
GlobalIcon: typeof import('./src/components/GlobalIcon/index.vue')['default']
|
||||||
|
NameAvatar: typeof import('./src/components/nameAvatar/index.vue')['default']
|
||||||
OverflowTabs: typeof import('./src/components/overflowTabs/index.vue')['default']
|
OverflowTabs: typeof import('./src/components/overflowTabs/index.vue')['default']
|
||||||
PageForm: typeof import('./src/components/pageForm/index.vue')['default']
|
PageForm: typeof import('./src/components/pageForm/index.vue')['default']
|
||||||
ProTable: typeof import('./src/components/proTable/index.vue')['default']
|
ProTable: typeof import('./src/components/proTable/index.vue')['default']
|
||||||
@@ -69,6 +74,7 @@ declare module 'vue' {
|
|||||||
StageBreadcrumbs: typeof import('./src/components/stageBreadcrumbs/index.vue')['default']
|
StageBreadcrumbs: typeof import('./src/components/stageBreadcrumbs/index.vue')['default']
|
||||||
StandardMenu: typeof import('./src/components/standardMenu/index.vue')['default']
|
StandardMenu: typeof import('./src/components/standardMenu/index.vue')['default']
|
||||||
StandMenu: typeof import('./src/components/standMenu/index.vue')['default']
|
StandMenu: typeof import('./src/components/standMenu/index.vue')['default']
|
||||||
|
Xxx: typeof import('./src/components/comment/xxx.vue')['default']
|
||||||
}
|
}
|
||||||
export interface GlobalDirectives {
|
export interface GlobalDirectives {
|
||||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"sass": "^1.97.1",
|
"sass": "^1.97.1",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
|
"unicode-emoji-json": "^0.8.0",
|
||||||
"unplugin-auto-import": "^20.3.0",
|
"unplugin-auto-import": "^20.3.0",
|
||||||
"unplugin-vue-components": "^30.0.0",
|
"unplugin-vue-components": "^30.0.0",
|
||||||
"vite": "^7.2.4",
|
"vite": "^7.2.4",
|
||||||
|
|||||||
25
pnpm-lock.yaml
generated
25
pnpm-lock.yaml
generated
@@ -51,6 +51,9 @@ importers:
|
|||||||
typescript:
|
typescript:
|
||||||
specifier: ~5.9.3
|
specifier: ~5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
unicode-emoji-json:
|
||||||
|
specifier: ^0.8.0
|
||||||
|
version: 0.8.0
|
||||||
unplugin-auto-import:
|
unplugin-auto-import:
|
||||||
specifier: ^20.3.0
|
specifier: ^20.3.0
|
||||||
version: 20.3.0(@vueuse/core@10.11.1(vue@3.5.26(typescript@5.9.3)))
|
version: 20.3.0(@vueuse/core@10.11.1(vue@3.5.26(typescript@5.9.3)))
|
||||||
@@ -317,42 +320,36 @@ packages:
|
|||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm-musl@2.5.1':
|
'@parcel/watcher-linux-arm-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
'@parcel/watcher-linux-arm64-glibc@2.5.1':
|
||||||
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
'@parcel/watcher-linux-arm64-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
'@parcel/watcher-linux-x64-glibc@2.5.1':
|
||||||
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-musl@2.5.1':
|
'@parcel/watcher-linux-x64-musl@2.5.1':
|
||||||
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@parcel/watcher-win32-arm64@2.5.1':
|
'@parcel/watcher-win32-arm64@2.5.1':
|
||||||
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
|
||||||
@@ -413,67 +410,56 @@ packages:
|
|||||||
resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==}
|
resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.54.0':
|
'@rollup/rollup-linux-arm-musleabihf@4.54.0':
|
||||||
resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==}
|
resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.54.0':
|
'@rollup/rollup-linux-arm64-gnu@4.54.0':
|
||||||
resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==}
|
resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.54.0':
|
'@rollup/rollup-linux-arm64-musl@4.54.0':
|
||||||
resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==}
|
resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-loong64-gnu@4.54.0':
|
'@rollup/rollup-linux-loong64-gnu@4.54.0':
|
||||||
resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==}
|
resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-ppc64-gnu@4.54.0':
|
'@rollup/rollup-linux-ppc64-gnu@4.54.0':
|
||||||
resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==}
|
resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.54.0':
|
'@rollup/rollup-linux-riscv64-gnu@4.54.0':
|
||||||
resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==}
|
resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-musl@4.54.0':
|
'@rollup/rollup-linux-riscv64-musl@4.54.0':
|
||||||
resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==}
|
resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.54.0':
|
'@rollup/rollup-linux-s390x-gnu@4.54.0':
|
||||||
resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==}
|
resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.54.0':
|
'@rollup/rollup-linux-x64-gnu@4.54.0':
|
||||||
resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==}
|
resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.54.0':
|
'@rollup/rollup-linux-x64-musl@4.54.0':
|
||||||
resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==}
|
resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-openharmony-arm64@4.54.0':
|
'@rollup/rollup-openharmony-arm64@4.54.0':
|
||||||
resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==}
|
resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==}
|
||||||
@@ -959,6 +945,9 @@ packages:
|
|||||||
undici-types@7.16.0:
|
undici-types@7.16.0:
|
||||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||||
|
|
||||||
|
unicode-emoji-json@0.8.0:
|
||||||
|
resolution: {integrity: sha512-3wDXXvp6YGoKGhS2O2H7+V+bYduOBydN1lnI0uVfr1cIdY02uFFiEH1i3kE5CCE4l6UqbLKVmEFW9USxTAMD1g==}
|
||||||
|
|
||||||
unimport@5.6.0:
|
unimport@5.6.0:
|
||||||
resolution: {integrity: sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==}
|
resolution: {integrity: sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==}
|
||||||
engines: {node: '>=18.12.0'}
|
engines: {node: '>=18.12.0'}
|
||||||
@@ -1872,6 +1861,8 @@ snapshots:
|
|||||||
|
|
||||||
undici-types@7.16.0: {}
|
undici-types@7.16.0: {}
|
||||||
|
|
||||||
|
unicode-emoji-json@0.8.0: {}
|
||||||
|
|
||||||
unimport@5.6.0:
|
unimport@5.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.15.0
|
acorn: 8.15.0
|
||||||
|
|||||||
@@ -66,26 +66,26 @@ export const saveDictTypeValue = (id:string,data: addDataProps) => {
|
|||||||
|
|
||||||
// 删除字典类型值
|
// 删除字典类型值
|
||||||
export const deleteDictTypeValue = (typeId:string,id: string) => {
|
export const deleteDictTypeValue = (typeId:string,id: string) => {
|
||||||
return request.delete(`/auth/v1/backend/dict/${typeId}/${id}`);
|
return request.delete(`/auth/v1/backend/dict/type/${typeId}/${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新字典类型值
|
// 更新字典类型值
|
||||||
export const updateDictTypeValue = (typeId:string,id: string,data: addDataProps) => {
|
export const updateDictTypeValue = (typeId:string,id: string,data: addDataProps) => {
|
||||||
return request.put(`/auth/v1/backend/dict/${typeId}/${id}`, data);
|
return request.put(`/auth/v1/backend/dict/type/${typeId}/${id}`, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取下级菜单数据
|
// 获取下级菜单数据
|
||||||
export const getNextDictMenu = (id:string,parentId:string) => {
|
export const getNextDictMenu = (id:string,parentId:string,params: paramsProps) => {
|
||||||
return request.get(`/auth/v1/backend/dict/type/${id}/data/${parentId}`);
|
return request.get(`/auth/v1/backend/dict/type/${id}/data/${parentId}`,params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 启用接口
|
// 启用接口
|
||||||
export const enableTypeDict = (id:string)=>{
|
export const enableTypeDict = (typeId:string,id:string)=>{
|
||||||
return request.post(`/auth/v1/backend/dict/type/${typeId}/${id}/enable`);
|
return request.post(`/auth/v1/backend/dict/type/${typeId}/${id}/enable`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 禁用接口
|
// 禁用接口
|
||||||
export const disableTypeDict = (id:string)=>{
|
export const disableTypeDict = (typeId:string,id:string)=>{
|
||||||
return request.post(`/auth/v1/backend/dict/type/${typeId}/${id}/disable`)
|
return request.post(`/auth/v1/backend/dict/type/${typeId}/${id}/disable`)
|
||||||
}
|
}
|
||||||
@@ -1,329 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="comment-app">
|
|
||||||
<section class="main-publisher">
|
|
||||||
<el-input
|
|
||||||
v-model="mainInput"
|
|
||||||
type="textarea"
|
|
||||||
:rows="3"
|
|
||||||
placeholder="发一条友善的评论吧..."
|
|
||||||
resize="none"
|
|
||||||
/>
|
|
||||||
<div class="pub-footer">
|
|
||||||
<div class="tools">
|
|
||||||
<el-button link class="tool-item">
|
|
||||||
<el-icon><Picture /></el-icon> 插入图片 (预留)
|
|
||||||
</el-button>
|
|
||||||
<el-button link class="tool-item">
|
|
||||||
<span class="emoji-icon">😀</span> 表情 (预留)
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
<el-button type="primary" round @click="submitMainComment"
|
|
||||||
>发布评论</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="comment-list">
|
|
||||||
<div v-for="item in commentData" :key="item.id" class="comment-group">
|
|
||||||
<div class="parent-node">
|
|
||||||
<el-avatar :size="42" :src="item.avatar" />
|
|
||||||
<div class="node-main">
|
|
||||||
<div class="user-info">
|
|
||||||
<span class="nickname">{{ item.nickname }}</span>
|
|
||||||
<el-tag v-if="item.isOwner" size="small">作者</el-tag>
|
|
||||||
</div>
|
|
||||||
<div class="content">{{ item.content }}</div>
|
|
||||||
<div class="node-footer">
|
|
||||||
<span class="time">{{ item.time }}</span>
|
|
||||||
<div class="actions">
|
|
||||||
<el-button link @click="openReply(item, item)">回复</el-button>
|
|
||||||
<el-button link>点赞</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="item.children?.length" class="sub-container">
|
|
||||||
<div
|
|
||||||
v-for="reply in item.children"
|
|
||||||
:key="reply.id"
|
|
||||||
class="sub-node"
|
|
||||||
>
|
|
||||||
<el-avatar :size="24" :src="reply.avatar" />
|
|
||||||
<div class="sub-main">
|
|
||||||
<div class="sub-text">
|
|
||||||
<span class="sub-nickname">{{ reply.nickname }}</span>
|
|
||||||
<template v-if="reply.replyTo !== item.nickname">
|
|
||||||
<span class="reply-label">回复</span>
|
|
||||||
<span class="target-name">@{{ reply.replyTo }}</span>
|
|
||||||
</template>
|
|
||||||
<span class="sep">:</span>
|
|
||||||
<span class="content-body">{{ reply.content }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="node-footer">
|
|
||||||
<span class="time">{{ reply.time }}</span>
|
|
||||||
<el-button link @click="openReply(reply, item)"
|
|
||||||
>回复</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="activeReply.groupId === item.id"
|
|
||||||
class="inline-publisher"
|
|
||||||
>
|
|
||||||
<el-input
|
|
||||||
v-model="replyInput"
|
|
||||||
:placeholder="`回复 @${activeReply.targetName}...`"
|
|
||||||
type="textarea"
|
|
||||||
autosize
|
|
||||||
/>
|
|
||||||
<div class="pub-footer">
|
|
||||||
<div class="tools">
|
|
||||||
<el-button link size="small">😀 表情</el-button>
|
|
||||||
</div>
|
|
||||||
<div class="btns">
|
|
||||||
<el-button size="small" @click="cancelReply">取消</el-button>
|
|
||||||
<el-button
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
round
|
|
||||||
@click="submitReply"
|
|
||||||
>发布</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, reactive } from "vue";
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import { Picture } from "@element-plus/icons-vue";
|
|
||||||
|
|
||||||
// 1. 模拟当前登录用户
|
|
||||||
const currentUser = {
|
|
||||||
nickname: "前端练习生",
|
|
||||||
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=Lucky",
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. 响应式数据
|
|
||||||
const mainInput = ref("");
|
|
||||||
const replyInput = ref("");
|
|
||||||
const commentData = ref([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
nickname: "张三",
|
|
||||||
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=1",
|
|
||||||
content: "扁平化递归不仅性能好,在手机端显示也非常整齐!",
|
|
||||||
time: "2小时前",
|
|
||||||
isOwner: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: 101,
|
|
||||||
nickname: "李四",
|
|
||||||
replyTo: "张三",
|
|
||||||
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=2",
|
|
||||||
content: "确实,无限缩进到后面根本没法看。",
|
|
||||||
time: "1小时前",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 记录当前正在回复的状态
|
|
||||||
const activeReply = reactive({
|
|
||||||
groupId: null, // 所属的一级评论ID
|
|
||||||
targetName: "", // 正在回复的具体人名
|
|
||||||
targetId: null, // 正在回复的评论ID
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. 逻辑方法
|
|
||||||
const submitMainComment = () => {
|
|
||||||
if (!mainInput.value.trim()) return ElMessage.warning("内容不能为空");
|
|
||||||
|
|
||||||
const newComment = {
|
|
||||||
id: Date.now(),
|
|
||||||
...currentUser,
|
|
||||||
content: mainInput.value,
|
|
||||||
time: "刚刚",
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
commentData.value.unshift(newComment);
|
|
||||||
mainInput.value = "";
|
|
||||||
ElMessage.success("发表成功");
|
|
||||||
};
|
|
||||||
|
|
||||||
const openReply = (target, group) => {
|
|
||||||
activeReply.groupId = group.id;
|
|
||||||
activeReply.targetName = target.nickname;
|
|
||||||
activeReply.targetId = target.id;
|
|
||||||
replyInput.value = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelReply = () => {
|
|
||||||
activeReply.groupId = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitReply = () => {
|
|
||||||
if (!replyInput.value.trim()) return ElMessage.warning("回复内容不能为空");
|
|
||||||
|
|
||||||
const targetGroup = commentData.value.find(
|
|
||||||
(item) => item.id === activeReply.groupId
|
|
||||||
);
|
|
||||||
if (targetGroup) {
|
|
||||||
const newReply = {
|
|
||||||
id: Date.now(),
|
|
||||||
...currentUser,
|
|
||||||
replyTo: activeReply.targetName,
|
|
||||||
content: replyInput.value,
|
|
||||||
time: "刚刚",
|
|
||||||
};
|
|
||||||
targetGroup.children.push(newReply);
|
|
||||||
ElMessage.success("回复成功");
|
|
||||||
cancelReply();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
$color-primary: #409eff;
|
|
||||||
$color-bg-sub: #f5f7fa;
|
|
||||||
$color-text-main: #303133;
|
|
||||||
$color-text-sub: #909399;
|
|
||||||
$border-color: #ebeef5;
|
|
||||||
|
|
||||||
.comment-app {
|
|
||||||
max-width: 760px;
|
|
||||||
margin: 40px auto;
|
|
||||||
padding: 0 20px;
|
|
||||||
|
|
||||||
// 发布区域公共样式
|
|
||||||
.main-publisher,
|
|
||||||
.inline-publisher {
|
|
||||||
border: 1px solid $border-color;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
.pub-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 10px;
|
|
||||||
|
|
||||||
.tool-item {
|
|
||||||
color: $color-text-sub;
|
|
||||||
font-size: 14px;
|
|
||||||
.emoji-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-publisher {
|
|
||||||
margin-top: 16px;
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-list {
|
|
||||||
margin-top: 32px;
|
|
||||||
|
|
||||||
.comment-group {
|
|
||||||
margin-bottom: 28px;
|
|
||||||
|
|
||||||
.parent-node {
|
|
||||||
display: flex;
|
|
||||||
gap: 14px;
|
|
||||||
|
|
||||||
.node-main {
|
|
||||||
flex: 1;
|
|
||||||
.user-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
.nickname {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
color: $color-text-main;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
margin: 8px 0;
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: $color-text-main;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 底部信息和动作
|
|
||||||
.node-footer {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 15px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: $color-text-sub;
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
.el-button {
|
|
||||||
font-size: 13px;
|
|
||||||
color: $color-text-sub;
|
|
||||||
&:hover {
|
|
||||||
color: $color-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 扁平化子评论容器
|
|
||||||
.sub-container {
|
|
||||||
background-color: $color-bg-sub;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 4px 12px 12px;
|
|
||||||
margin-top: 12px;
|
|
||||||
|
|
||||||
.sub-node {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 12px;
|
|
||||||
|
|
||||||
.sub-main {
|
|
||||||
flex: 1;
|
|
||||||
.sub-text {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.5;
|
|
||||||
.sub-nickname {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
.reply-label {
|
|
||||||
margin: 0 4px;
|
|
||||||
color: $color-text-sub;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
.target-name {
|
|
||||||
color: $color-primary;
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
.content-body {
|
|
||||||
color: $color-text-main;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
57
src/components/nameAvatar/index.vue
Normal file
57
src/components/nameAvatar/index.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<el-avatar
|
||||||
|
:size="size"
|
||||||
|
:src="src"
|
||||||
|
:style="{ backgroundColor: !src ? bgColor : '' }"
|
||||||
|
class="mj-name-avatar"
|
||||||
|
>
|
||||||
|
<span v-if="!src" class="avatar-text">{{ displayText }}</span>
|
||||||
|
</el-avatar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
defineOptions({name: 'NameAvatar'})
|
||||||
|
const props = defineProps({
|
||||||
|
name: { type: String, default: '' },
|
||||||
|
src: { type: String, default: '' },
|
||||||
|
size: { type: Number, default: 40 }
|
||||||
|
});
|
||||||
|
|
||||||
|
const displayText = computed(() => {
|
||||||
|
return props.name ? props.name.charAt(0) : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const bgColor = computed(() => {
|
||||||
|
if (!props.name) return '#409EFF';
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < props.name.length; i++) {
|
||||||
|
hash = props.name.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
const colors = [
|
||||||
|
'#337ecc', '#409eff', '#53a8ff', '#79bbff', '#95d475',
|
||||||
|
'#eebe77', '#f89898', '#b37feb', '#ff85c0',
|
||||||
|
'#52c41a', '#faad14', '#f5222d', '#722ed1', '#13c2c2',
|
||||||
|
'#eb2f96', '#a0d911', '#fa8c16', '#e74c3c', '#9b59b6',
|
||||||
|
'#1abc9c', '#34495e', '#f39c12', '#e67e22', '#3498db',
|
||||||
|
'#9b59b6', '#2ecc71', '#f1c40f', '#d35400', '#7f8c8d'
|
||||||
|
];
|
||||||
|
return colors[Math.abs(hash) % colors.length];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.mj-name-avatar {
|
||||||
|
--el-avatar-bg-color:transparent;
|
||||||
|
user-select: none;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
.avatar-text {
|
||||||
|
color: var(--el-avatar-text-color);
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,12 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="pro-table-container">
|
<div class="pro-table-container">
|
||||||
<el-table :data="data" v-bind="$attrs" v-loading="innerLoading" class="hover-action-table" header-row-class-name="header-row-name">
|
<el-table
|
||||||
<template v-for="(col,index) in columns" :key="col.prop">
|
:data="data"
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-loading="innerLoading"
|
||||||
|
class="hover-action-table"
|
||||||
|
header-row-class-name="header-row-name"
|
||||||
|
>
|
||||||
|
<template v-for="(col, index) in columns" :key="col.prop">
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-if="!col.slot && col.prop !== 'actions'"
|
v-if="!col.slot && col.prop !== 'actions'"
|
||||||
v-bind="col"
|
v-bind="col"
|
||||||
>
|
>
|
||||||
<template #default="scope" v-if="!col.formatter">{{ scope.row[col.prop] || "-" }}</template>
|
<template #default="scope" v-if="!col.formatter">{{
|
||||||
|
scope.row[col.prop] || "-"
|
||||||
|
}}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column v-else-if="col.slot" v-bind="col">
|
<el-table-column v-else-if="col.slot" v-bind="col">
|
||||||
@@ -22,7 +30,78 @@
|
|||||||
<el-table-column v-else-if="col.prop === 'actions'" v-bind="col">
|
<el-table-column v-else-if="col.prop === 'actions'" v-bind="col">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="action-group">
|
<div class="action-group">
|
||||||
<slot name="actions" :row="scope.row"></slot>
|
<slot name="actions" :row="scope.row">
|
||||||
|
<template v-if="col.actions && col.actions.length > 0">
|
||||||
|
<!-- 显示前maxButtons个按钮 -->
|
||||||
|
<template
|
||||||
|
v-for="(btn, idx) in col.actions.slice(
|
||||||
|
0,
|
||||||
|
getVisibleButtonCount(col)
|
||||||
|
)"
|
||||||
|
:key="idx"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="!shouldHideButton(btn, scope.row)"
|
||||||
|
v-permission="btn.permission"
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-bind="getButtonProps(btn)"
|
||||||
|
@click="handleButtonClick(btn, scope.row)"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
typeof btn.label === "function"
|
||||||
|
? btn.label(scope.row)
|
||||||
|
: btn.label
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 如果按钮超过maxButtons,显示下拉菜单 -->
|
||||||
|
<el-dropdown
|
||||||
|
v-if="
|
||||||
|
col.actions.length >
|
||||||
|
(col.maxButtons || MAX_BUTTON_LENGTH) &&
|
||||||
|
hasDropdownPermission(
|
||||||
|
col.actions.slice(col.maxButtons || MAX_BUTTON_LENGTH)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="dropdown-menu-table"
|
||||||
|
trigger="hover"
|
||||||
|
>
|
||||||
|
<el-button link type="primary">
|
||||||
|
{{ col.dropdownText || "更多" }}
|
||||||
|
<el-icon><arrow-down /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<template
|
||||||
|
v-for="(btn, idx) in col.actions.slice(getVisibleButtonCount(col), col.actions.length)"
|
||||||
|
:key="idx"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-permission="btn.permission"
|
||||||
|
v-if="!shouldHideButton(btn, scope.row)"
|
||||||
|
>
|
||||||
|
<el-dropdown-item>
|
||||||
|
<el-button
|
||||||
|
v-bind="getButtonProps(btn)"
|
||||||
|
@click="handleButtonClick(btn, scope.row)"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
typeof btn.label === "function"
|
||||||
|
? btn.label(scope.row)
|
||||||
|
: btn.label
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</template>
|
||||||
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -54,7 +133,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
|
import { ArrowDown } from "@element-plus/icons-vue";
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
columns: { type: Array, required: true },
|
columns: { type: Array, required: true },
|
||||||
data: { type: Array, required: true },
|
data: { type: Array, required: true },
|
||||||
@@ -64,40 +143,50 @@ const props = defineProps({
|
|||||||
// 是否立即请求数据
|
// 是否立即请求数据
|
||||||
immediate: { type: Boolean, default: true },
|
immediate: { type: Boolean, default: true },
|
||||||
// 是否在激活时刷新数据
|
// 是否在激活时刷新数据
|
||||||
refreshOnActivated: { type: Boolean, default: true }
|
refreshOnActivated: { type: Boolean, default: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(["current-change", "size-change", "update:data", "update:total"]);
|
const emit = defineEmits([
|
||||||
|
"current-change",
|
||||||
|
"size-change",
|
||||||
|
"update:data",
|
||||||
|
"update:total",
|
||||||
|
]);
|
||||||
|
|
||||||
// 内部控制 loading
|
// 内部控制 loading
|
||||||
const innerLoading = ref(false);
|
const innerLoading = ref(false);
|
||||||
|
|
||||||
|
// 默认按钮长度
|
||||||
|
const MAX_BUTTON_LENGTH = 3;
|
||||||
|
|
||||||
// 标记是否是首次挂载
|
// 标记是否是首次挂载
|
||||||
let isFirstMount = true;
|
let isFirstMount = true;
|
||||||
|
|
||||||
// 参数传递逻辑
|
// 参数传递逻辑
|
||||||
const params = computed(()=>{
|
const params = computed(() => {
|
||||||
if(!props.pagination){
|
if (!props.pagination) {
|
||||||
return {}
|
return {};
|
||||||
}else if(typeof props.pagination === 'object' && props.pagination !== null){
|
} else if (
|
||||||
|
typeof props.pagination === "object" &&
|
||||||
|
props.pagination !== null
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
pageNo:props.pagination.currentPage,
|
pageNo: props.pagination.currentPage,
|
||||||
pageSize:props.pagination.pageSize
|
pageSize: props.pagination.pageSize,
|
||||||
}
|
};
|
||||||
}else{
|
} else {
|
||||||
return {
|
return {
|
||||||
pageNo:1,
|
pageNo: 1,
|
||||||
pageSize:20
|
pageSize: 20,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
// 请求方法
|
// 请求方法
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
if (!props.requestApi) return;
|
if (!props.requestApi) return;
|
||||||
innerLoading.value = true;
|
innerLoading.value = true;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const res = await props.requestApi(params.value);
|
const res = await props.requestApi(params.value);
|
||||||
emit("update:data", res?.records || []);
|
emit("update:data", res?.records || []);
|
||||||
emit("update:total", res?.total || 0);
|
emit("update:total", res?.total || 0);
|
||||||
@@ -123,7 +212,9 @@ onMounted(() => {
|
|||||||
if (props.immediate) {
|
if (props.immediate) {
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
setTimeout(() => { isFirstMount = false; }, 0);
|
setTimeout(() => {
|
||||||
|
isFirstMount = false;
|
||||||
|
}, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 兼容设置了keep-alive
|
// 兼容设置了keep-alive
|
||||||
@@ -142,6 +233,35 @@ const paginationConfig = computed(() => ({
|
|||||||
background: false,
|
background: false,
|
||||||
...props.pagination,
|
...props.pagination,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// action按钮组的数据
|
||||||
|
const handleButtonClick = (button, row) => {
|
||||||
|
if (button.onClick) {
|
||||||
|
button.onClick(row);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const shouldHideButton = (button, row) => {
|
||||||
|
if (typeof button.show === "function") {
|
||||||
|
return !button.show(row);
|
||||||
|
}
|
||||||
|
return button.show === false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getVisibleButtonCount = (col) => {
|
||||||
|
const { actions, maxButtons } = col;
|
||||||
|
const totalButtons = maxButtons || MAX_BUTTON_LENGTH;
|
||||||
|
return totalButtons === actions.length ? totalButtons : Math.min(totalButtons, actions.length)-1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasDropdownPermission = (dropdownActions) => {
|
||||||
|
return dropdownActions.some(
|
||||||
|
(btn) => btn.permission && !shouldHideButton(btn, null)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const getButtonProps = (button) => {
|
||||||
|
const { label, onClick, show, permission, ...buttonProps } = button;
|
||||||
|
return buttonProps;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -152,9 +272,9 @@ const paginationConfig = computed(() => ({
|
|||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||||
border: 1px solid #f0f0f0;
|
border: 1px solid #f0f0f0;
|
||||||
|
|
||||||
:deep(.header-row-name){
|
:deep(.header-row-name) {
|
||||||
th.el-table__cell{
|
th.el-table__cell {
|
||||||
background-color: #FBFCFD;
|
background-color: #fbfcfd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,5 +322,8 @@ const paginationConfig = computed(() => ({
|
|||||||
color: #409eff;
|
color: #409eff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.dropdown-menu-table{
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const statusDict = {
|
|||||||
// 字典状态颜色
|
// 字典状态颜色
|
||||||
const statusDictColor = {
|
const statusDictColor = {
|
||||||
1:'#66E5BE',
|
1:'#66E5BE',
|
||||||
0:'#ff0000'
|
0:'#90A1B9'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置字典转换为目标格式
|
// 设置字典转换为目标格式
|
||||||
|
|||||||
@@ -21,13 +21,16 @@ const app = createApp(App);
|
|||||||
// 导入全局的i18n文件
|
// 导入全局的i18n文件
|
||||||
const loadLocalMessages = async () => {
|
const loadLocalMessages = async () => {
|
||||||
const messages: Record<string, any> = {};
|
const messages: Record<string, any> = {};
|
||||||
const locales = import.meta.glob("./locales/*.ts", { eager: true });
|
const locales = import.meta.glob(["./locales/*.ts","./modules/**/locales/*.ts"], { eager: true });
|
||||||
|
|
||||||
// 遍历所有匹配的文件
|
// 遍历所有匹配的文件
|
||||||
Object.keys(locales).forEach((path) => {
|
Object.keys(locales).forEach((path) => {
|
||||||
const lang = path.match(/\.\/locales\/(.+)\.ts$/)?.[1];
|
const lang = path.match(/.*\/locales\/(.+)\.ts$/)?.[1];
|
||||||
if (lang && locales[path]) {
|
if (lang && locales[path]) {
|
||||||
messages[lang] = locales[path].default;
|
if (!messages[lang]) {
|
||||||
|
messages[lang] = {};
|
||||||
|
}
|
||||||
|
messages[lang] = { ...messages[lang], ...locales[path].default };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
84
src/modules/Comment/emojiPicker.vue
Normal file
84
src/modules/Comment/emojiPicker.vue
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<el-popover
|
||||||
|
:placement="placement"
|
||||||
|
:width="width"
|
||||||
|
trigger="click"
|
||||||
|
popper-class="emoji-popover"
|
||||||
|
ref="emojiPopoverRef"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<slot>
|
||||||
|
<el-button link title="emoji" class="default-emoji-trigger">😊</el-button>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
<el-scrollbar height="240px">
|
||||||
|
<div class="emoji-container">
|
||||||
|
<span
|
||||||
|
v-for="emoji in emotionList"
|
||||||
|
:key="emoji"
|
||||||
|
class="emoji-item"
|
||||||
|
@click="selectEmoji(emoji)"
|
||||||
|
>
|
||||||
|
{{ emoji }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useEmoji } from "./useEmoji";
|
||||||
|
const { emotionList } = useEmoji();
|
||||||
|
const props = defineProps({
|
||||||
|
placement: { type: String, default: "bottom-start" },
|
||||||
|
width: { type: [Number, String], default: "auto" },
|
||||||
|
});
|
||||||
|
|
||||||
|
defineOptions({ name: "EmojiPicker" });
|
||||||
|
|
||||||
|
const emojiPopoverRef = ref(null);
|
||||||
|
const emit = defineEmits(["select"]);
|
||||||
|
|
||||||
|
// 筛选表情后的事件
|
||||||
|
const selectEmoji = (emoji) => {
|
||||||
|
emit("select", emoji);
|
||||||
|
nextTick(() => {
|
||||||
|
if (emojiPopoverRef.value) {
|
||||||
|
emojiPopoverRef.value.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.emoji-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
gap: 4px;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
.emoji-item {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 20px;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-scrollbar__bar.is-horizontal) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-emoji-trigger {
|
||||||
|
font-size: 20px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
539
src/modules/Comment/index.vue
Normal file
539
src/modules/Comment/index.vue
Normal file
@@ -0,0 +1,539 @@
|
|||||||
|
<template>
|
||||||
|
<div class="comment-app">
|
||||||
|
<section class="main-publisher">
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<el-input
|
||||||
|
v-model="mainInput"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
:placeholder="t('comment.placeholder')"
|
||||||
|
resize="none"
|
||||||
|
/>
|
||||||
|
<div class="input-tools">
|
||||||
|
<div class="left-icons">
|
||||||
|
<!-- 提到-后续迭代 -->
|
||||||
|
<!-- <el-button link title="提到">@</el-button> -->
|
||||||
|
<!-- 图片功能-后续迭代 -->
|
||||||
|
<!-- <el-button link title="图片"><el-icon><Picture /></el-icon></el-button> -->
|
||||||
|
<!-- 附件功能-后续迭代 -->
|
||||||
|
<!-- <el-button link title="附件"><el-icon><Paperclip /></el-icon></el-button> -->
|
||||||
|
|
||||||
|
<!-- 表情功能 -->
|
||||||
|
<emoji-picker @select="(e) => onSelectEmoji(e, 'main')" />
|
||||||
|
</div>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-button
|
||||||
|
class="send-btn"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
@click="submitMainComment"
|
||||||
|
>
|
||||||
|
<el-icon :size="20" :title="t('comment.send')"><Promotion /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="comment-list">
|
||||||
|
<!-- 骨架屏 -->
|
||||||
|
<div v-if="loading">
|
||||||
|
<div v-for="n in 2" :key="'skeleton-' + n" class="comment-group">
|
||||||
|
<el-skeleton :rows="1" :animated="true">
|
||||||
|
<template #template>
|
||||||
|
<div class="skeleton-comment-parent-node">
|
||||||
|
<el-skeleton-item variant="circle" class="skeleton-avatar" />
|
||||||
|
<div class="skeleton-node-main">
|
||||||
|
<div class="skeleton-user-info">
|
||||||
|
<el-skeleton-item
|
||||||
|
variant="p"
|
||||||
|
style="width: 80px; height: 18px; margin-right: 10px"
|
||||||
|
/>
|
||||||
|
<el-skeleton-item
|
||||||
|
variant="p"
|
||||||
|
style="width: 60px; height: 14px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<el-skeleton-item
|
||||||
|
variant="p"
|
||||||
|
style="width: 100%; height: 16px; margin: 8px 0"
|
||||||
|
/>
|
||||||
|
<el-skeleton-item
|
||||||
|
variant="p"
|
||||||
|
style="width: 70%; height: 16px; margin-bottom: 12px"
|
||||||
|
/>
|
||||||
|
<div class="skeleton-actions">
|
||||||
|
<el-skeleton-item
|
||||||
|
variant="button"
|
||||||
|
style="width: 40px; height: 24px; margin-right: 8px"
|
||||||
|
/>
|
||||||
|
<el-skeleton-item
|
||||||
|
variant="button"
|
||||||
|
style="width: 40px; height: 24px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-skeleton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 详细的内容展示 -->
|
||||||
|
<template v-else>
|
||||||
|
<div v-for="item in commentData" :key="item.id" class="comment-group">
|
||||||
|
<div class="parent-node">
|
||||||
|
<name-avatar :name="item.nickname" :src="item.avatar" :size="36" />
|
||||||
|
<div class="node-main">
|
||||||
|
<!-- 当前用户信息展示 -->
|
||||||
|
<div class="user-info">
|
||||||
|
<span class="nickname">{{ item.nickname }}</span>
|
||||||
|
<span class="time">{{ item.time }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 回复内容模块 -->
|
||||||
|
<div class="content" v-html="parseMention(item.content)"></div>
|
||||||
|
<!-- 回复内容-子集内容操作模块 -->
|
||||||
|
<div class="actions">
|
||||||
|
<el-button link @click="openReply(item, item)">
|
||||||
|
<el-icon><ChatDotSquare /></el-icon> 回复
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
class="delete-btn"
|
||||||
|
@click="deleteMainComment(item)"
|
||||||
|
v-if="item.canDelete"
|
||||||
|
>
|
||||||
|
<el-icon><Delete /></el-icon> 删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 回复内容展示(二级-子集评论内容) -->
|
||||||
|
<div v-if="item.children?.length" class="sub-container">
|
||||||
|
<div
|
||||||
|
v-for="reply in item.children"
|
||||||
|
:key="reply.id"
|
||||||
|
class="sub-node"
|
||||||
|
>
|
||||||
|
<name-avatar
|
||||||
|
:name="reply.nickname"
|
||||||
|
:src="reply.avatar"
|
||||||
|
:size="36"
|
||||||
|
/>
|
||||||
|
<div class="sub-main">
|
||||||
|
<div class="sub-header">
|
||||||
|
<div class="sub-user-info">
|
||||||
|
<span class="nickname">{{ reply.nickname }}</span>
|
||||||
|
<span class="reply-text">回复</span>
|
||||||
|
<span class="target-name">@{{ reply.replyTo }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="time">{{ reply.time }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="content-body">{{ reply.content }}</div>
|
||||||
|
<!-- 回复 删除功能 -->
|
||||||
|
<div class="actions">
|
||||||
|
<el-button link @click="openReply(item, item)">
|
||||||
|
回复
|
||||||
|
</el-button>
|
||||||
|
<!-- 删除功能-只有自己评论的可以删除 -->
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
class="delete-btn"
|
||||||
|
v-if="reply.canDelete"
|
||||||
|
@click="deleteReply(reply, item)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 统一回复输入框 -->
|
||||||
|
<div
|
||||||
|
v-if="activeReply.groupId === item.id"
|
||||||
|
class="inline-publisher"
|
||||||
|
>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<el-input
|
||||||
|
v-model="replyInput"
|
||||||
|
:placeholder="`${t('comment.reply')} @${activeReply.targetName}...`"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
resize="none"
|
||||||
|
/>
|
||||||
|
<div class="input-tools">
|
||||||
|
<div class="left-icons">
|
||||||
|
<!-- 表情功能 -->
|
||||||
|
<emoji-picker
|
||||||
|
@select="(e) => onSelectEmoji(e, 'reply')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-button
|
||||||
|
class="send-btn"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
@click="submitReply"
|
||||||
|
>
|
||||||
|
<el-icon :size="20" :title="t('comment.send')"><Promotion /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import EmojiPicker from "./EmojiPicker.vue";
|
||||||
|
import {
|
||||||
|
Picture,
|
||||||
|
Paperclip,
|
||||||
|
Promotion,
|
||||||
|
ChatDotSquare,
|
||||||
|
Delete,
|
||||||
|
} from "@element-plus/icons-vue";
|
||||||
|
import NameAvatar from "@/components/nameAvatar/index.vue";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useUserStore } from "@/store";
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
// 当前用户信息
|
||||||
|
const currentUser = computed(() => {
|
||||||
|
return {
|
||||||
|
nickname: userStore.userInfo.nickname,
|
||||||
|
avatar: userStore.userInfo.avatar,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 评论业务逻辑
|
||||||
|
const activeReply = reactive({ parentId: null, targetName: "" });
|
||||||
|
const mainInput = ref("");
|
||||||
|
const replyInput = ref("");
|
||||||
|
const loading = ref(true); //当前骨架屏显示
|
||||||
|
const commentData = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
nickname: "李星倩",
|
||||||
|
avatar: "",
|
||||||
|
content: "已完成ROI测算,请审核。",
|
||||||
|
time: "10分钟前",
|
||||||
|
canDelete: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 101,
|
||||||
|
nickname: "冯娜",
|
||||||
|
replyTo: "李星倩",
|
||||||
|
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=2",
|
||||||
|
content: "收到,数据已入库,我马上看下。",
|
||||||
|
time: "刚刚",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 回复
|
||||||
|
const openReply = (target, group) => {
|
||||||
|
activeReply.groupId = group.id;
|
||||||
|
activeReply.targetName = target.nickname;
|
||||||
|
replyInput.value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除回复-删除评论
|
||||||
|
const deleteReply = (target, group) => {
|
||||||
|
const index = group.children.findIndex((item) => item.id === target.id);
|
||||||
|
group.children.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除主评论-以及所有的子评论
|
||||||
|
const deleteMainComment = (target) => {
|
||||||
|
const index = commentData.value.findIndex((item) => item.id === target.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
commentData.value.splice(index, 1);
|
||||||
|
if (activeReply.groupId === target.id) {
|
||||||
|
cancelReply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// emoji输入框选择
|
||||||
|
const onSelectEmoji = (emoji, type) => {
|
||||||
|
console.log("emoji", emoji, type);
|
||||||
|
if (type === "main") {
|
||||||
|
mainInput.value += emoji;
|
||||||
|
} else {
|
||||||
|
replyInput.value += emoji;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelReply = () => {
|
||||||
|
activeReply.groupId = null;
|
||||||
|
};
|
||||||
|
// @ 识别解析函数
|
||||||
|
const parseMention = (text) => {
|
||||||
|
if (!text) return "";
|
||||||
|
// 基础转义
|
||||||
|
let safeText = text.replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
// 正则匹配 @用户,包裹为 span
|
||||||
|
return safeText.replace(
|
||||||
|
/@([\u4e00-\u9fa5\w-]+)/g,
|
||||||
|
'<span class="mention-link">@$1</span>'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// @提及 圈人操作
|
||||||
|
const handleMentionAction = (name) => {
|
||||||
|
const mentionStr = typeof name === "string" ? `@${name} ` : "@";
|
||||||
|
mainInput.value += mentionStr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitMainComment = () => {
|
||||||
|
if (!mainInput.value.trim()) return ElMessage.warning("内容不能为空");
|
||||||
|
commentData.value.unshift({
|
||||||
|
id: Date.now(),
|
||||||
|
...currentUser.value,
|
||||||
|
content: mainInput.value,
|
||||||
|
time: "刚刚",
|
||||||
|
canDelete: true,
|
||||||
|
children: [],
|
||||||
|
});
|
||||||
|
mainInput.value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitReply = () => {
|
||||||
|
const targetGroup = commentData.value.find(
|
||||||
|
(i) => i.id === activeReply.groupId
|
||||||
|
);
|
||||||
|
if (targetGroup) {
|
||||||
|
targetGroup.children.push({
|
||||||
|
id: Date.now(),
|
||||||
|
...currentUser.value,
|
||||||
|
replyTo: activeReply.targetName,
|
||||||
|
content: replyInput.value,
|
||||||
|
time: "刚刚",
|
||||||
|
});
|
||||||
|
cancelReply();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
loading.value = false;
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
$color-blue: #409eff;
|
||||||
|
$color-blue-bg: #f5f8ff;
|
||||||
|
$color-text-main: #303133;
|
||||||
|
$color-text-sub: #99a2aa;
|
||||||
|
$color-border: #e4e7ed;
|
||||||
|
$color-white: #fff;
|
||||||
|
|
||||||
|
.comment-app {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 20px auto;
|
||||||
|
font-family: -apple-system, sans-serif;
|
||||||
|
|
||||||
|
// 1. 输入框样式
|
||||||
|
.input-wrapper {
|
||||||
|
border: 1px solid $color-border;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
position: relative;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
background-color: $color-white;
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border-color: $color-blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-textarea__inner) {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-tools {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
|
.left-icons {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
.el-button {
|
||||||
|
color: $color-text-sub;
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
.el-button + .el-button {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn {
|
||||||
|
margin-left: 8px;
|
||||||
|
color: $color-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 评论列表样式
|
||||||
|
.comment-list {
|
||||||
|
margin-top: 30px;
|
||||||
|
|
||||||
|
.comment-group {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
|
||||||
|
.parent-node {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
background-color: $color-blue;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-main {
|
||||||
|
flex: 1;
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
.nickname {
|
||||||
|
font-weight: bold;
|
||||||
|
color: $color-text-main;
|
||||||
|
}
|
||||||
|
.time {
|
||||||
|
font-size: 13px;
|
||||||
|
color: $color-text-sub;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
margin: 8px 0;
|
||||||
|
font-size: 15px;
|
||||||
|
color: $color-text-main;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
.el-button {
|
||||||
|
font-size: 13px;
|
||||||
|
color: $color-text-sub;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
&:hover {
|
||||||
|
color: $color-blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.delete-btn:hover {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 子评论卡片样式
|
||||||
|
.sub-container {
|
||||||
|
background-color: $color-blue-bg;
|
||||||
|
border-radius: 8px;
|
||||||
|
border-left: 3px solid #d9e5ff;
|
||||||
|
padding: 16px;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.sub-node {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-main {
|
||||||
|
flex: 1;
|
||||||
|
.sub-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
.sub-user-info {
|
||||||
|
font-size: 14px;
|
||||||
|
.nickname {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.reply-text {
|
||||||
|
margin: 0 6px;
|
||||||
|
color: $color-text-sub;
|
||||||
|
}
|
||||||
|
.target-name {
|
||||||
|
color: $color-blue;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $color-text-sub;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.content-body {
|
||||||
|
font-size: 14px;
|
||||||
|
color: $color-text-main;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-publisher {
|
||||||
|
margin-top: 15px;
|
||||||
|
.inline-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 评论组件骨架屏
|
||||||
|
.skeleton-comment-parent-node {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
.skeleton-avatar {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-node-main {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.skeleton-user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-actions {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
src/modules/Comment/locales/en.ts
Normal file
7
src/modules/Comment/locales/en.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
comment: {
|
||||||
|
placeholder: 'Enter your comment',
|
||||||
|
send:'Send',
|
||||||
|
reply:'Reply'
|
||||||
|
},
|
||||||
|
}
|
||||||
7
src/modules/Comment/locales/zh.ts
Normal file
7
src/modules/Comment/locales/zh.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
comment: {
|
||||||
|
placeholder: '输入评论',
|
||||||
|
send:'发送',
|
||||||
|
reply:'回复'
|
||||||
|
},
|
||||||
|
}
|
||||||
18
src/modules/Comment/useEmoji.ts
Normal file
18
src/modules/Comment/useEmoji.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// useEmoji.ts
|
||||||
|
import emojiData from 'unicode-emoji-json'
|
||||||
|
|
||||||
|
export function useEmoji() {
|
||||||
|
const getEmotionList = (): string[] => {
|
||||||
|
return Object.keys(emojiData).filter(key => {
|
||||||
|
const item = emojiData[key];
|
||||||
|
return emojiData[key].group === 'Smileys & Emotion' && parseFloat(item.emoji_version) <= 12.0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预先生成好数据,避免组件每次渲染都执行过滤逻辑
|
||||||
|
const emotionList = getEmotionList()
|
||||||
|
|
||||||
|
return {
|
||||||
|
emotionList
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<span class="userinfo-username">{{ userInfo.username }}</span>
|
<span class="userinfo-username">{{ userInfo.username }}</span>
|
||||||
<span class="userinfo-role">SUPER ADMIN</span>
|
<span class="userinfo-role">SUPER ADMIN</span>
|
||||||
</div>
|
</div>
|
||||||
<el-avatar :size="30" :src="userInfo.avatar" />
|
<name-avatar :name="userInfo.nickname" :src="userInfo.avatar" :size="30" />
|
||||||
</div>
|
</div>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
@@ -50,6 +50,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Monitor, Bell } from "@element-plus/icons-vue";
|
import { Monitor, Bell } from "@element-plus/icons-vue";
|
||||||
import TokenManager from "@/utils/storage";
|
import TokenManager from "@/utils/storage";
|
||||||
|
import NameAvatar from "@/components/nameAvatar/index.vue";
|
||||||
import { useUserStore } from "@/store";
|
import { useUserStore } from "@/store";
|
||||||
defineOptions({ name: "RightMenuGroup" });
|
defineOptions({ name: "RightMenuGroup" });
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|||||||
99
src/pages/stage/dict/dictField.scss
Normal file
99
src/pages/stage/dict/dictField.scss
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
.mj-drawer-content {
|
||||||
|
.pro-table-container {
|
||||||
|
border-radius: 2px;
|
||||||
|
:deep(.pro-table-footer) {
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mj-drawer-top-container {
|
||||||
|
.top-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
padding: 16px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索框自定义 */
|
||||||
|
.custom-search-input {
|
||||||
|
--el-input-height: 30px;
|
||||||
|
&:deep(.el-input__wrapper) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* 新增字段按钮样式 */
|
||||||
|
.add-field-btn {
|
||||||
|
background: linear-gradient(to right, #2b65f6, #1e4edb);
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 4px 12px rgba(43, 101, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 筛选卡片内部样式 */
|
||||||
|
.filter-card {
|
||||||
|
padding: 10px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header .title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-link {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #2b65f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 统一输入框底色 */
|
||||||
|
.full-width-select :deep(.el-input__wrapper),
|
||||||
|
.full-width-date {
|
||||||
|
width: 100% !important;
|
||||||
|
background-color: #f5f7fa !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border: 1px solid #e4e7ed !important;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 应用筛选按钮 */
|
||||||
|
.apply-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 42px;
|
||||||
|
background-color: #2b65f6;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<el-drawer
|
<el-drawer
|
||||||
v-model="visible"
|
v-model="visible"
|
||||||
title="字段配置"
|
title="字段配置"
|
||||||
size="70%"
|
:size="size || '70%'"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
:close-on-press-escape="false"
|
:close-on-press-escape="false"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
</CommonFilter>
|
</CommonFilter>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right-actions">
|
<div class="right-actions" v-if="!hasChild">
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
class="add-field-btn"
|
class="add-field-btn"
|
||||||
@@ -95,7 +95,11 @@
|
|||||||
>
|
>
|
||||||
<!-- 名称点击 -->
|
<!-- 名称点击 -->
|
||||||
<template #labelName="{ row }">
|
<template #labelName="{ row }">
|
||||||
<el-button link type="primary" @click="onLevelNext">{{ row.label }}</el-button>
|
<el-button link type="primary" @click="onLevelNext(row)" v-if="!hasChild">{{
|
||||||
|
row.label
|
||||||
|
}}</el-button>
|
||||||
|
|
||||||
|
<span v-else>{{ row.label }}</span>
|
||||||
</template>
|
</template>
|
||||||
<!-- 状态插槽 -->
|
<!-- 状态插槽 -->
|
||||||
<template #status="{ row }">
|
<template #status="{ row }">
|
||||||
@@ -109,17 +113,16 @@
|
|||||||
{{ DictManage.statusDict[row.status] }}
|
{{ DictManage.statusDict[row.status] }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #actions="{ row }">
|
<!-- <template #actions="{ row }">
|
||||||
<el-button link type="primary" @click="handleAddNext(row)"
|
<el-button link type="primary" v-if="!hasChild" @click="handleAddNext(row)"
|
||||||
>添加二级字段</el-button
|
>添加二级字段</el-button>
|
||||||
>
|
|
||||||
<el-button link type="primary" @click="handleEdit(row)"
|
<el-button link type="primary" @click="handleEdit(row)"
|
||||||
>编辑</el-button
|
>编辑</el-button
|
||||||
>
|
>
|
||||||
<el-button link type="danger" @click="handleDelete(row)"
|
<el-button link type="danger" @click="handleDelete(row)"
|
||||||
>删除</el-button
|
>删除</el-button
|
||||||
>
|
>
|
||||||
</template>
|
</template> -->
|
||||||
</CommonTable>
|
</CommonTable>
|
||||||
<!-- 新增字段 -->
|
<!-- 新增字段 -->
|
||||||
<dictFieldLevelManage
|
<dictFieldLevelManage
|
||||||
@@ -129,6 +132,13 @@
|
|||||||
:parentId="parentId"
|
:parentId="parentId"
|
||||||
@confirm-success="onConfirmSuccess"
|
@confirm-success="onConfirmSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 当前弹层的组件-支持嵌套展开 -->
|
||||||
|
<dict-field-config
|
||||||
|
v-for="child in childModals"
|
||||||
|
:key="child.key"
|
||||||
|
:ref="(el) => setChildModalRef(el, child.key)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
@@ -146,14 +156,6 @@ import {
|
|||||||
disableTypeDict,
|
disableTypeDict,
|
||||||
} from "@/api/stage/dict";
|
} from "@/api/stage/dict";
|
||||||
defineOptions({ name: "DictFieldConfig" });
|
defineOptions({ name: "DictFieldConfig" });
|
||||||
interface User {
|
|
||||||
id: number;
|
|
||||||
date: string;
|
|
||||||
name: string;
|
|
||||||
address: string;
|
|
||||||
hasChildren?: boolean;
|
|
||||||
children?: User[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const dictTitle = ref("");
|
const dictTitle = ref("");
|
||||||
const addVisible = ref(false);
|
const addVisible = ref(false);
|
||||||
@@ -162,14 +164,18 @@ const searchQuery = ref("");
|
|||||||
const filterForm = reactive({
|
const filterForm = reactive({
|
||||||
status: "",
|
status: "",
|
||||||
});
|
});
|
||||||
|
const size = ref<string>(""); //抽屉大小
|
||||||
const tableRef = ref(null);
|
const tableRef = ref(null);
|
||||||
const visible = ref<boolean>(false);
|
const visible = ref<boolean>(false);
|
||||||
const parentId = ref<string>("");
|
const parentId = ref<string>("");
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const list = ref([]);
|
const list = ref([]);
|
||||||
|
const hasChild = ref<boolean>(false); //是否是子级弹窗
|
||||||
const columns = [
|
const childId = ref<string|number>(''); // 子集的id
|
||||||
|
const childModals = ref([]); //子弹窗的列表
|
||||||
|
const childModalRefs = ref({}); // 子弹窗的引用
|
||||||
|
const onCloseCallback = ref<Function | null>(null); // 关闭回调函数
|
||||||
|
const columns = computed(()=>[
|
||||||
{
|
{
|
||||||
prop: "id",
|
prop: "id",
|
||||||
label: "字典编码",
|
label: "字典编码",
|
||||||
@@ -201,28 +207,95 @@ const columns = [
|
|||||||
label: "更新时间",
|
label: "更新时间",
|
||||||
align: "center",
|
align: "center",
|
||||||
showOverflowTooltip: true,
|
showOverflowTooltip: true,
|
||||||
|
width:200,
|
||||||
formatter: (val) => {
|
formatter: (val) => {
|
||||||
return val.createTime
|
return val.updateTime
|
||||||
? dayjs(val.createTime).format("YYYY-MM-DD HH:mm")
|
? dayjs(val.updateTime).format("YYYY-MM-DD HH:mm")
|
||||||
: "-";
|
: "-";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ prop: "actions", label: "操作", align: "right", width: "300" },
|
{
|
||||||
];
|
prop: "actions",
|
||||||
|
label: "操作",
|
||||||
|
align: "right",
|
||||||
|
width: "300",
|
||||||
|
actions:[
|
||||||
|
{
|
||||||
|
label: "添加二级字段",
|
||||||
|
type: "primary",
|
||||||
|
link:true,
|
||||||
|
permission: ["edit"],
|
||||||
|
show:()=>{
|
||||||
|
return !hasChild.value
|
||||||
|
},
|
||||||
|
onClick: (row) => handleAddNext(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "编辑",
|
||||||
|
type: "primary",
|
||||||
|
link:true,
|
||||||
|
permission: ["edit"],
|
||||||
|
onClick: (row) => handleEdit(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "删除",
|
||||||
|
type: "danger",
|
||||||
|
link:true,
|
||||||
|
permission: ["delete"],
|
||||||
|
onClick: (row) => handleDelete(row),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
// 设置子弹窗引用
|
||||||
|
const setChildModalRef = (el, key) => {
|
||||||
|
if (el) {
|
||||||
|
childModalRefs.value[key] = el;
|
||||||
|
}
|
||||||
|
};
|
||||||
// 点击获取二级菜单数据
|
// 点击获取二级菜单数据
|
||||||
const onLevelNext = () =>{
|
const onLevelNext = (row) => {
|
||||||
console.log('next')
|
const childKey = `child-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
}
|
childModals.value.push({
|
||||||
|
key: childKey,
|
||||||
|
data: row,
|
||||||
|
hasChild: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
const childRef = childModalRefs.value[childKey];
|
||||||
|
if (childRef) {
|
||||||
|
childRef.open({
|
||||||
|
...row,
|
||||||
|
parentId:parentId.value,
|
||||||
|
hasChild: true,
|
||||||
|
onClose: () => removeChildModal(childKey)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 移除当前组件
|
||||||
|
const removeChildModal = (key: string) => {
|
||||||
|
const index = childModals.value.findIndex(child => child.key === key);
|
||||||
|
if (index !== -1) {
|
||||||
|
childModals.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
// 同时清理引用
|
||||||
|
delete childModalRefs.value[key];
|
||||||
|
};
|
||||||
|
|
||||||
// 请求数据信息
|
// 请求数据信息
|
||||||
const fetchData = async (params) => {
|
const fetchData = async (params) => {
|
||||||
try {
|
try {
|
||||||
const response = await getDictTypeValue(parentId.value, {
|
const queryParams = {
|
||||||
...params,
|
...params,
|
||||||
keyword: searchQuery.value,
|
keyword: searchQuery.value,
|
||||||
...filterForm,
|
...filterForm,
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const response = hasChild.value ? await getNextDictMenu(parentId.value,childId.value,queryParams) : await getDictTypeValue(parentId.value, queryParams);
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("getTableData Error", error);
|
console.log("getTableData Error", error);
|
||||||
@@ -242,25 +315,19 @@ const handleDictStatus = async (row) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME:tree懒加载数据
|
|
||||||
const load = async (
|
|
||||||
row: User,
|
|
||||||
treeNode: unknown,
|
|
||||||
resolve: (data: User[]) => void
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const resp = await getNextDictMenu(parentId.value, row.id);
|
|
||||||
console.log("获取当前返回的二级数据信息==>:", resp);
|
|
||||||
resolve([]);
|
|
||||||
} catch (error) {
|
|
||||||
resolve([]);
|
|
||||||
console.log("fetch tree error", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// 新增二级菜单数据
|
// 新增二级菜单数据
|
||||||
const addFields = () => {
|
const addFields = () => {
|
||||||
dictTitle.value = "新增字段";
|
dictTitle.value = "新增字段";
|
||||||
addVisible.value = true;
|
addVisible.value = true;
|
||||||
|
Object.assign(selectItem,{
|
||||||
|
id:null,
|
||||||
|
parentId:null,
|
||||||
|
label: "",
|
||||||
|
value: "",
|
||||||
|
sort: 0,
|
||||||
|
status:1,
|
||||||
|
remark:''
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// 确定刷新数据
|
// 确定刷新数据
|
||||||
@@ -283,6 +350,7 @@ const onReset = () => {
|
|||||||
const handleAddNext = async (item) => {
|
const handleAddNext = async (item) => {
|
||||||
addVisible.value = true;
|
addVisible.value = true;
|
||||||
dictTitle.value = "添加二级字段";
|
dictTitle.value = "添加二级字段";
|
||||||
|
Object.assign(selectItem,{},{parentId:item.id});
|
||||||
};
|
};
|
||||||
// 编辑当前字段
|
// 编辑当前字段
|
||||||
const handleEdit = (item) => {
|
const handleEdit = (item) => {
|
||||||
@@ -309,8 +377,17 @@ const handleDelete = async (item) => {
|
|||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
open: async (item) => {
|
open: async (item) => {
|
||||||
parentId.value = item.id;
|
parentId.value = item.parentId;
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
|
hasChild.value = item.hasChild ?? false;
|
||||||
|
// 处理子集的弹窗
|
||||||
|
if (hasChild.value) {
|
||||||
|
size.value = "60%";
|
||||||
|
childId.value = item.id;
|
||||||
|
if (item.onClose) {
|
||||||
|
onCloseCallback.value = item.onClose;
|
||||||
|
}
|
||||||
|
}
|
||||||
await nextTick();
|
await nextTick();
|
||||||
if (tableRef.value) {
|
if (tableRef.value) {
|
||||||
await tableRef.value.refresh();
|
await tableRef.value.refresh();
|
||||||
@@ -318,107 +395,13 @@ defineExpose({
|
|||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
|
if (onCloseCallback.value) {
|
||||||
|
onCloseCallback.value();
|
||||||
|
onCloseCallback.value = null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.mj-drawer-content {
|
@use "./dictField.scss" as *;
|
||||||
.pro-table-container {
|
|
||||||
border-radius: 2px;
|
|
||||||
:deep(.pro-table-footer) {
|
|
||||||
padding-top: 12px;
|
|
||||||
padding-bottom: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mj-drawer-top-container {
|
|
||||||
.top-toolbar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
background: #fff;
|
|
||||||
padding: 16px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 搜索框自定义 */
|
|
||||||
.custom-search-input {
|
|
||||||
--el-input-height: 30px;
|
|
||||||
&:deep(.el-input__wrapper) {
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: none;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* 新增字段按钮样式 */
|
|
||||||
.add-field-btn {
|
|
||||||
background: linear-gradient(to right, #2b65f6, #1e4edb);
|
|
||||||
border: none;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
box-shadow: 0 4px 12px rgba(43, 101, 246, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 筛选卡片内部样式 */
|
|
||||||
.filter-card {
|
|
||||||
padding: 10px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-header .title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 15px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reset-link {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #2b65f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-group label {
|
|
||||||
display: block;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #94a3b8;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 统一输入框底色 */
|
|
||||||
.full-width-select :deep(.el-input__wrapper),
|
|
||||||
.full-width-date {
|
|
||||||
width: 100% !important;
|
|
||||||
background-color: #f5f7fa !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
border: 1px solid #e4e7ed !important;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 应用筛选按钮 */
|
|
||||||
.apply-btn {
|
|
||||||
width: 100%;
|
|
||||||
height: 42px;
|
|
||||||
background-color: #2b65f6;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<el-radio :value="0">停用</el-radio>
|
<el-radio :value="0">停用</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备注:">
|
<el-form-item label="备注:" prop="remark">
|
||||||
<el-input
|
<el-input
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:autosize="{ minRows: 4, maxRows: 5 }"
|
:autosize="{ minRows: 4, maxRows: 5 }"
|
||||||
@@ -105,6 +105,7 @@ const rules = reactive({
|
|||||||
label: [{ required: true, message: "请输入字段名称", trigger: "blur" }],
|
label: [{ required: true, message: "请输入字段名称", trigger: "blur" }],
|
||||||
value: [{ required: true, message: "请输入字典值", trigger: "blur" }],
|
value: [{ required: true, message: "请输入字典值", trigger: "blur" }],
|
||||||
sort: [{ required: true, message: "请输入排序", trigger: "blur" }],
|
sort: [{ required: true, message: "请输入排序", trigger: "blur" }],
|
||||||
|
remark:[{ required: false, message: "请输入备注", trigger: "blur" }]
|
||||||
});
|
});
|
||||||
|
|
||||||
// 确定
|
// 确定
|
||||||
@@ -113,6 +114,8 @@ const onConfirm = async (formEl: FormInstance | undefined) => {
|
|||||||
await formEl.validate(async (valid, fields) => {
|
await formEl.validate(async (valid, fields) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
console.log("获取外部的数据信息:",form,parentId)
|
||||||
|
// 如果是一级新增字段就是parentId为0 如果是添加二级字段就是parentId为父级的id
|
||||||
try {
|
try {
|
||||||
const response = row.id ? await updateDictTypeValue(parentId,row.id,form) : await saveDictTypeValue(parentId,form);
|
const response = row.id ? await updateDictTypeValue(parentId,row.id,form) : await saveDictTypeValue(parentId,form);
|
||||||
ElMessage.success(row.id ? '修改成功' : '新增成功');
|
ElMessage.success(row.id ? '修改成功' : '新增成功');
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<span>:</span>
|
<span>:</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<el-input placeholder="请输入字典类型" v-model="form.key" :disabled="form.id"></el-input>
|
<el-input placeholder="请输入字典类型" v-model="form.key" :disabled="form.id ? true : false"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="状态:" prop="status">
|
<el-form-item label="状态:" prop="status">
|
||||||
<el-radio-group v-model="form.status">
|
<el-radio-group v-model="form.status">
|
||||||
|
|||||||
@@ -12,7 +12,11 @@
|
|||||||
<div class="mj-filter-content">
|
<div class="mj-filter-content">
|
||||||
<div class="filter-header">
|
<div class="filter-header">
|
||||||
<span class="title">条件筛选</span>
|
<span class="title">条件筛选</span>
|
||||||
<el-link type="primary" underline="never" class="reset-btn" @click="onReset"
|
<el-link
|
||||||
|
type="primary"
|
||||||
|
underline="never"
|
||||||
|
class="reset-btn"
|
||||||
|
@click="onReset"
|
||||||
>重置</el-link
|
>重置</el-link
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,7 +39,12 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-button type="primary" class="apply-btn" @click="fetchTableData">应用筛选</el-button>
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
class="apply-btn"
|
||||||
|
@click="fetchTableData"
|
||||||
|
>应用筛选</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</CommonFilter>
|
</CommonFilter>
|
||||||
<div class="search-dict-input">
|
<div class="search-dict-input">
|
||||||
@@ -84,7 +93,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions="{ row }">
|
<!-- <template #actions="{ row }">
|
||||||
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
|
<el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
|
||||||
<el-button link type="primary" @click="handlefieldsConfig(row)"
|
<el-button link type="primary" @click="handlefieldsConfig(row)"
|
||||||
>字段配置</el-button
|
>字段配置</el-button
|
||||||
@@ -92,7 +101,7 @@
|
|||||||
<el-button link type="danger" @click="handleDelete(row)"
|
<el-button link type="danger" @click="handleDelete(row)"
|
||||||
>删除</el-button
|
>删除</el-button
|
||||||
>
|
>
|
||||||
</template>
|
</template> -->
|
||||||
</CommonTable>
|
</CommonTable>
|
||||||
|
|
||||||
<!-- 新增-编辑字典弹窗 -->
|
<!-- 新增-编辑字典弹窗 -->
|
||||||
@@ -112,7 +121,12 @@ import CommonTable from "@/components/proTable/index.vue";
|
|||||||
import dictFieldConfig from "./dictFieldConfig.vue";
|
import dictFieldConfig from "./dictFieldConfig.vue";
|
||||||
import dictManage from "./dictManage.vue";
|
import dictManage from "./dictManage.vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { getDictValues, deleteDictValue,disableDict,enableDict } from "@/api/stage/dict";
|
import {
|
||||||
|
getDictValues,
|
||||||
|
deleteDictValue,
|
||||||
|
disableDict,
|
||||||
|
enableDict,
|
||||||
|
} from "@/api/stage/dict";
|
||||||
import { DictManage } from "@/dict";
|
import { DictManage } from "@/dict";
|
||||||
import { formatIndex } from "@/utils/utils";
|
import { formatIndex } from "@/utils/utils";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
@@ -164,33 +178,60 @@ const columns = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop:'updateByName',
|
prop: "updateByName",
|
||||||
label:'最后修改人',
|
label: "最后修改人",
|
||||||
align:'center'
|
align: "center",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "actions",
|
||||||
|
label: "操作",
|
||||||
|
align: "right",
|
||||||
|
width: "300",
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: "编辑",
|
||||||
|
type: "primary",
|
||||||
|
link:true,
|
||||||
|
permission: ["edit"],
|
||||||
|
onClick: (row) => handleEdit(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "字段配置",
|
||||||
|
type: "primary",
|
||||||
|
link:true,
|
||||||
|
permission: ["config"],
|
||||||
|
onClick: (row) => handlefieldsConfig(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "删除",
|
||||||
|
type: "danger",
|
||||||
|
link:true,
|
||||||
|
permission: ["delete"],
|
||||||
|
onClick: (row) => handleDelete(row),
|
||||||
|
}
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{ prop: "actions", label: "操作", align: "right", width: "200" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// 返回的data数据信息
|
// 返回的data数据信息
|
||||||
const dataValue = ref([]);
|
const dataValue = ref([]);
|
||||||
|
|
||||||
|
|
||||||
// popover关闭事件
|
// popover关闭事件
|
||||||
const onPopoverHide = () =>{
|
const onPopoverHide = () => {
|
||||||
filterForm.status = '';
|
filterForm.status = "";
|
||||||
}
|
};
|
||||||
// 筛选重置
|
// 筛选重置
|
||||||
const onReset = () =>{
|
const onReset = () => {
|
||||||
filterForm.status = '';
|
filterForm.status = "";
|
||||||
fetchTableData();
|
fetchTableData();
|
||||||
}
|
};
|
||||||
// 获取当前的table数据信息
|
// 获取当前的table数据信息
|
||||||
const getTableData = async (params) => {
|
const getTableData = async (params) => {
|
||||||
try {
|
try {
|
||||||
const response = await getDictValues({
|
const response = await getDictValues({
|
||||||
...params,
|
...params,
|
||||||
keyword: searchVal.value,
|
keyword: searchVal.value,
|
||||||
...filterForm
|
...filterForm,
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -201,26 +242,30 @@ const getTableData = async (params) => {
|
|||||||
const fetchTableData = () => {
|
const fetchTableData = () => {
|
||||||
dictTableRef.value && dictTableRef.value.refresh();
|
dictTableRef.value && dictTableRef.value.refresh();
|
||||||
};
|
};
|
||||||
|
const clearSelectItem = () => {
|
||||||
|
Object.assign(selectItem,{id:null,name:'',key:'',status:1,remark:''})
|
||||||
|
};
|
||||||
// 新增字典信息
|
// 新增字典信息
|
||||||
const addDict = () => {
|
const addDict = () => {
|
||||||
dictVisible.value = true;
|
dictVisible.value = true;
|
||||||
|
clearSelectItem();
|
||||||
};
|
};
|
||||||
// 编辑字典信息
|
// 编辑字典信息
|
||||||
const handleEdit = (item) => {
|
const handleEdit = (item) => {
|
||||||
addDict();
|
dictVisible.value = true;
|
||||||
Object.assign(selectItem, item);
|
Object.assign(selectItem, item);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 启用-禁用事件
|
// 启用-禁用事件
|
||||||
const handleDictStatus = async (row)=>{
|
const handleDictStatus = async (row) => {
|
||||||
try {
|
try {
|
||||||
row.status === 1 ? await disableDict(row.id) : await enableDict(row.id);
|
row.status === 1 ? await disableDict(row.id) : await enableDict(row.id);
|
||||||
ElMessage.success('操作成功');
|
ElMessage.success("操作成功");
|
||||||
onConfirmSuccess();
|
onConfirmSuccess();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error',error);
|
console.log("error", error);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// 刷新Table数据信息
|
// 刷新Table数据信息
|
||||||
const onConfirmSuccess = () => {
|
const onConfirmSuccess = () => {
|
||||||
@@ -228,7 +273,7 @@ const onConfirmSuccess = () => {
|
|||||||
};
|
};
|
||||||
// TODO:字段配置
|
// TODO:字段配置
|
||||||
const handlefieldsConfig = (ite) => {
|
const handlefieldsConfig = (ite) => {
|
||||||
fieldsConfigRef.value.open(ite);
|
fieldsConfigRef.value.open({ ...ite, parentId: ite.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除字典类型数据
|
// 删除字典类型数据
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Comment from "@/components/comment/index.vue";
|
import Comment from "@/modules/Comment/index.vue";
|
||||||
import { reactive, ref, onMounted } from "vue";
|
import { reactive, ref, onMounted } from "vue";
|
||||||
|
|
||||||
defineOptions({ name: "Personnel" });
|
defineOptions({ name: "Personnel" });
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const useUserStore = defineStore("user", {
|
|||||||
routes: [] as RouteMenu[],
|
routes: [] as RouteMenu[],
|
||||||
isRoutesLoaded: false, // 标记路由是否已加载
|
isRoutesLoaded: false, // 标记路由是否已加载
|
||||||
isBackendUser:true, //标记是否是后台用户
|
isBackendUser:true, //标记是否是后台用户
|
||||||
|
role:['edit','delete','config','add'] //当前的权限列表
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ body {
|
|||||||
|
|
||||||
// 筛选框全局样式内容
|
// 筛选框全局样式内容
|
||||||
.mj-filter-content {
|
.mj-filter-content {
|
||||||
width: 380px;
|
min-width: 380px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -111,14 +111,11 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.custom-select {
|
.custom-select {
|
||||||
|
--el-border-color:transparent;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #f5f7fa;
|
background-color: #f5f7fa;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border: 1px solid #e4e7ed;
|
border: 1px solid #e4e7ed;
|
||||||
:deep(.el-input__wrapper) {
|
|
||||||
background-color: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.apply-btn {
|
.apply-btn {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useUserStore } from "@/store";
|
|||||||
|
|
||||||
function permissionDirective(el: HTMLElement,binding:any) {
|
function permissionDirective(el: HTMLElement,binding:any) {
|
||||||
const appStore = useUserStore();
|
const appStore = useUserStore();
|
||||||
const userPermissions = appStore.role; // 假設從store中獲取用戶權限
|
const userPermissions = appStore.role;
|
||||||
let requiredPermissions = binding.value;
|
let requiredPermissions = binding.value;
|
||||||
if (typeof requiredPermissions === "string") {
|
if (typeof requiredPermissions === "string") {
|
||||||
requiredPermissions = [requiredPermissions];
|
requiredPermissions = [requiredPermissions];
|
||||||
|
|||||||
27
src/utils/permission.ts
Normal file
27
src/utils/permission.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// 封装权限公用方法
|
||||||
|
import { useUserStore } from "@/store";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用: import { usePermission } from "@/utils/permission";
|
||||||
|
* 示例: const { checkPermission } = usePermission();
|
||||||
|
* checkPermission('permission1') OR checkPermission(['permission1', 'permission2'])
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
export const usePermission = () => {
|
||||||
|
const appStore = useUserStore();
|
||||||
|
const checkPermission = (requiredPermissions: string | string[]):boolean => {
|
||||||
|
// 通过接口获取到的用户权限数据
|
||||||
|
const userPermissions = appStore.role;
|
||||||
|
const permissionArray = Array.isArray(requiredPermissions) ? requiredPermissions : [requiredPermissions];
|
||||||
|
|
||||||
|
const hasPermission = permissionArray.some((permission) =>
|
||||||
|
userPermissions.includes(permission)
|
||||||
|
);
|
||||||
|
|
||||||
|
return hasPermission;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { checkPermission };
|
||||||
|
};
|
||||||
|
|
||||||
Reference in New Issue
Block a user