mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2025-12-08 17:12:28 +08:00
【v3.8.3开源版本发布】
Merge remote-tracking branch 'origin/master' into springboot3 # Conflicts: # README.md # jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java # jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/FileDownloadUtils.java # jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/SysUserAgentController.java # jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/ISysDepartService.java # jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/ISysUserService.java # jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/SysAnnouncementServiceImpl.java # jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-sentinel/pom.xml # jeecg-boot/jeecg-server-cloud/jeecg-visual/jeecg-cloud-test/jeecg-cloud-test-seata/pom.xml # jeecg-boot/pom.xml
This commit is contained in:
@ -1,10 +1,10 @@
|
||||
JeecgBoot 企业级低代码开发平台
|
||||
===============
|
||||
当前最新版本: 3.8.2(预计发布时间:2025-08-04)
|
||||
当前最新版本: 3.8.3(预计发布时间:2025-09-22)
|
||||
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||
[](http://jeecg.com/aboutusIndex)
|
||||
[](https://github.com
|
||||
[](https://github.com
|
||||
/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot)
|
||||
|
||||
@ -32,6 +32,7 @@ export function wrapperEnv(envConf: Recordable): ViteEnv {
|
||||
try {
|
||||
realName = JSON.parse(realName.replace(/'/g, '"'));
|
||||
} catch (error) {
|
||||
console.log("PARSE VITE PROXY ERROR:", error);
|
||||
realName = '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jeecgboot-vue3",
|
||||
"version": "3.8.2",
|
||||
"version": "3.8.3",
|
||||
"author": {
|
||||
"name": "北京国炬信息技术有限公司",
|
||||
"email": "jeecgos@163.com",
|
||||
@ -107,7 +107,7 @@
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue": "5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "4.1.1",
|
||||
"@vue/compiler-sfc": "^3.5.13",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
|
||||
81
jeecgboot-vue3/pnpm-lock.yaml
generated
81
jeecgboot-vue3/pnpm-lock.yaml
generated
@ -25,13 +25,13 @@ importers:
|
||||
version: 3.8.2-beta
|
||||
'@logicflow/core':
|
||||
specifier: ^2.0.10
|
||||
version: 2.0.16
|
||||
version: 2.1.1
|
||||
'@logicflow/extension':
|
||||
specifier: ^2.0.14
|
||||
version: 2.0.21(@logicflow/core@2.0.16)
|
||||
version: 2.1.2(@logicflow/core@2.1.1)(@logicflow/vue-node-registry@1.1.1(@logicflow/core@2.1.1)(vue@3.5.13(typescript@4.9.5)))
|
||||
'@logicflow/vue-node-registry':
|
||||
specifier: ^1.0.12
|
||||
version: 1.0.18(@logicflow/core@2.0.16)(vue@3.5.13(typescript@4.9.5))
|
||||
version: 1.1.1(@logicflow/core@2.1.1)(vue@3.5.13(typescript@4.9.5))
|
||||
'@tinymce/tinymce-vue':
|
||||
specifier: 4.0.7
|
||||
version: 4.0.7(vue@3.5.13(typescript@4.9.5))
|
||||
@ -259,11 +259,11 @@ importers:
|
||||
specifier: ^6.21.0
|
||||
version: 6.21.0(eslint@8.57.1)(typescript@4.9.5)
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.3(vite@6.2.3(@types/node@20.17.28)(jiti@2.4.2)(less@4.2.2)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@4.9.5))
|
||||
specifier: 5.2.1
|
||||
version: 5.2.1(vite@6.2.3(@types/node@20.17.28)(jiti@2.4.2)(less@4.2.2)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@4.9.5))
|
||||
'@vitejs/plugin-vue-jsx':
|
||||
specifier: ^4.1.1
|
||||
version: 4.1.2(vite@6.2.3(@types/node@20.17.28)(jiti@2.4.2)(less@4.2.2)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@4.9.5))
|
||||
specifier: 4.1.1
|
||||
version: 4.1.1(vite@6.2.3(@types/node@20.17.28)(jiti@2.4.2)(less@4.2.2)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@4.9.5))
|
||||
'@vue/compiler-sfc':
|
||||
specifier: ^3.5.13
|
||||
version: 3.5.13
|
||||
@ -1153,18 +1153,19 @@ packages:
|
||||
'@keyv/serialize@1.0.3':
|
||||
resolution: {integrity: sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==}
|
||||
|
||||
'@logicflow/core@2.0.16':
|
||||
resolution: {integrity: sha512-KoNdY5g7WcAtfk7sMe+uOOso28mw6dwCHgLKmnzC0nenASD0HGWhFq+Yo7ktHP2asMXUISPb9hbQA221NcYZdg==}
|
||||
'@logicflow/core@2.1.1':
|
||||
resolution: {integrity: sha512-0NRq8BGTtH3whuRXKRGnspCawLFfq/z5Czu/bz/XshVbjnL3pm+IPoUg3JDCSY1YlObTih5r2/pqy3Dcm1/O+A==}
|
||||
|
||||
'@logicflow/extension@2.0.21':
|
||||
resolution: {integrity: sha512-SdYBOnDlCEOEElScGFIprgxqH0fv39ur7suyYzhiWUaWjL/TpvIESgqcR/ujE9aolFNTtzv2USc6xPcrouc4PQ==}
|
||||
'@logicflow/extension@2.1.2':
|
||||
resolution: {integrity: sha512-cj+xllwOzZfbWhd4+EZb3Q1lL3nWPRIxLtaQVzgkGaS59B2ZCH+HBiHYvnHQv9HtDHUujFgYLUQSfppt/Ezv4w==}
|
||||
peerDependencies:
|
||||
'@logicflow/core': 2.0.16
|
||||
'@logicflow/core': 2.1.1
|
||||
'@logicflow/vue-node-registry': 1.1.1
|
||||
|
||||
'@logicflow/vue-node-registry@1.0.18':
|
||||
resolution: {integrity: sha512-dqTojTpUowYVikE5gj3YQTday/aRYlNHAkX/PGObWWT2VyaymPdKclj5hxIGLGJhZlffBxjzWfxDzr6C7HOntQ==}
|
||||
'@logicflow/vue-node-registry@1.1.1':
|
||||
resolution: {integrity: sha512-/SFe87HPnzKypvb2JG6/zlreCgkho1Y8vJgRqEfPHrYCkIC5D8MJOSqVtTXJWgYR5CzYy0Y54pjjrc+38Z2Gpg==}
|
||||
peerDependencies:
|
||||
'@logicflow/core': 2.0.16
|
||||
'@logicflow/core': 2.1.1
|
||||
'@vue/composition-api': ^1.0.0-rc.10
|
||||
vue: ^2.0.0 || >=3.0.0
|
||||
peerDependenciesMeta:
|
||||
@ -1250,56 +1251,67 @@ packages:
|
||||
resolution: {integrity: sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.38.0':
|
||||
resolution: {integrity: sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.38.0':
|
||||
resolution: {integrity: sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.38.0':
|
||||
resolution: {integrity: sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loongarch64-gnu@4.38.0':
|
||||
resolution: {integrity: sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-powerpc64le-gnu@4.38.0':
|
||||
resolution: {integrity: sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.38.0':
|
||||
resolution: {integrity: sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.38.0':
|
||||
resolution: {integrity: sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.38.0':
|
||||
resolution: {integrity: sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.38.0':
|
||||
resolution: {integrity: sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.38.0':
|
||||
resolution: {integrity: sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.38.0':
|
||||
resolution: {integrity: sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==}
|
||||
@ -1657,15 +1669,15 @@ packages:
|
||||
'@vant/area-data@1.5.2':
|
||||
resolution: {integrity: sha512-Gtxgt6Rjgopt6234ANpO0bBsSwtjZ23lBlVDHIy8Mi2NJqyoj1vgVWY0dri8/2LCZAWzQ6EnwRrUVViUZ0cvMA==}
|
||||
|
||||
'@vitejs/plugin-vue-jsx@4.1.2':
|
||||
resolution: {integrity: sha512-4Rk0GdE0QCdsIkuMmWeg11gmM4x8UmTnZR/LWPm7QJ7+BsK4tq08udrN0isrrWqz5heFy9HLV/7bOLgFS8hUjA==}
|
||||
'@vitejs/plugin-vue-jsx@4.1.1':
|
||||
resolution: {integrity: sha512-uMJqv/7u1zz/9NbWAD3XdjaY20tKTf17XVfQ9zq4wY1BjsB/PjpJPMe2xiG39QpP4ZdhYNhm4Hvo66uJrykNLA==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
peerDependencies:
|
||||
vite: ^5.0.0 || ^6.0.0
|
||||
vue: ^3.0.0
|
||||
|
||||
'@vitejs/plugin-vue@5.2.3':
|
||||
resolution: {integrity: sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==}
|
||||
'@vitejs/plugin-vue@5.2.1':
|
||||
resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
peerDependencies:
|
||||
vite: ^5.0.0 || ^6.0.0
|
||||
@ -4976,8 +4988,8 @@ packages:
|
||||
resolution: {integrity: sha512-spBB5sgC4cv2YcW03f/IAUN1pgDJWNWD8FzkyY4mArLUMJW+KlQhlmUdKAHQuPfb00Jl5xIfImeOsf6YL8QK7Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
preact@10.27.0:
|
||||
resolution: {integrity: sha512-/DTYoB6mwwgPytiqQTh/7SFRL98ZdiD8Sk8zIUVOxtwq4oWcwrcd1uno9fE/zZmUaUrFNYzbH14CPebOz9tZQw==}
|
||||
preact@10.27.1:
|
||||
resolution: {integrity: sha512-V79raXEWch/rbqoNc7nT9E4ep7lu+mI3+sBmfRD4i1M73R3WLYcCtdI0ibxGVf4eQL8ZIz2nFacqEC+rmnOORQ==}
|
||||
|
||||
prelude-ls@1.2.1:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
@ -7179,32 +7191,33 @@ snapshots:
|
||||
dependencies:
|
||||
buffer: 6.0.3
|
||||
|
||||
'@logicflow/core@2.0.16':
|
||||
'@logicflow/core@2.1.1':
|
||||
dependencies:
|
||||
classnames: 2.5.1
|
||||
lodash-es: 4.17.21
|
||||
mobx: 5.15.7
|
||||
mobx-preact: 3.0.0(mobx@5.15.7)(preact@10.27.0)
|
||||
mobx-preact: 3.0.0(mobx@5.15.7)(preact@10.27.1)
|
||||
mobx-utils: 5.6.2(mobx@5.15.7)
|
||||
mousetrap: 1.6.5
|
||||
preact: 10.27.0
|
||||
preact: 10.27.1
|
||||
uuid: 9.0.1
|
||||
|
||||
'@logicflow/extension@2.0.21(@logicflow/core@2.0.16)':
|
||||
'@logicflow/extension@2.1.2(@logicflow/core@2.1.1)(@logicflow/vue-node-registry@1.1.1(@logicflow/core@2.1.1)(vue@3.5.13(typescript@4.9.5)))':
|
||||
dependencies:
|
||||
'@antv/hierarchy': 0.6.14
|
||||
'@logicflow/core': 2.0.16
|
||||
'@logicflow/core': 2.1.1
|
||||
'@logicflow/vue-node-registry': 1.1.1(@logicflow/core@2.1.1)(vue@3.5.13(typescript@4.9.5))
|
||||
classnames: 2.5.1
|
||||
lodash-es: 4.17.21
|
||||
medium-editor: 5.23.3
|
||||
mobx: 5.15.7
|
||||
preact: 10.27.0
|
||||
preact: 10.27.1
|
||||
rangy: 1.3.2
|
||||
vanilla-picker: 2.12.3
|
||||
|
||||
'@logicflow/vue-node-registry@1.0.18(@logicflow/core@2.0.16)(vue@3.5.13(typescript@4.9.5))':
|
||||
'@logicflow/vue-node-registry@1.1.1(@logicflow/core@2.1.1)(vue@3.5.13(typescript@4.9.5))':
|
||||
dependencies:
|
||||
'@logicflow/core': 2.0.16
|
||||
'@logicflow/core': 2.1.1
|
||||
lodash-es: 4.17.21
|
||||
vue: 3.5.13(typescript@4.9.5)
|
||||
vue-demi: 0.14.10(vue@3.5.13(typescript@4.9.5))
|
||||
@ -7781,7 +7794,7 @@ snapshots:
|
||||
|
||||
'@vant/area-data@1.5.2': {}
|
||||
|
||||
'@vitejs/plugin-vue-jsx@4.1.2(vite@6.2.3(@types/node@20.17.28)(jiti@2.4.2)(less@4.2.2)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@4.9.5))':
|
||||
'@vitejs/plugin-vue-jsx@4.1.1(vite@6.2.3(@types/node@20.17.28)(jiti@2.4.2)(less@4.2.2)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@4.9.5))':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.10
|
||||
'@babel/plugin-transform-typescript': 7.27.0(@babel/core@7.26.10)
|
||||
@ -7791,7 +7804,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitejs/plugin-vue@5.2.3(vite@6.2.3(@types/node@20.17.28)(jiti@2.4.2)(less@4.2.2)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@4.9.5))':
|
||||
'@vitejs/plugin-vue@5.2.1(vite@6.2.3(@types/node@20.17.28)(jiti@2.4.2)(less@4.2.2)(terser@5.39.0)(tsx@4.19.3))(vue@3.5.13(typescript@4.9.5))':
|
||||
dependencies:
|
||||
vite: 6.2.3(@types/node@20.17.28)(jiti@2.4.2)(less@4.2.2)(terser@5.39.0)(tsx@4.19.3)
|
||||
vue: 3.5.13(typescript@4.9.5)
|
||||
@ -11044,11 +11057,11 @@ snapshots:
|
||||
pkg-types: 1.3.1
|
||||
ufo: 1.5.4
|
||||
|
||||
mobx-preact@3.0.0(mobx@5.15.7)(preact@10.27.0):
|
||||
mobx-preact@3.0.0(mobx@5.15.7)(preact@10.27.1):
|
||||
dependencies:
|
||||
hoist-non-react-statics: 2.5.5
|
||||
mobx: 5.15.7
|
||||
preact: 10.27.0
|
||||
preact: 10.27.1
|
||||
|
||||
mobx-utils@5.6.2(mobx@5.15.7):
|
||||
dependencies:
|
||||
@ -11531,7 +11544,7 @@ snapshots:
|
||||
posthtml-parser: 0.2.1
|
||||
posthtml-render: 1.4.0
|
||||
|
||||
preact@10.27.0: {}
|
||||
preact@10.27.1: {}
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
|
||||
@ -16,6 +16,12 @@ enum Api {
|
||||
getCategoryData = '/sys/category/loadAllData',
|
||||
refreshDragCache = '/drag/page/refreshCache',
|
||||
refreshDefaultIndexCache = '/sys/sysRoleIndex/cleanDefaultIndexCache',
|
||||
//异步获取部门和岗位
|
||||
queryDepartAndPostTreeSync = '/sys/sysDepart/queryDepartAndPostTreeSync',
|
||||
//查询部门岗位下的用户
|
||||
queryDepartPostUserPageList = '/sys/user/queryDepartPostUserPageList',
|
||||
//查询所选部门的所有父节点ID
|
||||
queryAllParentId = '/sys/sysDepart/queryAllParentId',
|
||||
}
|
||||
|
||||
/**
|
||||
@ -53,6 +59,13 @@ export const getRoleList = (params) => {
|
||||
export const queryDepartTreeSync = (params?) => {
|
||||
return defHttp.get({ url: Api.queryDepartTreeSync, params });
|
||||
};
|
||||
/**
|
||||
* 异步获取部门职位树列表
|
||||
*/
|
||||
export const queryDepartAndPostTreeSync = (params?) => {
|
||||
return defHttp.get({ url: Api.queryDepartAndPostTreeSync, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取部门树列表
|
||||
*/
|
||||
@ -86,6 +99,21 @@ export const getDictItems = (dictCode) => {
|
||||
export const getTableList = (params) => {
|
||||
return defHttp.get({ url: Api.getTableList, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 部门岗位用户modal【查询部门岗位下的用户】
|
||||
*/
|
||||
export const queryDepartPostUserPageList = (params) => {
|
||||
return defHttp.get({ url: Api.queryDepartPostUserPageList, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询所选部门的所有父节点ID
|
||||
*/
|
||||
export const queryAllParentId = (params) => {
|
||||
return defHttp.get({ url: Api.queryAllParentId, params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 加载全部分类字典数据
|
||||
*/
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { getMenuListResultModel } from './model/menuModel';
|
||||
import { useUserStoreWithOut } from '@/store/modules/user';
|
||||
import { setAuthCache } from '@/utils/auth';
|
||||
import { TOKEN_KEY } from '@/enums/cacheEnum';
|
||||
import { router } from '@/router';
|
||||
import { PageEnum } from '@/enums/pageEnum';
|
||||
|
||||
enum Api {
|
||||
GetMenuList = '/sys/permission/getUserPermissionByToken',
|
||||
@ -28,7 +33,21 @@ export const getMenuList = () => {
|
||||
* @description: 获取后台菜单权限和按钮权限
|
||||
*/
|
||||
export function getBackMenuAndPerms() {
|
||||
return defHttp.get({ url: Api.GetMenuList });
|
||||
return defHttp.get({ url: Api.GetMenuList }).catch((e) => {
|
||||
// Token过期失效,直接跳转登录页面 2025-09-08 scott
|
||||
if (e && (e.message.includes('timeout') || e.message.includes('401'))) {
|
||||
const userStore = useUserStoreWithOut();
|
||||
userStore.setToken('');
|
||||
setAuthCache(TOKEN_KEY, null);
|
||||
router.push({
|
||||
path: PageEnum.BASE_LOGIN,
|
||||
query: {
|
||||
// 传入当前的路由,登录成功后跳转到当前路由
|
||||
redirect: router.currentRoute.value.fullPath,
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -81,13 +81,12 @@ export function phoneLoginApi(params: LoginParams, mode: ErrorMessageMode = 'mod
|
||||
export function getUserInfo() {
|
||||
return defHttp.get<GetUserInfoModel>({ url: Api.GetUserInfo }, {}).catch((e) => {
|
||||
// update-begin--author:zyf---date:20220425---for:【VUEN-76】捕获接口超时异常,跳转到登录界面
|
||||
// Token过期失效,直接跳转登录页面
|
||||
if (e && (e.message.includes('timeout') || e.message.includes('401'))) {
|
||||
//接口不通时跳转到登录界面
|
||||
const userStore = useUserStoreWithOut();
|
||||
userStore.setToken('');
|
||||
setAuthCache(TOKEN_KEY, null);
|
||||
|
||||
// update-begin-author:sunjianlei date:20230306 for: 修复登录成功后,没有正确重定向的问题
|
||||
router.push({
|
||||
path: PageEnum.BASE_LOGIN,
|
||||
query: {
|
||||
@ -95,8 +94,6 @@ export function getUserInfo() {
|
||||
redirect: router.currentRoute.value.fullPath,
|
||||
}
|
||||
});
|
||||
// update-end-author:sunjianlei date:20230306 for: 修复登录成功后,没有正确重定向的问题
|
||||
|
||||
}
|
||||
// update-end--author:zyf---date:20220425---for:【VUEN-76】捕获接口超时异常,跳转到登录界面
|
||||
});
|
||||
|
||||
BIN
jeecgboot-vue3/src/assets/icons/collaborationNotice.png
Normal file
BIN
jeecgboot-vue3/src/assets/icons/collaborationNotice.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
jeecgboot-vue3/src/assets/icons/superviseNotice.png
Normal file
BIN
jeecgboot-vue3/src/assets/icons/superviseNotice.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@ -14,6 +14,8 @@ export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
|
||||
export { default as JAreaLinkage } from './src/jeecg/components/JAreaLinkage.vue';
|
||||
export { default as JSelectUser } from './src/jeecg/components/JSelectUser.vue';
|
||||
export { default as JSelectDept } from './src/jeecg/components/JSelectDept.vue';
|
||||
export { default as JSelectDepartPost } from './src/jeecg/components/JSelectDepartPost.vue';
|
||||
export { default as JSelectUserByDeptPost } from './src/jeecg/components/JSelectUserByDeptPost.vue';
|
||||
export { default as JCodeEditor } from './src/jeecg/components/JCodeEditor.vue';
|
||||
export { default as JCategorySelect } from './src/jeecg/components/JCategorySelect.vue';
|
||||
export { default as JSelectMultiple } from './src/jeecg/components/JSelectMultiple.vue';
|
||||
|
||||
@ -44,6 +44,7 @@ import JSelectRole from './jeecg/components/JSelectRole.vue';
|
||||
import JImageUpload from './jeecg/components/JImageUpload.vue';
|
||||
import JDictSelectTag from './jeecg/components/JDictSelectTag.vue';
|
||||
import JSelectDept from './jeecg/components/JSelectDept.vue';
|
||||
import JSelectDepartPost from './jeecg/components/JSelectDepartPost.vue';
|
||||
import JAreaSelect from './jeecg/components/JAreaSelect.vue';
|
||||
import JEditor from './jeecg/components/JEditor.vue';
|
||||
// import JMarkdownEditor from './jeecg/components/JMarkdownEditor.vue';
|
||||
@ -77,6 +78,7 @@ import JRangeDate from './jeecg/components/JRangeDate.vue'
|
||||
import JRangeTime from './jeecg/components/JRangeTime.vue'
|
||||
import JInputSelect from './jeecg/components/JInputSelect.vue'
|
||||
import RoleSelectInput from './jeecg/components/roleSelect/RoleSelectInput.vue';
|
||||
import JSelectUserByDeptPost from './jeecg/components/JSelectUserByDeptPost.vue';
|
||||
import {DatePickerInFilter, CascaderPcaInFilter} from "@/components/InFilter";
|
||||
|
||||
const componentMap = new Map<ComponentType, Component>();
|
||||
@ -174,6 +176,8 @@ componentMap.set('RangeDate', JRangeDate);
|
||||
componentMap.set('RangeTime', JRangeTime);
|
||||
componentMap.set('RoleSelect', RoleSelectInput);
|
||||
componentMap.set('JInputSelect', JInputSelect);
|
||||
componentMap.set('JSelectDepartPost', JSelectDepartPost);
|
||||
componentMap.set('JSelectUserByDeptPost', JSelectUserByDeptPost);
|
||||
|
||||
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
v-bind="attrs_"
|
||||
v-model:value="state"
|
||||
:options="getOptions"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
@change="handleChange"
|
||||
@dropdownVisibleChange="handleFetch"
|
||||
@popupScroll="handlePopupScroll"
|
||||
@ -152,7 +154,14 @@
|
||||
watchEffect(() => {
|
||||
props.value && handleFetch();
|
||||
});
|
||||
|
||||
/**
|
||||
* 筛选流程
|
||||
* @param input
|
||||
* @param option
|
||||
*/
|
||||
const filterOption = (input: string, option: any) => {
|
||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0 || option.label.indexOf(input) >= 0;
|
||||
};
|
||||
async function fetch() {
|
||||
const api = props.api;
|
||||
if (!api || !isFunction(api)) return;
|
||||
@ -240,7 +249,7 @@
|
||||
}
|
||||
}
|
||||
// update-end--author:liusq---date:20250407---for:【QQYUN-11831】ApiSelect 分页下拉方案 #7883
|
||||
return { state, attrs_, attrs, getOptions, loading, t, handleFetch, handleChange, handlePopupScroll };
|
||||
return { state, attrs_, attrs, getOptions, loading, t, handleFetch, handleChange, handlePopupScroll,filterOption };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -492,7 +492,6 @@
|
||||
|
||||
const showSuffix = !!suffix;
|
||||
const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
name={field}
|
||||
|
||||
@ -0,0 +1,178 @@
|
||||
<!--部门选择组件-->
|
||||
<template>
|
||||
<div class="JSelectDepartPost">
|
||||
<JSelectBiz @change="handleSelectChange" @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs" :isCustomRenderTag="isCustomRenderTag" :rowKey="getBindValue?.rowKey"/>
|
||||
<a-form-item>
|
||||
<DeptSelectModal @register="regModal" @getSelectResult="setValue" modalTitle="部门岗位选择" v-bind="getBindValue" :multiple="multiple" @close="handleClose" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import DeptSelectModal from './modal/DeptSelectModal.vue';
|
||||
import JSelectBiz from './base/JSelectBiz.vue';
|
||||
import { defineComponent, ref, reactive, watchEffect, watch, provide, unref, toRaw } from 'vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { SelectValue } from 'ant-design-vue/es/select';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JSelectDepartPost',
|
||||
components: {
|
||||
DeptSelectModal,
|
||||
JSelectBiz,
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
|
||||
// 是否允许多选,默认 true
|
||||
multiple: propTypes.bool.def(true),
|
||||
//是否只选择岗位
|
||||
izOnlySelectDepartPost: propTypes.bool.def(true),
|
||||
// 自定义渲染tag
|
||||
isCustomRenderTag: propTypes.bool.def(true),
|
||||
},
|
||||
emits: ['options-change', 'change', 'select', 'update:value'],
|
||||
setup(props, { emit, refs }) {
|
||||
const emitData = ref<any[]>();
|
||||
//注册model
|
||||
const [regModal, { openModal }] = useModal();
|
||||
//下拉框选项值
|
||||
const selectOptions = ref<SelectValue>([]);
|
||||
//下拉框选中值
|
||||
let selectValues = reactive<Recordable>({
|
||||
value: [],
|
||||
});
|
||||
let tempSave: any = [];
|
||||
|
||||
// 是否正在加载回显数据
|
||||
const loadingEcho = ref<boolean>(false);
|
||||
//下发 selectOptions,xxxBiz组件接收
|
||||
provide('selectOptions', selectOptions);
|
||||
//下发 selectValues,xxxBiz组件接收
|
||||
provide('selectValues', selectValues);
|
||||
//下发 loadingEcho,xxxBiz组件接收
|
||||
provide('loadingEcho', loadingEcho);
|
||||
|
||||
const tag = ref(false);
|
||||
const attrs = useAttrs();
|
||||
|
||||
/**
|
||||
* 监听组件值
|
||||
*/
|
||||
watchEffect(() => {
|
||||
tempSave = [];
|
||||
props.value && initValue();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
initValue();
|
||||
}
|
||||
);
|
||||
|
||||
watch(selectOptions, () => {
|
||||
if (selectOptions) {
|
||||
emit('select', toRaw(unref(selectOptions)), toRaw(unref(selectValues)));
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 打卡弹出框
|
||||
*/
|
||||
function handleOpen() {
|
||||
tag.value = true;
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串值转化为数组
|
||||
*/
|
||||
function initValue() {
|
||||
let value = props.value ? props.value : [];
|
||||
if (value && typeof value === 'string') {
|
||||
selectValues.value = value.split(',');
|
||||
tempSave = value.split(',');
|
||||
} else {
|
||||
selectValues.value = value;
|
||||
tempSave = cloneDeep(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置下拉框的值
|
||||
*/
|
||||
function setValue(options, values) {
|
||||
selectOptions.value = options;
|
||||
selectValues.value = values;
|
||||
send(values);
|
||||
}
|
||||
const getBindValue = Object.assign({}, unref(props), unref(attrs));
|
||||
|
||||
const handleClose = () => {
|
||||
if (tempSave.length) {
|
||||
selectValues.value = cloneDeep(tempSave);
|
||||
} else {
|
||||
send(tempSave);
|
||||
}
|
||||
};
|
||||
const handleSelectChange = (values) => {
|
||||
tempSave = cloneDeep(values);
|
||||
send(tempSave);
|
||||
};
|
||||
const send = (values) => {
|
||||
let result = typeof props.value == 'string' ? values.join(',') : values;
|
||||
emit('update:value', result);
|
||||
emit('change', result);
|
||||
if (!values || values.length == 0) {
|
||||
emit('select', null, null);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
// state,
|
||||
attrs,
|
||||
selectOptions,
|
||||
selectValues,
|
||||
loadingEcho,
|
||||
getBindValue,
|
||||
tag,
|
||||
regModal,
|
||||
setValue,
|
||||
handleOpen,
|
||||
handleClose,
|
||||
handleSelectChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.JSelectDepartPost {
|
||||
> .ant-form-item {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.j-select-row {
|
||||
@width: 82px;
|
||||
|
||||
.left {
|
||||
width: calc(100% - @width - 8px);
|
||||
}
|
||||
|
||||
.right {
|
||||
width: @width;
|
||||
}
|
||||
|
||||
.full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-select-search__field) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,7 +1,7 @@
|
||||
<!--部门选择组件-->
|
||||
<template>
|
||||
<div class="JSelectDept">
|
||||
<JSelectBiz @change="handleSelectChange" @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs"/>
|
||||
<JSelectBiz @change="handleSelectChange" @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs" :isCustomRenderTag="isCustomRenderTag" :rowKey="getBindValue?.rowKey"/>
|
||||
<!-- update-begin--author:liaozhiyang---date:20240515---for:【QQYUN-9260】必填模式下会影响到弹窗内antd组件的样式 -->
|
||||
<a-form-item>
|
||||
<DeptSelectModal @register="regModal" @getSelectResult="setValue" v-bind="getBindValue" :multiple="multiple" @close="handleClose"/>
|
||||
@ -31,6 +31,8 @@
|
||||
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
|
||||
// 是否允许多选,默认 true
|
||||
multiple: propTypes.bool.def(true),
|
||||
// 自定义渲染tag
|
||||
isCustomRenderTag: propTypes.bool.def(true),
|
||||
},
|
||||
emits: ['options-change', 'change', 'select', 'update:value'],
|
||||
setup(props, { emit, refs }) {
|
||||
|
||||
@ -0,0 +1,158 @@
|
||||
<!--用户选择组件-->
|
||||
<template>
|
||||
<div>
|
||||
<JSelectBiz @change="handleChange" @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs"></JSelectBiz>
|
||||
<UserSelectByDepPostModal :rowKey="rowKey" @register="regModal" @getSelectResult="setValue" v-bind="getBindValue"></UserSelectByDepPostModal>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { unref } from 'vue';
|
||||
import UserSelectByDepPostModal from './modal/UserSelectByDepPostModal.vue';
|
||||
import JSelectBiz from './base/JSelectBiz.vue';
|
||||
import { defineComponent, ref, reactive, watchEffect, watch, provide } from 'vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { SelectValue } from 'ant-design-vue/es/select';
|
||||
export default defineComponent({
|
||||
name: 'JSelectUserByDeptPost',
|
||||
components: {
|
||||
UserSelectByDepPostModal,
|
||||
JSelectBiz,
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'username',
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'realname',
|
||||
},
|
||||
},
|
||||
emits: ['options-change', 'change', 'update:value'],
|
||||
setup(props, { emit, refs }) {
|
||||
const emitData = ref<any[]>();
|
||||
//注册model
|
||||
const [regModal, { openModal }] = useModal();
|
||||
//表单值
|
||||
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
||||
//下拉框选项值
|
||||
const selectOptions = ref<SelectValue>([]);
|
||||
//下拉框选中值
|
||||
let selectValues = reactive<object>({
|
||||
value: [],
|
||||
change: false,
|
||||
});
|
||||
// 是否正在加载回显数据
|
||||
const loadingEcho = ref<boolean>(false);
|
||||
//下发 selectOptions,xxxBiz组件接收
|
||||
provide('selectOptions', selectOptions);
|
||||
//下发 selectValues,xxxBiz组件接收
|
||||
provide('selectValues', selectValues);
|
||||
//下发 loadingEcho,xxxBiz组件接收
|
||||
provide('loadingEcho', loadingEcho);
|
||||
|
||||
const tag = ref(false);
|
||||
const attrs = useAttrs();
|
||||
|
||||
/**
|
||||
* 监听组件值
|
||||
*/
|
||||
watchEffect(() => {
|
||||
initValue();
|
||||
});
|
||||
|
||||
/**
|
||||
* 监听selectValues变化
|
||||
*/
|
||||
watch(selectValues, () => {
|
||||
if (selectValues) {
|
||||
// update-begin--author:liaozhiyang---date:20250616---for:【QQYUN-12869】通过部门选择用户组件,必填状态下选择用户后,点击重置后,会出校验信息
|
||||
if (props.value === undefined && selectValues.value?.length == 0) {
|
||||
return;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20250616---for:【QQYUN-12869】通过部门选择用户组件,必填状态下选择用户后,点击重置后,会出校验信息
|
||||
state.value = selectValues.value;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 打卡弹出框
|
||||
*/
|
||||
function handleOpen() {
|
||||
tag.value = true;
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字符串值转化为数组
|
||||
*/
|
||||
function initValue() {
|
||||
let value = props.value ? props.value : [];
|
||||
if (value && typeof value === 'string' && value != 'null' && value != 'undefined') {
|
||||
state.value = value.split(',');
|
||||
selectValues.value = value.split(',');
|
||||
} else {
|
||||
selectValues.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置下拉框的值
|
||||
*/
|
||||
function setValue(options, values) {
|
||||
selectOptions.value = options;
|
||||
//emitData.value = values.join(",");
|
||||
state.value = values;
|
||||
selectValues.value = values;
|
||||
emit('update:value', values);
|
||||
emit('options-change', options);
|
||||
}
|
||||
|
||||
function handleChange(values) {
|
||||
emit('update:value', values);
|
||||
}
|
||||
const getBindValue = Object.assign({}, unref(props), unref(attrs));
|
||||
return {
|
||||
state,
|
||||
attrs,
|
||||
selectOptions,
|
||||
getBindValue,
|
||||
selectValues,
|
||||
loadingEcho,
|
||||
tag,
|
||||
regModal,
|
||||
setValue,
|
||||
handleOpen,
|
||||
handleChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.j-select-row {
|
||||
@width: 82px;
|
||||
|
||||
.left {
|
||||
width: calc(100% - @width - 8px);
|
||||
}
|
||||
|
||||
.right {
|
||||
width: @width;
|
||||
}
|
||||
|
||||
.full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-select-search__field) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -15,6 +15,7 @@
|
||||
@change="onChange"
|
||||
@search="onSearch"
|
||||
:tree-checkable="treeCheckAble"
|
||||
:tagRender="tagRender"
|
||||
>
|
||||
<template #[name]="data" v-for="name in slotNamesGroup" :key="name">
|
||||
<slot :name="name" v-bind="data"></slot>
|
||||
@ -63,6 +64,10 @@
|
||||
treeCheckAble: propTypes.bool.def(false),
|
||||
//update-end---author:wangshuai date: 20230202 for: 新增是否有复选框
|
||||
hiddenNodeKey: propTypes.string.def(''),
|
||||
//update-begin---author:wangshuai---date:2025-09-06---for: 多选时渲染tag文本,为空不渲染,不支持单选---
|
||||
//多选时渲染tag文本
|
||||
tagRender: propTypes.func,
|
||||
//update-end---author:wangshuai---date:2025-09-06---for:多选时渲染tag文本,为空不渲染,不支持单选---
|
||||
});
|
||||
const attrs = useAttrs();
|
||||
const { t } = useI18n();
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg
|
||||
class="svg-company"
|
||||
v-if="orgCategory && (orgCategory === '1' || orgCategory === '4')"
|
||||
viewBox="0 0 1024 1024"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="256"
|
||||
>
|
||||
<path
|
||||
d="M234.9 226.9c0-25.9 25.1-46.9 56-46.9h226.9c30.5 0 55.3 21.1 55.3 46.9v580.9h40V340.2h132.5c25.9 0 46.9 21.1 46.9 46.9v413.5h40V387.2c0-47.9-39-86.9-86.9-86.9H613v-73.4c0-23.7-10.2-45.8-28.7-62.3-17.9-15.9-41.5-24.7-66.6-24.7H290.9c-52.9 0-96 39-96 86.9v580.9h40V226.9zM145.8 841.2h734.9v40H145.8z"
|
||||
fill="#333333"
|
||||
p-id="10104"
|
||||
></path>
|
||||
<path
|
||||
d="M286.9 354.1h124.7v40H286.9zM448.1 353.9h63.2v40h-63.2zM286.9 489.1h124.7v40H286.9zM448.1 488.9h63.2v40h-63.2zM286.9 616.6h124.7v40H286.9zM448.1 616.3h63.2v40h-63.2zM638.5 433.1h71v40h-71zM730.2 433h36v40h-36zM638.5 557.9h71v40h-71zM730.2 557.7h36v40h-36zM638.5 684.7h71v40h-71zM730.2 684.5h36v40h-36z"
|
||||
fill="#333333"
|
||||
></path>
|
||||
</svg>
|
||||
<svg class="svg-depart" v-if="orgCategory === '2'" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="256" height="256">
|
||||
<path
|
||||
d="M192 384H64V64h320v320H256v256h128v128h384V512h-128V192h320v320h-128v320H384v128H64v-320h128V384z m128 320H128v192h192v-192z m576-448h-192v192h192V256zM320 128H128v192h192V128z"
|
||||
p-id="8946"
|
||||
></path>
|
||||
</svg>
|
||||
<svg
|
||||
class="svg-post"
|
||||
v-if="orgCategory === '3'"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="11309"
|
||||
width="256"
|
||||
height="256"
|
||||
>
|
||||
<path
|
||||
d="M866.346667 89.6H207.573333C167.253333 89.6 134.4 122.453333 134.4 162.773333v146.346667h64V162.773333c0-5.12 4.266667-9.173333 9.173333-9.173333h658.773334c5.12 0 9.173333 4.053333 9.173333 9.173333v699.946667c0 5.12-4.053333 9.173333-9.173333 9.173333H207.573333c-5.12 0-9.173333-4.053333-9.173333-9.173333v-144h-64v144c0 40.32 32.853333 73.173333 73.173333 73.173333h658.773334c40.32 0 73.173333-32.853333 73.173333-73.173333V162.773333c0-40.32-32.853333-73.173333-73.173333-73.173333z"
|
||||
p-id="11310"
|
||||
></path>
|
||||
<path
|
||||
d="M134.4 428.16h64V597.333333h-64zM120.96 400.64h82.346667c17.706667 0 32-14.293333 32-32s-14.293333-32-32-32H120.96c-17.706667 0-32 14.293333-32 32s14.506667 32 32 32zM233.386667 657.493333c0-17.706667-14.293333-32-32-32H119.04c-17.706667 0-32 14.293333-32 32s14.293333 32 32 32h82.346667c17.706667 0 32-14.293333 32-32zM384.213333 655.146667l146.346667 139.306666c6.186667 5.973333 14.08 8.746667 21.973333 8.746667s15.786667-2.986667 21.973334-8.746667l148.053333-139.306666c8.746667-8.106667 12.16-20.48 8.746667-32l-64.853334-228.053334 39.68-122.666666c3.2-9.813333 1.493333-20.48-4.48-28.586667s-15.573333-13.226667-25.813333-13.226667H428.586667a31.872 31.872 0 0 0-30.293334 41.813334l40.96 126.933333-63.786666 224c-3.2 11.306667 0.213333 23.68 8.746666 31.786667z m168.533334 72.32l-110.506667-105.173334 56.96-199.893333h108.373333l56.96 199.893333-111.786666 105.173334z m78.933333-432.64l-19.413333 60.16h-120.32l-19.413334-60.16h159.146667z"
|
||||
p-id="11311"
|
||||
></path>
|
||||
</svg>
|
||||
<span :title="title">{{ title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps } from 'vue';
|
||||
const props = defineProps({
|
||||
orgCategory: String,
|
||||
title: String,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.svg-company {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
}
|
||||
.svg-depart,
|
||||
.svg-post {
|
||||
width: 14px;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
right: 2px;
|
||||
}
|
||||
html[data-theme='light'] {
|
||||
.svg-post{
|
||||
path { fill: #333333; }
|
||||
}
|
||||
.svg-company{
|
||||
path { fill: #333333; }
|
||||
}
|
||||
.svg-depart{
|
||||
path { fill: #333333; }
|
||||
}
|
||||
}
|
||||
html[data-theme='dark'] {
|
||||
.svg-post{
|
||||
path { fill: #ffffff; }
|
||||
}
|
||||
.svg-company{
|
||||
path { fill: #ffffff; }
|
||||
}
|
||||
.svg-depart{
|
||||
path { fill: #ffffff; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -25,7 +25,16 @@
|
||||
style="width: 100%"
|
||||
@click="!disabled && openModal(false)"
|
||||
v-bind="attrs"
|
||||
></a-select>
|
||||
>
|
||||
<template v-if="isCustomRenderTag" #tagRender="{ label, value, option}">
|
||||
<a-tag class="ant-select-selection-item">
|
||||
<span class="ant-select-selection-item-content" style="font-size: 14px;max-width: 300px" :title="tagRender(label, value, option)">{{ tagRender(label, value, option) }}</span>
|
||||
<span class="ant-select-selection-item-remove">
|
||||
<Icon icon="ant-design:close-outlined" size="12" @click="handleRemoveClick(value)"></Icon>
|
||||
</span>
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-select>
|
||||
</a-col>
|
||||
<a-col v-if="showButton" class="right">
|
||||
<a-button v-if="buttonIcon" :preIcon="buttonIcon" type="primary" @click="openModal(true)" :disabled="disabled">
|
||||
@ -43,6 +52,7 @@
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||
import { getDepartPathNameByOrgCode } from "@/utils/common/compUtils";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JSelectBiz',
|
||||
@ -69,6 +79,11 @@
|
||||
buttonIcon: propTypes.string.def(''),
|
||||
// 【TV360X-1002】是否是详情模式
|
||||
isDetailsMode: propTypes.bool.def(false),
|
||||
//update-begin---author:wangshuai---date:2025-09-06---for: 多选时是否自定义渲染tag文本,为空不渲染,不支持单选---
|
||||
//是否自定义渲染tag
|
||||
isCustomRenderTag: propTypes.bool.def(false),
|
||||
rowKey: propTypes.string.def('id'),
|
||||
//update-end---author:wangshuai---date:2025-09-06---for:多选时是否自定义渲染tag文本,为空不渲染,不支持单选---
|
||||
},
|
||||
emits: ['handleOpen', 'change'],
|
||||
setup(props, { emit, refs }) {
|
||||
@ -78,6 +93,10 @@
|
||||
const selectValues = inject('selectValues') || ref({});
|
||||
const attrs = useAttrs();
|
||||
const detailStr = ref('');
|
||||
|
||||
//存放部门名称
|
||||
const departNamePath = ref<Record<string, string>>({});
|
||||
|
||||
/**
|
||||
* 打开弹出框
|
||||
*/
|
||||
@ -98,7 +117,54 @@
|
||||
selectValues.change = true;
|
||||
emit('change', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多选tag自定义渲染
|
||||
*
|
||||
* @param label
|
||||
* @param value
|
||||
* @param isEllipsis 是否省略
|
||||
*/
|
||||
function tagRender(label, value, isEllipsis) {
|
||||
if (departNamePath.value[value]) {
|
||||
//是否需要省略显示
|
||||
if(!isEllipsis){
|
||||
return departNamePath.value[value];
|
||||
} else {
|
||||
let departName = departNamePath.value[value];
|
||||
//超过20则截取后20位的字符,前面加省略号
|
||||
if(departName && departName.length >= 20){
|
||||
const name:any = departName.substring(departName.length - 20);
|
||||
return '...' + name;
|
||||
} else {
|
||||
return departName;
|
||||
}
|
||||
}
|
||||
}
|
||||
//判断rowKey是否为orgCode
|
||||
if(props?.rowKey && props?.rowKey === 'orgCode'){
|
||||
getDepartPathNameByOrgCode(value, label, '').then((data) => {
|
||||
departNamePath.value[value] = data;
|
||||
});
|
||||
} else {
|
||||
//否则按照id处理
|
||||
getDepartPathNameByOrgCode('', label, value).then((data) => {
|
||||
departNamePath.value[value] = data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tag删除
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
function handleRemoveClick(value) {
|
||||
if(selectValues?.value){
|
||||
let values = selectValues?.value.filter(item => item !== value);
|
||||
handleChange(values);
|
||||
}
|
||||
}
|
||||
// -update-begin--author:liaozhiyang---date:20240617---for:【TV360X-1002】详情页面行编辑用户组件和部门组件显示方式优化
|
||||
watch(
|
||||
[selectValues, options],
|
||||
@ -121,6 +187,8 @@
|
||||
handleChange,
|
||||
openModal,
|
||||
detailStr,
|
||||
tagRender,
|
||||
handleRemoveClick,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<!--部门选择框-->
|
||||
<template>
|
||||
<div>
|
||||
<BasicModal v-bind="$attrs" @register="register" :title="modalTitle" width="500px" :maxHeight="maxHeight" @ok="handleOk" destroyOnClose @visible-change="visibleChange">
|
||||
<BasicModal v-bind="$attrs" @register="register" :title="modalTitle" width="600px" :minHeight="300" :maxHeight="maxHeight" @ok="handleOk" destroyOnClose @visible-change="visibleChange">
|
||||
<a-input-search v-if="izOnlySelectDepartPost" placeholder="按岗位名称搜索…" style="margin-bottom: 10px" @search="onSearch" @change="handelSearchChange"/>
|
||||
<BasicTree
|
||||
ref="treeRef"
|
||||
:treeData="treeData"
|
||||
@ -11,9 +12,15 @@
|
||||
@check="onCheck"
|
||||
:fieldNames="fieldNames"
|
||||
:checkedKeys="checkedKeys"
|
||||
:expandedKeys="expandedKeys"
|
||||
:multiple="multiple"
|
||||
:checkStrictly="getCheckStrictly"
|
||||
/>
|
||||
:key="reloadKey"
|
||||
>
|
||||
<template #title="{ orgCategory, title }">
|
||||
<TreeIcon :orgCategory="orgCategory" :title="title"></TreeIcon>
|
||||
</template>
|
||||
</BasicTree>
|
||||
<!--树操作部分-->
|
||||
<template #insertFooter>
|
||||
<a-dropdown placement="top">
|
||||
@ -34,17 +41,19 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { queryDepartTreeSync, queryTreeList } from '/@/api/common/api';
|
||||
import { queryDepartTreeSync, queryTreeList, queryDepartAndPostTreeSync } from '/@/api/common/api';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { treeProps } from '/@/components/Form/src/jeecg/props/props';
|
||||
import { BasicTree, TreeActionType } from '/@/components/Tree';
|
||||
import { useTreeBiz } from '/@/components/Form/src/jeecg/hooks/useTreeBiz';
|
||||
import {propTypes} from "/@/utils/propTypes";
|
||||
import { omit } from 'lodash-es';
|
||||
import TreeIcon from '@/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DeptSelectModal',
|
||||
components: {
|
||||
TreeIcon,
|
||||
BasicModal,
|
||||
BasicTree,
|
||||
},
|
||||
@ -61,17 +70,28 @@
|
||||
default: 500,
|
||||
},
|
||||
// update-end--author:liaozhiyang---date:20231220---for:【QQYUN-7678】部门组件内容过多没有滚动条(给一个默认最大高)
|
||||
value: propTypes.oneOfType([propTypes.string, propTypes.array])
|
||||
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
|
||||
//查询参数
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
emits: ['register', 'getSelectResult', 'close'],
|
||||
setup(props, { emit, refs }) {
|
||||
setup(props, { emit }) {
|
||||
//注册弹框
|
||||
const [register, { closeModal }] = useModalInner();
|
||||
const attrs = useAttrs();
|
||||
const treeRef = ref<Nullable<TreeActionType>>(null);
|
||||
//加载树key
|
||||
const reloadKey = ref<number>(Math.random());
|
||||
|
||||
//update-begin-author:taoyan date:2022-10-28 for: 部门选择警告类型不匹配
|
||||
let propValue = props.value === ''?[]:props.value;
|
||||
// 确保传递给BasicTree的value是数组格式
|
||||
if (propValue && typeof propValue === 'string') {
|
||||
propValue = propValue.split(',');
|
||||
}
|
||||
//update-begin-author:liusq date:2023-05-26 for: [issues/538]JSelectDept组件受 dynamicDisabled 影响
|
||||
let temp = Object.assign({}, unref(props), unref(attrs), {value: propValue},{disabled: false});
|
||||
const getBindValue = omit(temp, 'multiple');
|
||||
@ -79,9 +99,9 @@
|
||||
//update-end-author:taoyan date:2022-10-28 for: 部门选择警告类型不匹配
|
||||
|
||||
const queryUrl = getQueryUrl();
|
||||
const [{ visibleChange, checkedKeys, getCheckStrictly, getSelectTreeData, onCheck, onLoadData, treeData, checkALL, expandAll, onSelect }] =
|
||||
const [{ visibleChange, checkedKeys, getCheckStrictly, getSelectTreeData, onCheck, onLoadData, treeData, checkALL, expandAll, onSelect, onSearch, expandedKeys }] =
|
||||
useTreeBiz(treeRef, queryUrl, getBindValue, props, emit);
|
||||
const searchInfo = ref(props.params);
|
||||
const searchInfo = ref(props.params || {});
|
||||
const tree = ref([]);
|
||||
//替换treeNode中key字段为treeData中对应的字段
|
||||
const fieldNames = {
|
||||
@ -102,12 +122,21 @@
|
||||
|
||||
/** 获取查询数据方法 */
|
||||
function getQueryUrl() {
|
||||
let queryFn = props.sync ? queryDepartTreeSync : queryTreeList;
|
||||
let queryFn = props.izOnlySelectDepartPost ? queryDepartAndPostTreeSync :props.sync ? queryDepartTreeSync : queryTreeList;
|
||||
//update-begin-author:taoyan date:2022-7-4 for: issues/I5F3P4 online配置部门选择后编辑,查看数据应该显示部门名称,不是部门代码
|
||||
return (params) => queryFn(Object.assign({}, params, { primaryKey: props.rowKey }));
|
||||
//update-end-author:taoyan date:2022-7-4 for: issues/I5F3P4 online配置部门选择后编辑,查看数据应该显示部门名称,不是部门代码
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索值改变事件
|
||||
* @param value
|
||||
*/
|
||||
function handelSearchChange(value) {
|
||||
if(!value.target.value){
|
||||
reloadKey.value = Math.random();
|
||||
}
|
||||
}
|
||||
return {
|
||||
tree,
|
||||
handleOk,
|
||||
@ -120,12 +149,31 @@
|
||||
expandAll,
|
||||
fieldNames,
|
||||
checkedKeys,
|
||||
expandedKeys,
|
||||
register,
|
||||
getBindValue,
|
||||
getCheckStrictly,
|
||||
visibleChange,
|
||||
onLoadData,
|
||||
onSearch,
|
||||
reloadKey,
|
||||
handelSearchChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.svg-company {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
right: 2px;
|
||||
}
|
||||
.svg-depart,.svg-post {
|
||||
width: 14px;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
right: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -121,21 +121,14 @@
|
||||
};
|
||||
//定义表格列
|
||||
const columns = [
|
||||
{
|
||||
title: '职务编码',
|
||||
dataIndex: 'code',
|
||||
width: 180,
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: '职务名称',
|
||||
dataIndex: 'name',
|
||||
// width: 180,
|
||||
},
|
||||
{
|
||||
title: '职务等级',
|
||||
dataIndex: 'postRank_dictText',
|
||||
width: 180,
|
||||
title: '职务级别',
|
||||
dataIndex: 'postLevel',
|
||||
},
|
||||
];
|
||||
//已选择的table信息
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="register" :title="modalTitle" width="1200px" @ok="handleOk" destroyOnClose @visible-change="visibleChange">
|
||||
<a-row :gutter="10">
|
||||
<a-col :md="7" :sm="24">
|
||||
<a-col :md="7" :sm="24" style="height: 613px;overflow: auto ">
|
||||
<a-card :style="{ minHeight: '613px', overflow: 'auto' }">
|
||||
<!--组织机构-->
|
||||
<BasicTree
|
||||
@ -15,7 +15,11 @@
|
||||
:selectedKeys="selectedDepIds"
|
||||
:expandedKeys="expandedKeys"
|
||||
:clickRowToExpand="false"
|
||||
></BasicTree>
|
||||
>
|
||||
<template #title="{ orgCategory, title }">
|
||||
<TreeIcon :orgCategory="orgCategory" :title="title"></TreeIcon>
|
||||
</template>
|
||||
</BasicTree>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :md="17" :sm="24">
|
||||
@ -37,9 +41,11 @@
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { queryDepartTreeSync as queryDepartTreeSyncOrigin } from '/@/views/system/depart/depart.api';
|
||||
import { selectProps } from '/@/components/Form/src/jeecg/props/props';
|
||||
import TreeIcon from '@/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue';
|
||||
export default defineComponent({
|
||||
name: 'UserSelectByDepModal',
|
||||
components: {
|
||||
TreeIcon,
|
||||
//此处需要异步加载BasicTable
|
||||
BasicModal,
|
||||
BasicTree,
|
||||
@ -108,7 +114,7 @@
|
||||
md: 6,
|
||||
lg: 8,
|
||||
xl: 6,
|
||||
xxl: 10,
|
||||
xxl: 8,
|
||||
},
|
||||
//update-begin-author:liusq date:2023-10-30 for: [issues/5514]组件页面显示错位
|
||||
actionColOptions: {
|
||||
@ -126,6 +132,11 @@
|
||||
field: 'username',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
label: '姓名',
|
||||
field: 'realname',
|
||||
component: 'Input',
|
||||
},
|
||||
],
|
||||
resetFunc: customResetFunc,
|
||||
},
|
||||
|
||||
@ -0,0 +1,291 @@
|
||||
<!--通过部门选择用户-->
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="register" :title="modalTitle" width="1200px" @ok="handleOk" destroyOnClose @visible-change="visibleChange">
|
||||
<a-row :gutter="10">
|
||||
<a-col :md="7" :sm="24">
|
||||
<a-card :style="{ minHeight: '613px', overflow: 'auto' }">
|
||||
<a-input-search placeholder="按岗位名称搜索…" style="margin-bottom: 10px" @search="onSearch" @change="handelSearchChange"/>
|
||||
<!--组织机构-->
|
||||
<BasicTree
|
||||
ref="treeRef"
|
||||
:style="{ minWidth: '250px' }"
|
||||
selectable
|
||||
@select="onDepSelect"
|
||||
:load-data="loadChildrenTreeData"
|
||||
:treeData="departTree"
|
||||
:selectedKeys="selectedDepIds"
|
||||
:expandedKeys="expandedKeys"
|
||||
:clickRowToExpand="false"
|
||||
:key="reloadKey"
|
||||
>
|
||||
<template #title="{ orgCategory, title }">
|
||||
<TreeIcon :orgCategory="orgCategory" :title="title"></TreeIcon>
|
||||
</template>
|
||||
</BasicTree>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :md="17" :sm="24">
|
||||
<a-card :style="{ minHeight: '613px', overflow: 'auto' }">
|
||||
<!--用户列表-->
|
||||
<BasicTable ref="tableRef" v-bind="getBindValue" :searchInfo="searchInfo" :api="getTableList" :rowSelection="rowSelection"></BasicTable>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, unref, ref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicTree } from '/@/components/Tree/index';
|
||||
import { queryDepartPostUserPageList as getTableListOrigin} from '/@/api/common/api';
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
import { useSelectBiz } from '/@/components/Form/src/jeecg/hooks/useSelectBiz';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import TreeIcon from '/@/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue';
|
||||
import { queryDepartAndPostTreeSync as queryDepartTreeSyncOrigin } from '/@/views/system/depart/depart.api';
|
||||
import { selectProps } from '/@/components/Form/src/jeecg/props/props';
|
||||
import {defHttp} from "@/utils/http/axios";
|
||||
export default defineComponent({
|
||||
name: 'UserSelectByDepPostModal',
|
||||
components: {
|
||||
//此处需要异步加载BasicTable
|
||||
BasicModal,
|
||||
BasicTree,
|
||||
BasicTable: createAsyncComponent(() => import('/@/components/Table/src/BasicTable.vue'), {
|
||||
loading: true,
|
||||
}),
|
||||
TreeIcon,
|
||||
},
|
||||
props: {
|
||||
...selectProps,
|
||||
//选择框标题
|
||||
modalTitle: {
|
||||
type: String,
|
||||
default: '部门用户选择',
|
||||
},
|
||||
},
|
||||
emits: ['register', 'getSelectResult'],
|
||||
setup(props, { emit, refs }) {
|
||||
const tableRef = ref();
|
||||
const treeRef = ref();
|
||||
//注册弹框
|
||||
const [register, { closeModal }] = useModalInner(async (data) => {
|
||||
await queryDepartTree();
|
||||
});
|
||||
const attrs = useAttrs();
|
||||
const departTree = ref<any>([]);
|
||||
const selectedDepIds = ref([]);
|
||||
const expandedKeys = ref([]);
|
||||
const searchInfo = {};
|
||||
//树加载的key
|
||||
const reloadKey = ref(Math.random());
|
||||
|
||||
/**
|
||||
*表格配置
|
||||
*/
|
||||
const tableProps = {
|
||||
columns: [
|
||||
{
|
||||
title: '用户账号',
|
||||
dataIndex: 'username',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '用户姓名',
|
||||
dataIndex: 'realname',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'sex_dictText',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'phone',
|
||||
},
|
||||
],
|
||||
useSearchForm: true,
|
||||
canResize: false,
|
||||
showIndexColumn: false,
|
||||
striped: true,
|
||||
bordered: true,
|
||||
size: 'small',
|
||||
formConfig: {
|
||||
//labelWidth: 200,
|
||||
baseColProps: {
|
||||
xs: 24,
|
||||
sm: 8,
|
||||
md: 6,
|
||||
lg: 8,
|
||||
xl: 6,
|
||||
xxl: 10,
|
||||
},
|
||||
//update-begin-author:liusq date:2023-10-30 for: [issues/5514]组件页面显示错位
|
||||
actionColOptions: {
|
||||
xs: 24,
|
||||
sm: 12,
|
||||
md: 12,
|
||||
lg: 12,
|
||||
xl: 8,
|
||||
xxl: 8,
|
||||
},
|
||||
//update-end-author:liusq date:2023-10-30 for: [issues/5514]组件页面显示错位
|
||||
schemas: [
|
||||
{
|
||||
label: '账号',
|
||||
field: 'username',
|
||||
component: 'Input',
|
||||
},
|
||||
],
|
||||
resetFunc: customResetFunc,
|
||||
},
|
||||
};
|
||||
const getBindValue = Object.assign({}, unref(props), unref(attrs), tableProps);
|
||||
const [{ rowSelection, visibleChange, indexColumnProps, getSelectResult, reset }] = useSelectBiz(getTableList, getBindValue);
|
||||
|
||||
function getTableList(params) {
|
||||
params = parseParams(params);
|
||||
return getTableListOrigin({ ...params });
|
||||
}
|
||||
|
||||
function queryDepartTreeSync(params) {
|
||||
params = parseParams(params);
|
||||
return queryDepartTreeSyncOrigin({ ...params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析参数
|
||||
* @param params
|
||||
*/
|
||||
function parseParams(params) {
|
||||
if (props?.params) {
|
||||
return {
|
||||
...params,
|
||||
...props.params,
|
||||
};
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载树形数据
|
||||
*/
|
||||
function queryDepartTree() {
|
||||
queryDepartTreeSync({}).then((res) => {
|
||||
if (res) {
|
||||
departTree.value = res;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载子级部门
|
||||
*/
|
||||
async function loadChildrenTreeData(treeNode) {
|
||||
try {
|
||||
const result = await queryDepartTreeSync({
|
||||
pid: treeNode.eventKey,
|
||||
});
|
||||
const asyncTreeAction = unref(treeRef);
|
||||
if (asyncTreeAction) {
|
||||
asyncTreeAction.updateNodeByKey(treeNode.eventKey, { children: result });
|
||||
asyncTreeAction.setExpandedKeys([treeNode.eventKey, ...asyncTreeAction.getExpandedKeys()]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
/**
|
||||
* 点击树节点,筛选出对应的用户
|
||||
*/
|
||||
function onDepSelect(keys) {
|
||||
if (keys[0] != null) {
|
||||
if (unref(selectedDepIds)[0] !== keys[0]) {
|
||||
selectedDepIds.value = [keys[0]];
|
||||
}
|
||||
searchInfo['departId'] = unref(selectedDepIds).join(',');
|
||||
tableRef.value.reload();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 自定义重置方法
|
||||
* */
|
||||
async function customResetFunc() {
|
||||
console.log('自定义查询');
|
||||
//树节点清空
|
||||
selectedDepIds.value = [];
|
||||
//查询条件清空
|
||||
searchInfo['departId'] = '';
|
||||
//选择项清空
|
||||
reset();
|
||||
}
|
||||
/**
|
||||
* 确定选择
|
||||
*/
|
||||
function handleOk() {
|
||||
getSelectResult((options, values) => {
|
||||
//回传选项和已选择的值
|
||||
emit('getSelectResult', options, values);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 岗位搜索
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
async function onSearch(value) {
|
||||
if(value){
|
||||
let result = await defHttp.get({ url: "/sys/sysDepart/searchBy", params: { keyWord: value, orgCategory: "3",...props.params } });
|
||||
if (Array.isArray(result)) {
|
||||
departTree.value = result;
|
||||
} else {
|
||||
departTree.value = [];
|
||||
}
|
||||
} else {
|
||||
departTree.value = [];
|
||||
await queryDepartTree();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索值改变事件
|
||||
* @param value
|
||||
*/
|
||||
function handelSearchChange(value) {
|
||||
if(!value.target.value){
|
||||
reloadKey.value = Math.random();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
//config,
|
||||
handleOk,
|
||||
searchInfo,
|
||||
register,
|
||||
indexColumnProps,
|
||||
visibleChange,
|
||||
getBindValue,
|
||||
rowSelection,
|
||||
|
||||
departTree,
|
||||
selectedDepIds,
|
||||
expandedKeys,
|
||||
treeRef,
|
||||
tableRef,
|
||||
getTableList,
|
||||
onDepSelect,
|
||||
loadChildrenTreeData,
|
||||
onSearch,
|
||||
handelSearchChange,
|
||||
reloadKey,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@ -171,6 +171,7 @@
|
||||
component: (hasCustomApi.value && !props.customApiJInput) ? 'Input' : 'JInput',
|
||||
},
|
||||
],
|
||||
autoSubmitOnEnter: true
|
||||
};
|
||||
//定义表格列
|
||||
const columns = [
|
||||
|
||||
@ -3,6 +3,8 @@ import { inject, reactive, ref, computed, unref, watch, nextTick } from 'vue';
|
||||
import { TreeActionType } from '/@/components/Tree';
|
||||
import { listToTree } from '/@/utils/common/compUtils';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { defHttp } from "@/utils/http/axios";
|
||||
import { queryAllParentId } from "/@/api/common/api";
|
||||
|
||||
export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
||||
//接收下拉框选项
|
||||
@ -24,6 +26,10 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
||||
// 是否是首次加载回显,只有首次加载,才会显示 loading
|
||||
let isFirstLoadEcho = true;
|
||||
let prevSelectValues = [];
|
||||
// 需要展开的父节点ID列表
|
||||
const expandedKeys = ref<Array<string | number>>([]);
|
||||
// 是否启用自动展开功能(可以通过props控制)
|
||||
const enableAutoExpand = props.enableAutoExpand !== false;
|
||||
/**
|
||||
* 监听selectValues变化
|
||||
*/
|
||||
@ -55,11 +61,49 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
||||
function getTree() {
|
||||
const tree = unref(treeRef);
|
||||
if (!tree) {
|
||||
throw new Error('tree is null!');
|
||||
//throw new Error('tree is null!');
|
||||
return null;
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要展开的父节点ID
|
||||
*/
|
||||
async function getParentIdsToExpand(selectedIds) {
|
||||
if (!selectedIds || selectedIds.length === 0) return [];
|
||||
|
||||
try {
|
||||
const result = await queryAllParentId({
|
||||
departId: selectedIds.join(','),
|
||||
orgCode: props.params?.orgCode
|
||||
});
|
||||
|
||||
if (result) {
|
||||
const allParentIds = [];
|
||||
// 处理 Map 或 Object 结构
|
||||
const valuesToProcess = result instanceof Map
|
||||
? Array.from(result.values())
|
||||
: Object.values(result);
|
||||
|
||||
// 遍历所有选中节点的父节点
|
||||
valuesToProcess.forEach((nodeData: any) => {
|
||||
if (nodeData && nodeData.parentIds && Array.isArray(nodeData.parentIds)) {
|
||||
// 添加父节点ID(不包含选中节点本身)
|
||||
const parentIds = nodeData.parentIds.filter(id => !selectedIds.includes(id));
|
||||
allParentIds.push(...parentIds);
|
||||
}
|
||||
});
|
||||
|
||||
return [...new Set(allParentIds)]; // 去重
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.warn('获取父节点ID失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置树展开级别
|
||||
*/
|
||||
@ -70,7 +114,19 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
||||
}
|
||||
//设置列表默认选中
|
||||
checkedKeys.value = selectValues['value'];
|
||||
}).then();
|
||||
|
||||
// 如果有需要展开的父节点,则展开它们
|
||||
if (expandedKeys.value.length > 0) {
|
||||
getTree().setExpandedKeys(expandedKeys.value);
|
||||
}
|
||||
}).then(() => {
|
||||
// 再次确保展开,因为树可能还没有完全渲染
|
||||
if (expandedKeys.value.length > 0) {
|
||||
setTimeout(() => {
|
||||
getTree().setExpandedKeys(expandedKeys.value);
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,8 +227,20 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
||||
startPid = '';
|
||||
params['ids'] = ids;
|
||||
}
|
||||
|
||||
if(props.params?.departIds){
|
||||
params['departIds'] = props.params.departIds;
|
||||
}
|
||||
let record = await getList(params);
|
||||
let optionData = record;
|
||||
//只展示公司信息(公司+子公司)
|
||||
if(props.onlyShowCompany){
|
||||
record = getCompanyData(record)
|
||||
}
|
||||
//是否只选择部门岗位
|
||||
if (props.izOnlySelectDepartPost) {
|
||||
setCompanyDepartCheckable(record);
|
||||
}
|
||||
if (!props.serverTreeData) {
|
||||
//前端处理数据为tree结构
|
||||
record = listToTree(record, props, startPid);
|
||||
@ -212,6 +280,14 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到公司/子公司数据
|
||||
* @param record
|
||||
*/
|
||||
function getCompanyData(record){
|
||||
const companyData = record.filter(item=>item.orgCategory && ['1','4'].includes(item.orgCategory));
|
||||
return companyData
|
||||
}
|
||||
/**
|
||||
* 异步加载时检测是否含有下级节点
|
||||
* @param pid 父节点
|
||||
@ -256,6 +332,57 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
||||
//弹出框打开时加载全部数据
|
||||
openModal.value = true;
|
||||
await onLoadData(null, null);
|
||||
|
||||
// 在数据加载完成后,如果有选中的值且启用了自动展开功能,则展开父节点
|
||||
if (enableAutoExpand && selectValues.value && selectValues.value.length > 0) {
|
||||
try {
|
||||
const selectedIds = selectValues.value;
|
||||
const parentIds = await getParentIdsToExpand(selectedIds);
|
||||
|
||||
if (parentIds.length > 0) {
|
||||
expandedKeys.value = parentIds;
|
||||
|
||||
// 延迟展开,确保树已经渲染完成
|
||||
nextTick(() => {
|
||||
try {
|
||||
const tree = getTree();
|
||||
if (tree) {
|
||||
tree.setExpandedKeys(parentIds);
|
||||
|
||||
// 再次确保展开
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const tree = getTree();
|
||||
if (tree) {
|
||||
tree.setExpandedKeys(parentIds);
|
||||
console.log('父节点已展开:', parentIds);
|
||||
// 第三次确保展开,使用更长的延迟
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const tree = getTree();
|
||||
if (tree) {
|
||||
tree.setExpandedKeys(parentIds);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('展开父节点失败:', error);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('展开父节点失败:', error);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('展开父节点失败:', error);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('获取父节点ID失败:', error);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
openModal.value = false;
|
||||
// update-begin--author:liaozhiyang---date:20240527---for:【TV360X-414】部门设置了默认值,查询重置变成空了(同步JSelectUser组件改法)
|
||||
@ -264,6 +391,46 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置公司部门复选框显示
|
||||
* @param record
|
||||
*/
|
||||
function setCompanyDepartCheckable(record) {
|
||||
if (record && record.length > 0) {
|
||||
for (const item of record) {
|
||||
if (item.orgCategory !== '3') {
|
||||
item.checkable = false;
|
||||
item.selectable = false;
|
||||
} else {
|
||||
item.checkable = true;
|
||||
item.selectable = true;
|
||||
}
|
||||
if (item.isLeaf) {
|
||||
setCompanyDepartCheckable(item.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 岗位搜索
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
async function onSearch(value) {
|
||||
if(value){
|
||||
let result = await defHttp.get({ url: "/sys/sysDepart/searchBy", params: { keyWord: value, orgCategory: "3",...props.params } });
|
||||
if (Array.isArray(result)) {
|
||||
treeData.value = result;
|
||||
} else {
|
||||
treeData.value = [];
|
||||
}
|
||||
} else {
|
||||
treeData.value = [];
|
||||
await onLoadData(null, null)
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
visibleChange,
|
||||
@ -279,6 +446,8 @@ export function useTreeBiz(treeRef, getList, props, realProps, emit) {
|
||||
treeData,
|
||||
getCheckStrictly,
|
||||
getSelectTreeData,
|
||||
onSearch,
|
||||
expandedKeys,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ export const treeProps = {
|
||||
//初始展开的层级
|
||||
defaultExpandLevel: {
|
||||
type: [Number],
|
||||
default: 0,
|
||||
default: 1,
|
||||
},
|
||||
//根pid值
|
||||
startPid: {
|
||||
@ -78,10 +78,14 @@ export const treeProps = {
|
||||
sync: propTypes.bool.def(true),
|
||||
//是否显示选择按钮
|
||||
showButton: propTypes.bool.def(true),
|
||||
//是否只显示公司
|
||||
onlyShowCompany: propTypes.bool.def(false),
|
||||
//是否显示复选框
|
||||
checkable: propTypes.bool.def(true),
|
||||
//checkable 状态下节点选择完全受控(父子节点选中状态不再关联)
|
||||
checkStrictly: propTypes.bool.def(false),
|
||||
// 是否允许多选,默认 true
|
||||
multiple: propTypes.bool.def(true),
|
||||
// 是否只选择岗位
|
||||
izOnlySelectDepartPost: propTypes.bool.def(false),
|
||||
};
|
||||
|
||||
@ -121,6 +121,7 @@ export type ComponentType =
|
||||
| 'JImageUpload'
|
||||
| 'JDictSelectTag'
|
||||
| 'JSelectDept'
|
||||
| 'JSelectDepartPost'
|
||||
| 'JAreaSelect'
|
||||
| 'JEditor'
|
||||
| 'JMarkdownEditor'
|
||||
@ -139,6 +140,7 @@ export type ComponentType =
|
||||
| 'JTreeSelect'
|
||||
| 'JEllipsis'
|
||||
| 'JSelectUserByDept'
|
||||
| 'JSelectUserByDeptPost'
|
||||
| 'JSelectUserByDepartment'
|
||||
| 'JUpload'
|
||||
| 'JSearchSelect'
|
||||
|
||||
@ -124,8 +124,10 @@
|
||||
return;
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20250114---for:【issues/7706】顶部栏导航内部路由也可以支持采用新浏览器tab打开
|
||||
const findItem = getMatchingMenu(props.items, key);
|
||||
if (findItem?.internalOrExternal == true) {
|
||||
const menus = await getMenus();
|
||||
const findItem = getMatchingPath(menus, key);
|
||||
if (findItem?.internalOrExternal == true && !findItem?.children?.length) {
|
||||
// 一级菜单当设置了外部打开,只有没有子菜单时才生效
|
||||
window.open(location.origin + key);
|
||||
return;
|
||||
}
|
||||
@ -137,18 +139,32 @@
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240418---for:【QQYUN-8773】顶部混合导航(顶部左侧组合菜单)一级菜单没有配置redirect默认跳子菜单的第一个
|
||||
if (props.type === MenuTypeEnum.MIX) {
|
||||
const menus = await getMenus();
|
||||
const menuItem = getMatchingPath(menus, key);
|
||||
if (menuItem && !menuItem.redirect && menuItem.children?.length) {
|
||||
// 没有重定向且originComponent不是系统默认的就当做是组件,否则就跳子菜单的第一个
|
||||
if (menuItem && !menuItem.redirect && menuItem.originComponent == '/layouts/default/index' && menuItem.children?.length) {
|
||||
const subMenuItem = getSubMenu(menuItem.children);
|
||||
if (subMenuItem?.path) {
|
||||
const path = subMenuItem.redirect ?? subMenuItem.path;
|
||||
let _key = path;
|
||||
if (isUrl(path)) {
|
||||
window.open(path);
|
||||
// window.open(path);
|
||||
// 外部打开emit出去的key不能是url,否则左侧菜单出不来
|
||||
_key = key;
|
||||
}
|
||||
|
||||
// update-begin--author:liaozhiyang---date:20250825---for:【QQYUN-13593】敲敲云首页菜单外部打开
|
||||
// =====================================================================
|
||||
// TODO: 临时代码 - 需要删除!!!
|
||||
// 这是针对敲敲云首页菜单的特殊处理,后续需要重构或删除
|
||||
// =====================================================================
|
||||
// 是外部打开且是白名单内的菜单,则直接打开
|
||||
if (subMenuItem?.internalOrExternal == true && ['/myapps/index'].includes(path)) {
|
||||
window.open(location.origin + path);
|
||||
return;
|
||||
}
|
||||
// =====================================================================
|
||||
// update-end--author:liaozhiyang---date:20250825---for:【QQYUN-13593】敲敲云首页菜单外部打开
|
||||
|
||||
emit('menuClick', _key, { title: subMenuItem.title });
|
||||
} else {
|
||||
emit('menuClick', key, item);
|
||||
@ -189,12 +205,12 @@
|
||||
/**
|
||||
* liaozhiyang
|
||||
* 2024-05-18
|
||||
* 获取指定菜单下的第一个菜单
|
||||
* 获取指定菜单下的第一个菜单(忽略隐藏路由)
|
||||
*/
|
||||
function getSubMenu(menus) {
|
||||
for (let i = 0, len = menus.length; i < len; i++) {
|
||||
const item = menus[i];
|
||||
if (item.path && !item.children?.length) {
|
||||
if (item.path && !item.hideMenu && !item.children?.length) {
|
||||
return item;
|
||||
} else if (item.children?.length) {
|
||||
const result = getSubMenu(item.children);
|
||||
|
||||
@ -382,6 +382,9 @@
|
||||
getSize: () => {
|
||||
return unref(getBindValues).size as SizeType;
|
||||
},
|
||||
// update-begin--author:liaozhiyang---date:20250904---for:【QQYUN-13558】erp风格主表在5条数据时也有滚动条
|
||||
getBindValuesRef: () => getBindValues,
|
||||
// update-end--author:liaozhiyang---date:20250904---for:【QQYUN-13558】erp风格主表在5条数据时也有滚动条
|
||||
};
|
||||
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
||||
|
||||
|
||||
@ -7,12 +7,12 @@
|
||||
<template v-else>
|
||||
<Tooltip v-if="action.tooltip" v-bind="getTooltip(action.tooltip)">
|
||||
<PopConfirmButton v-bind="action">
|
||||
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" :color="action.iconColor"/>
|
||||
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" />
|
||||
<template v-if="action.label">{{ action.label }}</template>
|
||||
</PopConfirmButton>
|
||||
</Tooltip>
|
||||
<PopConfirmButton v-else v-bind="action">
|
||||
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" :color="action.iconColor"/>
|
||||
<Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" />
|
||||
<template v-if="action.label">{{ action.label }}</template>
|
||||
</PopConfirmButton>
|
||||
</template>
|
||||
|
||||
@ -121,6 +121,7 @@ export interface TableActionType {
|
||||
getShowPagination: () => boolean;
|
||||
setCacheColumnsByField?: (dataIndex: string | undefined, value: BasicColumn) => void;
|
||||
getColumnsRef: () => ComputedRef<BasicColumn[]>;
|
||||
getBindValuesRef: () => ComputedRef<any>;
|
||||
}
|
||||
|
||||
export interface FetchSetting {
|
||||
|
||||
@ -441,7 +441,7 @@
|
||||
const showTitle = title || toolbar || search || slots.headerTitle;
|
||||
const scrollStyle: CSSProperties = { height: 'calc(100% - 38px)' };
|
||||
return (
|
||||
<div class={[bem(), 'h-full', attrs.class]}>
|
||||
<div class={[bem(), 'h-full',unref(getBindValues).multiple === false ? 'custom-radio':'' , attrs.class]}>
|
||||
{showTitle && (
|
||||
<TreeHeader
|
||||
checkable={checkable}
|
||||
@ -475,3 +475,27 @@
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
// update-begin--author:liaozhiyang---date:20250908---for:【JHHB-192】主职务选择,多选框改成单选
|
||||
.custom-radio {
|
||||
:deep(.ant-tree) {
|
||||
.ant-tree-checkbox {
|
||||
.ant-tree-checkbox-inner {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 50%;
|
||||
&::after {
|
||||
width: 0;
|
||||
height: 0;
|
||||
left: 50%;
|
||||
border-width: 6px;
|
||||
border-radius: 50%;
|
||||
margin-left: -3px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20250908---for:【JHHB-192】主职务选择,多选框改成单选
|
||||
</style>
|
||||
|
||||
@ -129,7 +129,7 @@
|
||||
}
|
||||
|
||||
function getMyAvatar(){
|
||||
return userInfo.avatar;
|
||||
return getFileAccessHttpUrl(userInfo.avatar);
|
||||
}
|
||||
|
||||
// 获取头像
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
<upload-chunk ref="uploadRef" :visible="uploadVisible" @select="selectFirstFile"></upload-chunk>
|
||||
</div>
|
||||
<UserSelectModal rowKey="username" @register="registerModal" @selected="setValue" :multi="false"></UserSelectModal>
|
||||
<a-modal v-model:open="visibleEmoji" :footer="null" wrapClassName="emoji-modal" :closable="false" :width="490">
|
||||
<a-modal v-model:open="visibleEmoji" :footer="null" wrapClassName="emoji-modal" :closable="false" :width="460">
|
||||
<template #title>
|
||||
<span></span>
|
||||
</template>
|
||||
@ -235,7 +235,7 @@
|
||||
}
|
||||
|
||||
const pickerStyles = {
|
||||
width: '490px'
|
||||
width: '460px'
|
||||
/* height: '350px',
|
||||
top: '0px',
|
||||
left: '-75px',
|
||||
|
||||
@ -16,10 +16,7 @@ interface ListPageOptions {
|
||||
// 样式作用域范围
|
||||
designScope?: string;
|
||||
// 【必填】表格参数配置
|
||||
tableProps: TableProps & {
|
||||
// 添加 defSort 类型定义
|
||||
defSort?: DefSort;
|
||||
};
|
||||
tableProps: TableProps;
|
||||
// 是否分页
|
||||
pagination?: boolean;
|
||||
// 导出配置
|
||||
@ -49,11 +46,6 @@ interface IDoRequestOptions {
|
||||
clearSelection?: boolean;
|
||||
}
|
||||
|
||||
interface DefSort {
|
||||
column: string;
|
||||
order: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
/**
|
||||
* listPage页面公共方法
|
||||
*
|
||||
@ -93,17 +85,8 @@ export function useListPage(options: ListPageOptions) {
|
||||
//update-end-author:taoyan date:20220507 for: erp代码生成 子表 导出报错,原因未知-
|
||||
|
||||
//update-begin-author:liusq date:20230410 for:[/issues/409]导出功能没有按排序结果导出,设置导出默认排序,创建时间倒序
|
||||
// 获取表格的默认排序
|
||||
const { defSort } = options?.tableProps ?? {};
|
||||
if (defSort && !paramsForm?.column) {
|
||||
// 使用类型断言确保 defSort 类型正确
|
||||
Object.assign(paramsForm, {
|
||||
column: (defSort as DefSort).column,
|
||||
order: (defSort as DefSort).order,
|
||||
});
|
||||
} else if (!paramsForm?.column) {
|
||||
// 如果没有默认排序,则使用创建时间倒序
|
||||
Object.assign(paramsForm, { column: 'createTime', order: 'desc' });
|
||||
if(!paramsForm?.column){
|
||||
Object.assign(paramsForm,{column:'createTime',order:'desc'});
|
||||
}
|
||||
//update-begin-author:liusq date:20230410 for: [/issues/409]导出功能没有按排序结果导出,设置导出默认排序,创建时间倒序
|
||||
|
||||
|
||||
@ -22,12 +22,23 @@ export function useMethods() {
|
||||
*/
|
||||
async function exportXls(name, url, params, isXlsx = false) {
|
||||
//update-begin---author:wangshuai---date:2024-01-25---for:【QQYUN-8118】导出超时时间设置长点---
|
||||
const data = await defHttp.get({ url: url, params: params, responseType: 'blob', timeout: 60000 }, { isTransformResponse: false });
|
||||
// 修改为返回原生 response,便于获取 headers
|
||||
const response = await defHttp.get(
|
||||
{ url: url, params: params, responseType: 'blob', timeout: 60000 },
|
||||
{ isTransformResponse: false, isReturnNativeResponse: true }
|
||||
);
|
||||
//update-end---author:wangshuai---date:2024-01-25---for:【QQYUN-8118】导出超时时间设置长点---
|
||||
if (!data) {
|
||||
if (!response || !response.data) {
|
||||
createMessage.warning('文件下载失败');
|
||||
return;
|
||||
}
|
||||
// 判断 header 中 content-disposition 是否包含 .xlsx
|
||||
let isXlsxByHeader = isXlsx;
|
||||
const disposition = response.headers && response.headers['content-disposition'];
|
||||
if (disposition && disposition.indexOf('.xlsx') !== -1) {
|
||||
isXlsxByHeader = true;
|
||||
}
|
||||
const data = response.data;
|
||||
//update-begin---author:wangshuai---date:2024-04-18---for: 导出excel失败提示,不进行导出---
|
||||
let reader = new FileReader()
|
||||
reader.readAsText(data, 'utf-8')
|
||||
@ -40,16 +51,16 @@ export function useMethods() {
|
||||
if (!success) {
|
||||
createMessage.warning('导出失败,失败原因:' + message);
|
||||
} else {
|
||||
exportExcel(name, isXlsx, data);
|
||||
exportExcel(name, isXlsxByHeader, data);
|
||||
}
|
||||
return;
|
||||
} catch (error) {
|
||||
exportExcel(name, isXlsx, data);
|
||||
exportExcel(name, isXlsxByHeader, data);
|
||||
}
|
||||
// update-end---author:liaozhiyang---date:2025-02-11---for:【issues/7738】文件中带"success"导出报错 ---
|
||||
}
|
||||
}
|
||||
exportExcel(name, isXlsx, data);
|
||||
exportExcel(name, isXlsxByHeader, data);
|
||||
//update-end---author:wangshuai---date:2024-04-18---for: 导出excel失败提示,不进行导出---
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" width="600px">
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :title="title" @ok="handleSubmit" width="500px" :bodyStyle="{ padding: '20px 40px 20px 20px'}">
|
||||
<BasicForm @register="registerForm" />
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
// components
|
||||
import { Dropdown, Menu } from 'ant-design-vue';
|
||||
|
||||
import { defineComponent, computed, ref, nextTick } from 'vue';
|
||||
import { defineComponent, computed, ref } from 'vue';
|
||||
|
||||
import { SITE_URL } from '/@/settings/siteSetting';
|
||||
|
||||
|
||||
@ -2,12 +2,13 @@ import type { Menu } from '/@/router/types';
|
||||
import type { Ref } from 'vue';
|
||||
import { watch, unref, ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { MenuSplitTyeEnum } from '/@/enums/menuEnum';
|
||||
import { MenuSplitTyeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
|
||||
import { useThrottleFn } from '@vueuse/core';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { getChildrenMenus, getCurrentParentPath, getMenus, getShallowMenus } from '/@/router/menus';
|
||||
import { usePermissionStore } from '/@/store/modules/permission';
|
||||
import { useAppInject } from '/@/hooks/web/useAppInject';
|
||||
import { PAGE_NOT_FOUND_NAME_404 } from '/@/router/constant';
|
||||
|
||||
export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
|
||||
// Menu array
|
||||
@ -15,7 +16,7 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
|
||||
const { currentRoute } = useRouter();
|
||||
const { getIsMobile } = useAppInject();
|
||||
const permissionStore = usePermissionStore();
|
||||
const { setMenuSetting, getIsHorizontal, getSplit } = useMenuSetting();
|
||||
const { setMenuSetting, getIsHorizontal, getSplit, getMenuType } = useMenuSetting();
|
||||
|
||||
const throttleHandleSplitLeftMenu = useThrottleFn(handleSplitLeftMenu, 50);
|
||||
|
||||
@ -33,9 +34,22 @@ export function useSplitMenu(splitType: Ref<MenuSplitTyeEnum>) {
|
||||
[() => unref(currentRoute).path, () => unref(splitType)],
|
||||
async ([path]: [string, MenuSplitTyeEnum]) => {
|
||||
if (unref(splitNotLeft) || unref(getIsMobile)) return;
|
||||
|
||||
const { meta } = unref(currentRoute);
|
||||
const currentActiveMenu = meta.currentActiveMenu as string;
|
||||
// update-begin--author:liaozhiyang---date:20250908---for:【QQYUN-13718】一级菜单默认重定向到子菜单,但子菜单未授权,导致点击一级菜单加载不出子菜单
|
||||
// 顶部混合模式且顶部左侧组合菜单开始时
|
||||
if (unref(getMenuType) === MenuTypeEnum.MIX && unref(getSplit)) {
|
||||
// 404页面时,跳转到重定向的路径
|
||||
if (unref(currentRoute).name === PAGE_NOT_FOUND_NAME_404 && unref(currentRoute)?.redirectedFrom?.path) {
|
||||
const menus = await getMenus();
|
||||
const findItem = menus.find((item:any) => item.redirect === unref(currentRoute).path);
|
||||
if (findItem) {
|
||||
// 说明是从一级菜单重定向过来的
|
||||
path = findItem.path;
|
||||
}
|
||||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20250908---for:【QQYUN-13718】一级菜单默认重定向到子菜单,但子菜单未授权,导致点击一级菜单加载不出子菜单
|
||||
let parentPath = await getCurrentParentPath(path);
|
||||
if (!parentPath) {
|
||||
parentPath = await getCurrentParentPath(currentActiveMenu);
|
||||
|
||||
@ -36,13 +36,14 @@ export function layoutHandler(event: HandlerEnum, value: any) {
|
||||
baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value);
|
||||
} else if (isMixMenu) {
|
||||
baseHandler(event, value);
|
||||
baseHandler(HandlerEnum.HEADER_THEME, HEADER_PRESET_BG_COLOR_LIST[4]);
|
||||
baseHandler(HandlerEnum.HEADER_THEME, HEADER_PRESET_BG_COLOR_LIST[2]);
|
||||
baseHandler(HandlerEnum.MENU_THEME, SIDE_BAR_BG_COLOR_LIST[3]);
|
||||
if (darkMode) {
|
||||
updateHeaderBgColor();
|
||||
updateSidebarBgColor();
|
||||
}
|
||||
baseHandler(HandlerEnum.CHANGE_THEME_COLOR, APP_PRESET_COLOR_LIST[1]);
|
||||
// 顶部混合导航模式主题色改成绿色
|
||||
baseHandler(HandlerEnum.CHANGE_THEME_COLOR, APP_PRESET_COLOR_LIST[2]);
|
||||
baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value);
|
||||
} else if (isMixSidebarMenu) {
|
||||
baseHandler(event, value);
|
||||
@ -65,6 +66,13 @@ export function layoutHandler(event: HandlerEnum, value: any) {
|
||||
baseHandler(HandlerEnum.CHANGE_THEME_COLOR, APP_PRESET_COLOR_LIST[1]);
|
||||
baseHandler(HandlerEnum.TABS_THEME, tabsThemeOptions[1].value);
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20250825---for:【QQYUN-13600】默认顶部混合导航模式且启用顶部左侧导航,切换到其他模式时导航刷新后菜单样式混乱
|
||||
if (isMixMenu) {
|
||||
baseHandler(HandlerEnum.MENU_SPLIT, true);
|
||||
} else {
|
||||
baseHandler(HandlerEnum.MENU_SPLIT, false);
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20250825---for:【QQYUN-13600】默认顶部混合导航模式且启用顶部左侧导航,切换到其他模式时导航刷新后菜单样式混乱
|
||||
}
|
||||
|
||||
export function baseHandler(event: HandlerEnum, value: any) {
|
||||
|
||||
@ -14,10 +14,26 @@
|
||||
<!-- mode="out-in"-->
|
||||
<!-- appear-->
|
||||
<!-- >-->
|
||||
<keep-alive v-if="openCache" :include="getCaches">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
<component v-else :is="Component" :key="route.fullPath" />
|
||||
<template v-if="Component">
|
||||
<keep-alive v-if="openCache" :include="getCaches">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
<component v-else :is="Component" :key="route.fullPath" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- 【QQYUN-13593】空白页美化 -->
|
||||
<div class="animationEffect" :style="effectVars">
|
||||
<div class="effect-layer">
|
||||
<div class="blob blob-a"></div>
|
||||
<div class="blob blob-b"></div>
|
||||
<div class="blob blob-c"></div>
|
||||
</div>
|
||||
<div class="effect-grid"></div>
|
||||
<div class="effect-tip">
|
||||
<p>{{pageTip}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- </transition>-->
|
||||
</template>
|
||||
</RouterView>
|
||||
@ -36,6 +52,7 @@
|
||||
import { getTransitionName } from './transition';
|
||||
|
||||
import { useMultipleTabStore } from '/@/store/modules/multipleTab';
|
||||
import { useEmpty } from './useEmpty';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PageLayout',
|
||||
@ -56,7 +73,9 @@
|
||||
}
|
||||
return tabStore.getCachedTabList;
|
||||
});
|
||||
|
||||
// update-begin--author:liaozhiyang---date:20250826---for:【QQYUN-13593】空白页美化
|
||||
const { pageTip, getPageTip, effectVars } = useEmpty();
|
||||
// update-end--author:liaozhiyang---date:20250826---for:【QQYUN-13593】空白页美化
|
||||
return {
|
||||
getTransitionName,
|
||||
openCache,
|
||||
@ -64,7 +83,123 @@
|
||||
getBasicTransition,
|
||||
getCaches,
|
||||
getCanEmbedIFramePage,
|
||||
pageTip,
|
||||
getPageTip,
|
||||
effectVars,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
/** update-begin---author:liaozy ---date:2025-08-26 for:空白页美化样式 */
|
||||
.pageTip {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
font-size: 18px;
|
||||
color: #999;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.animationEffect {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 420px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(180deg, var(--bg-1) 0%, var(--bg-2) 100%);
|
||||
}
|
||||
|
||||
.effect-layer {
|
||||
position: absolute;
|
||||
top: -20%;
|
||||
left: -20%;
|
||||
right: -20%;
|
||||
bottom: -20%;
|
||||
filter: blur(30px);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.blob {
|
||||
position: absolute;
|
||||
width: 380px;
|
||||
height: 380px;
|
||||
border-radius: 50%;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.blob-a {
|
||||
background: radial-gradient(circle at 30% 30%, var(--blob-a-1) 0%, var(--blob-a-2) 60%, var(--blob-a-2) 100%);
|
||||
left: 5%;
|
||||
top: 10%;
|
||||
animation: float-a 18s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.blob-b {
|
||||
background: radial-gradient(circle at 30% 30%, var(--blob-b-1) 0%, var(--blob-b-2) 60%, var(--blob-b-2) 100%);
|
||||
right: 0;
|
||||
top: 30%;
|
||||
animation: float-b 22s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.blob-c {
|
||||
background: radial-gradient(circle at 30% 30%, var(--blob-c-1) 0%, var(--blob-c-2) 60%, var(--blob-c-2) 100%);
|
||||
left: 35%;
|
||||
bottom: -5%;
|
||||
animation: float-c 26s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes float-a {
|
||||
0% { transform: translate(0, 0) scale(1); }
|
||||
25% { transform: translate(20%, -10%) scale(1.05); }
|
||||
50% { transform: translate(35%, 5%) scale(0.95); }
|
||||
75% { transform: translate(10%, 15%) scale(1.02); }
|
||||
100% { transform: translate(0, 0) scale(1); }
|
||||
}
|
||||
|
||||
@keyframes float-b {
|
||||
0% { transform: translate(0, 0) scale(1); }
|
||||
25% { transform: translate(-15%, 10%) scale(1.08); }
|
||||
50% { transform: translate(-30%, -5%) scale(0.92); }
|
||||
75% { transform: translate(-10%, -15%) scale(1.03); }
|
||||
100% { transform: translate(0, 0) scale(1); }
|
||||
}
|
||||
|
||||
@keyframes float-c {
|
||||
0% { transform: translate(0, 0) scale(1); }
|
||||
25% { transform: translate(-10%, -10%) scale(0.9); }
|
||||
50% { transform: translate(10%, -25%) scale(1.05); }
|
||||
75% { transform: translate(20%, 0%) scale(0.98); }
|
||||
100% { transform: translate(0, 0) scale(1); }
|
||||
}
|
||||
|
||||
.effect-grid {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: linear-gradient(0deg, var(--grid-color) 1px, rgba(0, 0, 0, 0) 1px),
|
||||
linear-gradient(90deg, var(--grid-color) 1px, rgba(0, 0, 0, 0) 1px);
|
||||
background-size: 36px 36px, 36px 36px;
|
||||
mask-image: radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0) 70%);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.effect-tip {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 8px 14px;
|
||||
color: var(--tip-color);
|
||||
font-size: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
/** update-end---author:liaozy ---date:2025-08-26 for:空白页美化样式 */
|
||||
</style>
|
||||
|
||||
87
jeecgboot-vue3/src/layouts/page/useEmpty.ts
Normal file
87
jeecgboot-vue3/src/layouts/page/useEmpty.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { computed, unref, ref, watch } from 'vue';
|
||||
import { getMenus } from '/@/router/menus';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
|
||||
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
|
||||
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
|
||||
import { lighten, darken } from '/@/utils/color';
|
||||
export const useEmpty = () => {
|
||||
const { getThemeColor, getDarkMode } = useRootSetting();
|
||||
const route = useRoute();
|
||||
const { getHeaderBgColor } = useHeaderSetting();
|
||||
const { getMenuBgColor } = useMenuSetting();
|
||||
const pageTip = ref('');
|
||||
const effectVars = computed(() => {
|
||||
const primary = unref(getThemeColor) || '#1890ff';
|
||||
const menuBg = unref(getMenuBgColor) || '#ffffff';
|
||||
const headerBg = unref(getHeaderBgColor);
|
||||
const isDark = unref(getDarkMode) === 'dark';
|
||||
// 以主题色为基色,派生三组渐变色
|
||||
const a1 = lighten(primary, 25);
|
||||
const a2 = primary;
|
||||
const b1 = lighten(headerBg, 45);
|
||||
const b2 = lighten(headerBg, 10);
|
||||
const c1 = lighten(menuBg, 35);
|
||||
const c2 = darken(primary, 5);
|
||||
const bg1 = isDark ? '#0f172a' : '#f7f8fa';
|
||||
const bg2 = isDark ? '#111827' : '#f2f5f9';
|
||||
const grid = isDark ? 'rgba(255,255,255,0.04)' : 'rgba(60,70,90,0.06)';
|
||||
const tipColor = isDark ? '#626262' : '#b9b9b9';
|
||||
const tipBg = isDark ? 'rgba(17,24,39,0.6)' : 'rgba(255,255,255,0.6)';
|
||||
const tipBorder = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)';
|
||||
return {
|
||||
'--blob-a-1': a1,
|
||||
'--blob-a-2': a2,
|
||||
'--blob-b-1': b1,
|
||||
'--blob-b-2': b2,
|
||||
'--blob-c-1': c1,
|
||||
'--blob-c-2': c2,
|
||||
'--bg-1': bg1,
|
||||
'--bg-2': bg2,
|
||||
'--grid-color': grid,
|
||||
'--tip-color': tipColor,
|
||||
'--tip-bg': tipBg,
|
||||
'--tip-border': tipBorder,
|
||||
} as Record<string, string>;
|
||||
});
|
||||
|
||||
const getPageTip = async (route) => {
|
||||
const menus = await getMenus();
|
||||
const menu = getMatchingPath(menus, route.path);
|
||||
if (menu) {
|
||||
if (['/layouts/default/index'].includes(menu.originComponent)) {
|
||||
pageTip.value = '点击子菜单跳转到对应外部链接!';
|
||||
} else {
|
||||
pageTip.value = '查看组件引用是否正确';
|
||||
}
|
||||
}
|
||||
};
|
||||
watch(
|
||||
route,
|
||||
() => {
|
||||
getPageTip({ path: window.location.pathname });
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function getMatchingPath(menus, path) {
|
||||
for (let i = 0, len = menus.length; i < len; i++) {
|
||||
const item = menus[i];
|
||||
if (item.path === path) {
|
||||
return item;
|
||||
} else if (item.children?.length) {
|
||||
const result = getMatchingPath(item.children, path);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
pageTip,
|
||||
getPageTip,
|
||||
effectVars,
|
||||
};
|
||||
};
|
||||
@ -98,6 +98,7 @@ export function transformRouteToMenu(routeModList: AppRouteModule[], routerMappi
|
||||
hideMenu,
|
||||
alwaysShow:node.alwaysShow||false,
|
||||
path: node.path,
|
||||
originComponent: node.originComponent,
|
||||
...(node.redirect ? { redirect: node.redirect } : {}),
|
||||
};
|
||||
},
|
||||
|
||||
@ -134,6 +134,7 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul
|
||||
routeList.forEach((route) => {
|
||||
const component = route.component as string;
|
||||
if (component) {
|
||||
route.originComponent = component;
|
||||
if (component.toUpperCase() === 'LAYOUT') {
|
||||
route.component = LayoutMap.get(component.toUpperCase());
|
||||
} else {
|
||||
|
||||
@ -5,6 +5,7 @@ import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic';
|
||||
import { mainOutRoutes } from './mainOut';
|
||||
import { PageEnum } from '/@/enums/pageEnum';
|
||||
import { t } from '/@/hooks/web/useI18n';
|
||||
import { LAYOUT } from '/@/router/constant';
|
||||
|
||||
const modules = import.meta.glob('./modules/**/*.ts', { eager: true });
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
||||
name: string;
|
||||
meta: RouteMeta;
|
||||
component?: Component | string;
|
||||
originComponent?: string;
|
||||
components?: Component;
|
||||
children?: AppRouteRecordRaw[];
|
||||
props?: Recordable;
|
||||
|
||||
@ -3,7 +3,7 @@ import { merge, random } from 'lodash-es';
|
||||
import { isArray } from '/@/utils/is';
|
||||
import { FormSchema } from '/@/components/Form';
|
||||
import { reactive } from "vue";
|
||||
import { getTenantId, getToken } from "/@/utils/auth";
|
||||
import { getTenantId, getToken, getAuthCache, setAuthCache } from "/@/utils/auth";
|
||||
import { useUserStoreWithOut } from "/@/store/modules/user";
|
||||
import dayjs from 'dayjs';
|
||||
import Big from 'big.js';
|
||||
@ -609,3 +609,28 @@ export function freezeDeep(obj: Recordable | Recordable[]) {
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取父级名称
|
||||
*
|
||||
* @param orgCode 当前部门的code
|
||||
* @param label 当前默认显示的值
|
||||
* @param depId depId
|
||||
* @return 部门名称
|
||||
*/
|
||||
export async function getDepartPathNameByOrgCode(orgCode, label, depId){
|
||||
let key:any = "DEPARTNAME" + depId + orgCode;
|
||||
let authCache = getAuthCache(key);
|
||||
if (authCache) {
|
||||
return authCache;
|
||||
}
|
||||
if (orgCode) {
|
||||
depId = "";
|
||||
}
|
||||
let result = await defHttp.get({ url: "/sys/sysDepart/getDepartPathNameByOrgCode", params:{ orgCode: orgCode, depId: depId } }, { isTransformResponse: false });
|
||||
if (result.success) {
|
||||
setAuthCache(key,result.result);
|
||||
return result.result;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ export default class signMd5Utils {
|
||||
//update-end---author:wangshuai---date:2024-04-16---for:【QQYUN-9005】发送短信加签---
|
||||
let requestBody = this.sortAsc(jsonObj);
|
||||
delete requestBody._t;
|
||||
console.log('sign requestBody:', requestBody);
|
||||
// console.log('sign requestBody:', requestBody);
|
||||
return md5(JSON.stringify(requestBody) + signatureSecret).toUpperCase();
|
||||
}
|
||||
|
||||
|
||||
@ -219,6 +219,22 @@ export const schemas: FormSchema[] = [
|
||||
label: '选中部门',
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
field: 'depart4',
|
||||
component: 'JSelectDepartPost',
|
||||
label: '选择岗位',
|
||||
helpMessage: ['component模式'],
|
||||
componentProps: { showButton: false },
|
||||
colProps: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'depart4',
|
||||
component: 'JEllipsis',
|
||||
label: '选择岗位',
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
field: 'user2',
|
||||
component: 'JSelectUser',
|
||||
@ -258,6 +274,25 @@ export const schemas: FormSchema[] = [
|
||||
label: '选中用户',
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
field: 'userPost1',
|
||||
component: 'JSelectUserByDeptPost',
|
||||
label: '部门岗位选择用户',
|
||||
helpMessage: ['component模式'],
|
||||
componentProps: {
|
||||
labelKey: 'realname',
|
||||
rowKey: 'username',
|
||||
},
|
||||
colProps: {
|
||||
span: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'userPost1',
|
||||
component: 'JEllipsis',
|
||||
label: '选中用户',
|
||||
colProps: { span: 12 },
|
||||
},
|
||||
{
|
||||
field: 'user4',
|
||||
component: 'JSelectUserByDepartment',
|
||||
|
||||
@ -1,11 +1,19 @@
|
||||
<template>
|
||||
<BasicTable :ellipsis="true" @register="registerTable" :searchInfo="searchInfo" :columns="logColumns" :expand-column-width="16">
|
||||
<template #tableTitle>
|
||||
<a-tabs defaultActiveKey="4" @change="tabChange" size="small">
|
||||
<a-tab-pane tab="异常日志" key="4"></a-tab-pane>
|
||||
<a-tab-pane tab="登录日志" key="1"></a-tab-pane>
|
||||
<a-tab-pane tab="操作日志" key="2"></a-tab-pane>
|
||||
</a-tabs>
|
||||
<div class="table-title-bar">
|
||||
<a-tabs defaultActiveKey="4" @change="tabChange" size="small">
|
||||
<a-tab-pane tab="异常日志" key="4"></a-tab-pane>
|
||||
<a-tab-pane tab="登录日志" key="1"></a-tab-pane>
|
||||
<a-tab-pane tab="操作日志" key="2"></a-tab-pane>
|
||||
</a-tabs>
|
||||
<span class="export-btn" v-if="searchInfo.logType == 2">
|
||||
<a-tooltip>
|
||||
<template #title>导出</template>
|
||||
<a-button type="text" preIcon="ant-design:download-outlined" shape="circle" @click="onExportXls" />
|
||||
</a-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #expandedRowRender="{ record }">
|
||||
<div v-if="searchInfo.logType == 2">
|
||||
@ -30,7 +38,7 @@
|
||||
<script lang="ts" name="monitor-log" setup>
|
||||
import { ref } from 'vue';
|
||||
import { BasicTable, useTable, TableAction } from '/@/components/Table';
|
||||
import { getLogList } from './log.api';
|
||||
import { getLogList, getExportUrl } from './log.api';
|
||||
import {
|
||||
columns,
|
||||
searchFormSchema,
|
||||
@ -47,7 +55,7 @@
|
||||
const searchSchema = ref<any>(searchFormSchema);
|
||||
const searchInfo = { logType: '4' };
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext } = useListPage({
|
||||
const { prefixCls, tableContext, onExportXls } = useListPage({
|
||||
designScope: 'user-list',
|
||||
tableProps: {
|
||||
title: '日志列表',
|
||||
@ -62,6 +70,11 @@
|
||||
fieldMapToTime: [['fieldTime', ['createTime_begin', 'createTime_end'], 'YYYY-MM-DD']],
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
name:"操作日志",
|
||||
url: getExportUrl,
|
||||
params: searchInfo,
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }] = tableContext;
|
||||
@ -95,4 +108,16 @@
|
||||
.error-box {
|
||||
white-space: break-spaces;
|
||||
}
|
||||
.table-title-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
.export-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
:deep(.jeecg-basic-table-header__toolbar){
|
||||
width:100px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -2,6 +2,7 @@ import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/sys/log/list',
|
||||
exportXls = '/sys/log/exportXls',
|
||||
}
|
||||
|
||||
/**
|
||||
@ -11,3 +12,10 @@ enum Api {
|
||||
export const getLogList = (params) => {
|
||||
return defHttp.get({ url: Api.list, params });
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 导出api
|
||||
* @param params
|
||||
*/
|
||||
export const getExportUrl = Api.exportXls;
|
||||
|
||||
@ -58,6 +58,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a v-if="noticeFiles.length > 1" :href="downLoadFiles + '?id=' + content.id + '&token=' + getToken()" target="_blank" style="margin: 15px 6px; color: #5ac0fa">
|
||||
<download-outlined class="item-icon" style="margin-right: 5px" /><span>批量下载所有附件</span>
|
||||
</a>
|
||||
</template>
|
||||
</BasicModal>
|
||||
</template>
|
||||
@ -72,15 +75,21 @@
|
||||
import { getFileAccessHttpUrl } from '@/utils/common/compUtils';
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
import { encryptByBase64 } from '@/utils/cipher';
|
||||
import { getToken } from '@/utils/auth';
|
||||
const router = useRouter();
|
||||
const glob = useGlobSetting();
|
||||
const isUpdate = ref(true);
|
||||
const content = ref<any>({});
|
||||
const noticeFiles = ref([]);
|
||||
/**
|
||||
* 下载文件路径
|
||||
*/
|
||||
const downLoadFiles = `${glob.domainUrl}/sys/annountCement/downLoadFiles`;
|
||||
const emit = defineEmits(['close', 'register']);
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
noticeFiles.value = [];
|
||||
if (unref(isUpdate)) {
|
||||
//data.record.msgContent = '<p>2323</p><input onmouseover=alert(1)>xss test';
|
||||
//update-begin-author:taoyan date:2022-7-14 for: VUEN-1702 【禁止问题】sql注入漏洞
|
||||
@ -194,6 +203,24 @@
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.ant-card-meta-detail {
|
||||
display: flex !important ;
|
||||
justify-content: center !important;
|
||||
align-items: center !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
.ant-card-meta-title {
|
||||
font-size: 22px !important;
|
||||
color: rgba(51, 51, 51, 0.88);
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.ant-card .ant-card-meta-description {
|
||||
color: rgba(51, 51, 51, 0.45);
|
||||
}
|
||||
`;
|
||||
frameDoc.head.appendChild(style);
|
||||
|
||||
@ -273,8 +300,8 @@
|
||||
|
||||
.print-btn {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
top: 80px;
|
||||
right: 40px;
|
||||
cursor: pointer;
|
||||
color: #a3a3a5;
|
||||
z-index: 999;
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
<a-tabs v-model:activeKey="activeKey" @change="tabChange">
|
||||
<a-tab-pane key="1" tab="服务器信息"></a-tab-pane>
|
||||
<a-tab-pane key="2" tab="JVM信息" force-render></a-tab-pane>
|
||||
<!-- <a-tab-pane key="3" tab="Tomcat信息"></a-tab-pane> -->
|
||||
<a-tab-pane key="6" tab="Undertow信息"></a-tab-pane>
|
||||
<a-tab-pane key="3" tab="Tomcat信息"></a-tab-pane>
|
||||
<!-- <a-tab-pane key="6" tab="Undertow信息"></a-tab-pane>-->
|
||||
<a-tab-pane key="4" tab="磁盘监控">
|
||||
<DiskInfo v-if="activeKey == 4" style="height: 100%"></DiskInfo>
|
||||
</a-tab-pane>
|
||||
|
||||
@ -326,7 +326,7 @@
|
||||
background: white;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
z-index: 999;
|
||||
z-index: 800;
|
||||
border: 1px solid #eeeeee;
|
||||
:deep(.ant-spin) {
|
||||
position: absolute;
|
||||
|
||||
@ -447,7 +447,7 @@
|
||||
uploadUrlList.value = [];
|
||||
fileInfoList.value = [];
|
||||
knowList.value = [];
|
||||
|
||||
options.message = message;
|
||||
const readableStream = await defHttp.post(
|
||||
{
|
||||
url: props.url,
|
||||
@ -473,72 +473,7 @@
|
||||
console.error(e)
|
||||
//update-end---author:wangshuai---date:2025-04-28---for:【QQYUN-12297】【AI】聊天,超时以后提示---
|
||||
});
|
||||
const reader = readableStream.getReader();
|
||||
const decoder = new TextDecoder('UTF-8');
|
||||
let conversationId = '';
|
||||
let buffer = '';
|
||||
let text = ''; // 按 SSE 协议分割消息
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
//update-begin---author:wangshuai---date:2025-03-12---for:【QQYUN-11555】聊天时要流式显示消息---
|
||||
let result = decoder.decode(value, { stream: true });
|
||||
result = buffer + result;
|
||||
const lines = result.split('\n\n');
|
||||
for (let line of lines) {
|
||||
if (line.startsWith('data:')) {
|
||||
let content = line.replace('data:', '').trim();
|
||||
if(!content){
|
||||
continue;
|
||||
}
|
||||
if(!content.endsWith('}')){
|
||||
buffer = buffer + line;
|
||||
continue;
|
||||
}
|
||||
buffer = "";
|
||||
try {
|
||||
//update-begin---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答---
|
||||
if(content.indexOf(":::card:::") !== -1){
|
||||
content = content.replace(/\s+/g, '');
|
||||
}
|
||||
let parse = JSON.parse(content);
|
||||
await renderText(parse,conversationId,text,options).then((res)=>{
|
||||
text = res.returnText;
|
||||
conversationId = res.conversationId;
|
||||
});
|
||||
//update-end---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答---
|
||||
} catch (error) {
|
||||
console.log('Error parsing update:', error);
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-03-12---for:【QQYUN-11555】聊天时要流式显示消息---
|
||||
}else{
|
||||
if(!line){
|
||||
continue;
|
||||
}
|
||||
if(!line.endsWith('}')){
|
||||
buffer = buffer + line;
|
||||
continue;
|
||||
}
|
||||
buffer = "";
|
||||
//update-begin---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答---
|
||||
try {
|
||||
if(line.indexOf(":::card:::") !== -1){
|
||||
line = line.replace(/\s+/g, '');
|
||||
}
|
||||
let parse = JSON.parse(line);
|
||||
await renderText(parse, conversationId, text, options).then((res) => {
|
||||
text = res.returnText;
|
||||
conversationId = res.conversationId;
|
||||
});
|
||||
}catch (error) {
|
||||
console.log('Error parsing update:', error);
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答---
|
||||
}
|
||||
}
|
||||
}
|
||||
await renderChatByResult(readableStream,options);
|
||||
}
|
||||
// 是否使用上下文
|
||||
const handleUsingContext = () => {
|
||||
@ -598,12 +533,14 @@
|
||||
if(item.event == 'INIT_REQUEST_ID'){
|
||||
if (item.requestId) {
|
||||
requestId.value = item.requestId;
|
||||
localStorage.setItem('chat_requestId_' + uuid.value, JSON.stringify({ requestId: item.requestId, message: options.message }));
|
||||
}
|
||||
}
|
||||
if (item.event == 'MESSAGE_END') {
|
||||
topicId.value = item.topicId;
|
||||
conversationId = item.conversationId;
|
||||
uuid.value = item.conversationId;
|
||||
localStorage.removeItem('chat_requestId_' + uuid.value);
|
||||
handleStop();
|
||||
}
|
||||
if (item.event == 'FLOW_FINISHED') {
|
||||
@ -831,6 +768,135 @@
|
||||
await defHttp.uploadFile({ url: "/airag/chat/upload" }, { file: image }, { success: isReturn });
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染返回来的结果
|
||||
* @param readableStream
|
||||
* @param options
|
||||
*/
|
||||
async function renderChatByResult(readableStream, options) {
|
||||
const reader = readableStream.getReader();
|
||||
const decoder = new TextDecoder('UTF-8');
|
||||
let conversationId = '';
|
||||
let buffer = '';
|
||||
let text = ''; // 按 SSE 协议分割消息
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
//update-begin---author:wangshuai---date:2025-03-12---for:【QQYUN-11555】聊天时要流式显示消息---
|
||||
let result = decoder.decode(value, { stream: true });
|
||||
result = buffer + result;
|
||||
const lines = result.split('\n\n');
|
||||
for (let line of lines) {
|
||||
if (line.startsWith('data:')) {
|
||||
let content = line.replace('data:', '').trim();
|
||||
if(!content){
|
||||
continue;
|
||||
}
|
||||
if(!content.endsWith('}')){
|
||||
buffer = buffer + line;
|
||||
continue;
|
||||
}
|
||||
buffer = "";
|
||||
try {
|
||||
//update-begin---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答---
|
||||
if(content.indexOf(":::card:::") !== -1){
|
||||
content = content.replace(/\s+/g, '');
|
||||
}
|
||||
let parse = JSON.parse(content);
|
||||
await renderText(parse,conversationId,text,options).then((res)=>{
|
||||
text = res.returnText;
|
||||
conversationId = res.conversationId;
|
||||
});
|
||||
//update-end---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答---
|
||||
} catch (error) {
|
||||
console.log('Error parsing update:', error);
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-03-12---for:【QQYUN-11555】聊天时要流式显示消息---
|
||||
}else{
|
||||
if(!line){
|
||||
continue;
|
||||
}
|
||||
if(!line.endsWith('}')){
|
||||
buffer = buffer + line;
|
||||
continue;
|
||||
}
|
||||
buffer = "";
|
||||
//update-begin---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答---
|
||||
try {
|
||||
if(line.indexOf(":::card:::") !== -1){
|
||||
line = line.replace(/\s+/g, '');
|
||||
}
|
||||
let parse = JSON.parse(line);
|
||||
await renderText(parse, conversationId, text, options).then((res) => {
|
||||
text = res.returnText;
|
||||
conversationId = res.conversationId;
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('Error parsing update:', error);
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-03-13---for:【QQYUN-11572】发布到线上不能实时动态,内容不能加载出来,得刷新才能看到全部回答---
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ai重连
|
||||
*/
|
||||
async function aiReConnection() {
|
||||
//查询requestId
|
||||
let chat = localStorage.getItem("chat_requestId_" + uuid.value);
|
||||
if(chat) {
|
||||
let array = JSON.parse(chat);
|
||||
let message = array.message;
|
||||
let requestId = array.requestId;
|
||||
const result = await defHttp.get({ url: '/airag/chat/receive/' + requestId ,
|
||||
adapter: 'fetch',
|
||||
responseType: 'stream',
|
||||
timeout: 5 * 60 * 1000
|
||||
}, { isTransformResponse: false }).catch(async (err)=>{
|
||||
loading.value = false;
|
||||
});
|
||||
if(result && message){
|
||||
loading.value = true;
|
||||
//发送用户消息
|
||||
addChat(uuid.value, {
|
||||
dateTime: new Date().toLocaleString(),
|
||||
content: message,
|
||||
images:uploadUrlList.value?uploadUrlList.value:[],
|
||||
inversion: 'user',
|
||||
error: false,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: null },
|
||||
});
|
||||
let options: any = {};
|
||||
const lastContext = conversationList.value[conversationList.value.length - 1]?.conversationOptions;
|
||||
if (lastContext && usingContext.value) {
|
||||
options = { ...lastContext };
|
||||
}
|
||||
//添加ai消息
|
||||
addChat(uuid.value, {
|
||||
dateTime: new Date().toLocaleString(),
|
||||
content: '请稍后',
|
||||
loading: false,
|
||||
inversion: 'ai',
|
||||
error: false,
|
||||
conversationOptions: null,
|
||||
requestOptions: { prompt: message, options: { ...options } },
|
||||
referenceKnowledge: [],
|
||||
});
|
||||
options.message = message;
|
||||
scrollToBottom();
|
||||
//流式输出
|
||||
await renderChatByResult(result,options);
|
||||
} else {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//监听开场白
|
||||
watch(
|
||||
() => props.prologue,
|
||||
@ -882,6 +948,8 @@
|
||||
if(props.prologue && props.chatTitle){
|
||||
topChat(props.prologue)
|
||||
}
|
||||
//ai回复重连
|
||||
aiReConnection();
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
@ -183,7 +183,7 @@
|
||||
v-model:value="formState.modelId"
|
||||
:disabled="isRelease"
|
||||
placeholder="请选择AI模型"
|
||||
dict-code="airag_model where model_type = 'LLM',name,id"
|
||||
dict-code="airag_model where model_type = 'LLM' and activate_flag = 1,name,id"
|
||||
style="width: 100%;"
|
||||
></JDictSelectTag>
|
||||
</a-form-item>
|
||||
|
||||
@ -40,7 +40,7 @@ export const formSchema: FormSchema[] = [
|
||||
required: true,
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: {
|
||||
dictCode: "airag_model where model_type = 'EMBED',name,id",
|
||||
dictCode: "airag_model where model_type = 'EMBED' and activate_flag = 1,name,id",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -277,7 +277,7 @@
|
||||
async function handleVectorization(id) {
|
||||
rebuild({ knowIds: id }).then((res) =>{
|
||||
if(res.success){
|
||||
createMessage.success("向量化成功!");
|
||||
createMessage.success("操作成功,开始异步重建知识库,请稍后查看!");
|
||||
reload();
|
||||
}else{
|
||||
createMessage.warning("向量化失败!");
|
||||
|
||||
@ -47,6 +47,9 @@
|
||||
<li class="flex mr-14">
|
||||
<span class="label">模型类型</span>
|
||||
<span class="described">{{ item.modelType_dictText }}</span>
|
||||
<a-tooltip v-if="!item.activateFlag" title="未激活模型暂无法被系统其他功能调用,激活后可正常使用。">
|
||||
<span class="no-activate">未激活</span>
|
||||
</a-tooltip>
|
||||
</li>
|
||||
<li class="flex mr-14 mt-6">
|
||||
<span class="label">基础模型</span>
|
||||
@ -314,6 +317,17 @@
|
||||
color: #8a8f98;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.no-activate{
|
||||
font-size: 10px;
|
||||
color: #ff4d4f;
|
||||
border: 1px solid #ff4d4f;
|
||||
border-radius: 10px;
|
||||
padding: 0 6px;
|
||||
height: 14px;
|
||||
line-height: 12px;
|
||||
margin-left: 6px;
|
||||
align-self: center;
|
||||
}
|
||||
.described {
|
||||
font-weight: 400;
|
||||
margin-left: 14px;
|
||||
|
||||
@ -77,6 +77,7 @@
|
||||
</AutoComplete>
|
||||
</template>
|
||||
</BasicForm>
|
||||
<a-alert v-if="!modelActivate" message="模型未激活,请通过下方「保存并激活」按钮激活当前模型" type="warning" show-icon />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane :key="2" v-if="modelParamsShow">
|
||||
@ -96,9 +97,9 @@
|
||||
|
||||
</div>
|
||||
<template v-if="dataIndex === 'add' || dataIndex === 'edit'" #footer>
|
||||
<a-button @click="test" :loading="testLoading">测试</a-button>
|
||||
<a-button @click="cancel">关闭</a-button>
|
||||
<a-button @click="save" type="primary">保存</a-button>
|
||||
<a-button @click="save" type="primary" ghost="true">保存</a-button>
|
||||
<a-button @click="test" v-if="!modelActivate" :loading="testLoading" type="primary" >保存并激活</a-button>
|
||||
</template>
|
||||
<template v-else #footer> </template>
|
||||
</BasicModal>
|
||||
@ -159,6 +160,8 @@
|
||||
const modelParamsRef = ref();
|
||||
//测试按钮loading状态
|
||||
const testLoading = ref<boolean>(false);
|
||||
//模型是否已激活
|
||||
const modelActivate = ref<boolean>(false);
|
||||
|
||||
const getImage = (name) => {
|
||||
return imageList.value[name];
|
||||
@ -209,6 +212,11 @@
|
||||
if(values.result.modelType && values.result.modelType === 'LLM'){
|
||||
modelParamsShow.value = true;
|
||||
}
|
||||
if (values.result.activateFlag) {
|
||||
modelActivate.value = true;
|
||||
}else{
|
||||
modelActivate.value = false;
|
||||
}
|
||||
if(values.result.modelParams){
|
||||
modelParams.value = JSON.parse(values.result.modelParams)
|
||||
}
|
||||
@ -303,6 +311,11 @@
|
||||
values.modelParams = JSON.stringify(modelParams);
|
||||
}
|
||||
}
|
||||
if(modelActivate.value){
|
||||
values.activateFlag = 1
|
||||
}else{
|
||||
values.activateFlag = 0;
|
||||
}
|
||||
values.credential = JSON.stringify(credential);
|
||||
//新增
|
||||
if (!values.id) {
|
||||
@ -330,6 +343,7 @@
|
||||
function cancel() {
|
||||
dataIndex.value = 'list';
|
||||
closeModal();
|
||||
emit('success');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -354,7 +368,10 @@
|
||||
values.provider = modelData.value.value;
|
||||
}
|
||||
//测试
|
||||
await testConn(values);
|
||||
await testConn(values).then((result) => {
|
||||
modelActivate.value = true;
|
||||
save();
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.hasOwnProperty('errorFields')) {
|
||||
activeKey.value = 1;
|
||||
@ -424,6 +441,7 @@
|
||||
activeKey,
|
||||
modelParams,
|
||||
modelParamsShow,
|
||||
modelActivate,
|
||||
modelParamsRef,
|
||||
filterOption,
|
||||
getTitle,
|
||||
|
||||
@ -3,7 +3,7 @@ import { defHttp } from '/@/utils/http/axios';
|
||||
export enum Api {
|
||||
list = '/sys/user/queryByOrgCodeForAddressList',
|
||||
positionList = '/sys/position/list',
|
||||
queryDepartTreeSync = '/sys/sysDepart/queryDepartTreeSync',
|
||||
queryDepartTreeSync = '/sys/sysDepart/queryDepartAndPostTreeSync',
|
||||
}
|
||||
/**
|
||||
* 获取部门树列表
|
||||
|
||||
@ -14,7 +14,12 @@ export const columns: BasicColumn[] = [
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'departName',
|
||||
dataIndex: 'orgCodeTxt',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '主岗位',
|
||||
dataIndex: 'mainDepPostId_dictText',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
|
||||
@ -14,7 +14,11 @@
|
||||
:load-data="loadChildrenTreeData"
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
@select="onSelect"
|
||||
></a-tree>
|
||||
>
|
||||
<template #title="{ orgCategory, title }">
|
||||
<TreeIcon :orgCategory="orgCategory" :title="title"></TreeIcon>
|
||||
</template>
|
||||
</a-tree>
|
||||
</template>
|
||||
<a-empty v-else description="暂无数据" />
|
||||
</a-spin>
|
||||
@ -26,6 +30,7 @@
|
||||
import { queryDepartTreeSync } from '../address.api';
|
||||
import { searchByKeywords } from '/@/views/system/departUser/depart.user.api';
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
import TreeIcon from "@/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue";
|
||||
|
||||
const prefixCls = inject('prefixCls');
|
||||
// 定义props
|
||||
@ -142,7 +147,7 @@
|
||||
try {
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
let result = await searchByKeywords({ keyWord: value });
|
||||
let result = await searchByKeywords({ keyWord: value, orgCategory: '1,2,3,4' });
|
||||
if (Array.isArray(result)) {
|
||||
treeData.value = result;
|
||||
}
|
||||
|
||||
@ -25,10 +25,12 @@
|
||||
import { provide, ref, unref } from 'vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import DepartLeftTree from './components/DepartLeftTree.vue';
|
||||
import { BasicTable } from '/@/components/Table';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { columns, searchFormSchema } from './address.data';
|
||||
import { list, positionList } from './address.api';
|
||||
import { getCacheByDynKey } from "@/utils/auth";
|
||||
import { JEECG_CHAT_UID } from "@/enums/cacheEnum";
|
||||
|
||||
const { prefixCls } = useDesign('address-list');
|
||||
provide('prefixCls', prefixCls);
|
||||
@ -46,14 +48,13 @@
|
||||
api: list,
|
||||
columns,
|
||||
//update-begin---author:wangshuai ---date:20220629 for:[VUEN-1485]进入系统管理--通讯录页面后,网页命令行报错------------
|
||||
rowKey: 'userId',
|
||||
rowKey: 'id',
|
||||
//update-end---author:wangshuai ---date:20220629 for:[VUEN-1485]进入系统管理--通讯录页面后,网页命令行报错--------------
|
||||
showIndexColumn: true,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
canResize: false,
|
||||
actionColumn: null,
|
||||
showTableSetting: false,
|
||||
// 请求之前对参数做处理
|
||||
beforeFetch(params) {
|
||||
|
||||
@ -78,4 +78,4 @@ export const deleteThirdAppConfig = (params, handleSuccess) => {
|
||||
return defHttp.delete({ url: Api.deleteThirdAppConfig, params }, { joinParamsToUrl: true }).then(() => {
|
||||
handleSuccess();
|
||||
});
|
||||
};
|
||||
};
|
||||
97
jeecgboot-vue3/src/views/system/depart/TenantDepartList.vue
Normal file
97
jeecgboot-vue3/src/views/system/depart/TenantDepartList.vue
Normal file
@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<a-row :class="['p-4', `${prefixCls}--box`]" type="flex" :gutter="10">
|
||||
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<DepartLeftTree
|
||||
v-if="showDepart"
|
||||
ref="leftTree"
|
||||
@select="onTreeSelect"
|
||||
@rootTreeData="onRootTreeData"
|
||||
:isTenantDepart="true"
|
||||
:loginTenantName="loginTenantName"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<div style="height: 100%" :class="[`${prefixCls}`]">
|
||||
<a-tabs v-show="departData != null" defaultActiveKey="base-info">
|
||||
<a-tab-pane tab="基本信息" key="base-info" forceRender style="position: relative">
|
||||
<div style="padding: 20px">
|
||||
<DepartFormTab v-if="showDepart" :data="departData" :rootTreeData="rootTreeData" @success="onSuccess" :isTenantDepart="true" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<div v-show="departData == null" style="padding-top: 40px">
|
||||
<a-empty description="尚未选择部门" />
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup name="TenantDepartList">
|
||||
import { onMounted, provide, ref } from 'vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import DepartLeftTree from '/@/views/system/depart/components/DepartLeftTree.vue';
|
||||
import DepartFormTab from '/@/views/system/depart/components/DepartFormTab.vue';
|
||||
import { getLoginTenantName } from '@/views/system/tenant/tenant.api';
|
||||
import { tenantSaasMessage } from '@/utils/common/compUtils';
|
||||
|
||||
const { prefixCls } = useDesign('tenant-depart-manage');
|
||||
provide('prefixCls', prefixCls);
|
||||
|
||||
// 给子组件定义一个ref变量
|
||||
const leftTree = ref();
|
||||
//是否显示部门
|
||||
const showDepart = ref(false);
|
||||
|
||||
// 当前选中的部门信息
|
||||
const departData = ref({});
|
||||
const rootTreeData = ref<any[]>([]);
|
||||
const loginTenantName = ref<string>('');
|
||||
|
||||
/**
|
||||
* 获取租户名称
|
||||
*/
|
||||
getTenantName();
|
||||
|
||||
async function getTenantName() {
|
||||
loginTenantName.value = await getLoginTenantName();
|
||||
if (loginTenantName.value) {
|
||||
showDepart.value = true;
|
||||
} else {
|
||||
showDepart.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 左侧树选择后触发
|
||||
function onTreeSelect(data) {
|
||||
departData.value = data;
|
||||
}
|
||||
|
||||
// 左侧树rootTreeData触发
|
||||
function onRootTreeData(data) {
|
||||
rootTreeData.value = data;
|
||||
}
|
||||
|
||||
function onSuccess() {
|
||||
leftTree.value.loadRootTreeData();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
//提示信息
|
||||
tenantSaasMessage('租户部门');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-tenant-depart-manage';
|
||||
|
||||
.@{prefix-cls} {
|
||||
background: @component-background;
|
||||
|
||||
&--box {
|
||||
.ant-tabs-nav {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +1,17 @@
|
||||
<template>
|
||||
<BasicModal :title="title" :width="800" v-bind="$attrs" @ok="handleOk" @register="registerModal">
|
||||
<BasicForm @register="registerForm" />
|
||||
<BasicForm @register="registerForm" >
|
||||
<template #depPostParentId="{ model, field }">
|
||||
<a-tree-select v-model:value="depPostValue" :treeData="treeData" allowClear treeCheckable @select="treeSelect">
|
||||
<template #title="{ orgCategory, title }">
|
||||
<TreeIcon :orgCategory="orgCategory" :title="title"></TreeIcon>
|
||||
</template>
|
||||
<template #tagRender="{option}">
|
||||
<span style="margin-left: 10px" v-if="orgNameMap[option.id]">{{orgNameMap[option.id]}}</span>
|
||||
</template>
|
||||
</a-tree-select>
|
||||
</template>
|
||||
</BasicForm>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
@ -12,6 +23,8 @@
|
||||
|
||||
import { saveOrUpdateDepart } from '../depart.api';
|
||||
import { useBasicFormSchema, orgCategoryOptions } from '../depart.data';
|
||||
import TreeIcon from "@/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue";
|
||||
import { getDepartPathNameByOrgCode } from "@/utils/common/compUtils";
|
||||
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
const props = defineProps({
|
||||
@ -23,10 +36,15 @@
|
||||
// 当前的弹窗数据
|
||||
const model = ref<object>({});
|
||||
const title = computed(() => (isUpdate.value ? '编辑' : '新增'));
|
||||
const treeData = ref<any>([]);
|
||||
//上级岗位
|
||||
const depPostValue = ref<any>([]);
|
||||
//上级岗位名称映射
|
||||
const orgNameMap = ref<Record<string, string>>({});
|
||||
|
||||
//注册表单
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
|
||||
schemas: useBasicFormSchema().basicFormSchema,
|
||||
schemas: useBasicFormSchema(treeData).basicFormSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
|
||||
@ -37,6 +55,17 @@
|
||||
// 当前是否为添加子级
|
||||
let isChild = unref(data?.isChild);
|
||||
let categoryOptions = isChild ? orgCategoryOptions.child : orgCategoryOptions.root;
|
||||
|
||||
if(data.record?.orgCategory && data.record?.orgCategory === '2'){
|
||||
categoryOptions = orgCategoryOptions.childDepartPost;
|
||||
}
|
||||
if(data.record?.orgCategory && data.record?.orgCategory === '3'){
|
||||
categoryOptions = orgCategoryOptions.childPost;
|
||||
}
|
||||
if(data.record?.depPostParentId){
|
||||
orgNameMap.value[data.record.depPostParentId] = await getDepartPathNameByOrgCode('', '', data.record.depPostParentId);
|
||||
depPostValue.value = [data.record.depPostParentId];
|
||||
}
|
||||
// 隐藏不需要展示的字段
|
||||
updateSchema([
|
||||
{
|
||||
@ -62,11 +91,14 @@
|
||||
if (typeof record !== 'object') {
|
||||
record = {};
|
||||
}
|
||||
let orgCategory = data.record?.orgCategory;
|
||||
let company = orgCategory === '1' || orgCategory === '4';
|
||||
delete data.record?.orgCategory;
|
||||
// 赋默认值
|
||||
record = Object.assign(
|
||||
{
|
||||
departOrder: 0,
|
||||
orgCategory: categoryOptions[0].value,
|
||||
orgCategory: company?categoryOptions[1].value:categoryOptions[0].value,
|
||||
},
|
||||
record
|
||||
);
|
||||
@ -79,6 +111,11 @@
|
||||
try {
|
||||
setModalProps({ confirmLoading: true });
|
||||
let values = await validate();
|
||||
if(depPostValue.value && depPostValue.value.length > 0){
|
||||
values.depPostParentId = depPostValue.value[0];
|
||||
}else{
|
||||
values.depPostParentId = "";
|
||||
}
|
||||
//提交表单
|
||||
await saveOrUpdateDepart(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
@ -89,4 +126,29 @@
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 树选中事件
|
||||
*
|
||||
* @param info
|
||||
* @param keys
|
||||
*/
|
||||
async function treeSelect(keys,info) {
|
||||
if (info.checkable) {
|
||||
//解决闪动问题
|
||||
orgNameMap.value[info.id] = "";
|
||||
depPostValue.value = [info.value];
|
||||
orgNameMap.value[info.id] = await getDepartPathNameByOrgCode(info.orgCode,info.label,info.id);
|
||||
} else {
|
||||
depPostValue.value = [];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-select-selector .ant-select-selection-item){
|
||||
svg {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,6 +1,17 @@
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<BasicForm @register="registerForm" />
|
||||
<BasicForm @register="registerForm" >
|
||||
<template #depPostParentId="{ model, field }">
|
||||
<a-tree-select v-model:value="depPostValue" :treeData="treeData" allowClear treeCheckable @select="treeSelect">
|
||||
<template #title="{ orgCategory, title }">
|
||||
<TreeIcon :orgCategory="orgCategory" :title="title"></TreeIcon>
|
||||
</template>
|
||||
<template #tagRender="{ option }">
|
||||
<span style="margin-left: 10px">{{ orgNameMap[option.id] }}</span>
|
||||
</template>
|
||||
</a-tree-select>
|
||||
</template>
|
||||
</BasicForm>
|
||||
<div class="j-box-bottom-button offset-20" style="margin-top: 30px">
|
||||
<div class="j-box-bottom-button-float" :class="[`${prefixCls}`]">
|
||||
<a-button preIcon="ant-design:sync-outlined" @click="onReset">重置</a-button>
|
||||
@ -14,8 +25,10 @@
|
||||
import { watch, computed, inject, ref, unref, onMounted } from 'vue';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { saveOrUpdateDepart } from '../depart.api';
|
||||
import { useBasicFormSchema, orgCategoryOptions } from '../depart.data';
|
||||
import { useBasicFormSchema, orgCategoryOptions, positionChange } from '../depart.data';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import TreeIcon from "@/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue";
|
||||
import { getDepartPathNameByOrgCode } from '@/utils/common/compUtils';
|
||||
|
||||
const { prefixCls } = useDesign('j-depart-form-content');
|
||||
|
||||
@ -29,10 +42,15 @@
|
||||
const isUpdate = ref<boolean>(true);
|
||||
// 当前的弹窗数据
|
||||
const model = ref<object>({});
|
||||
const treeData = ref<any>([]);
|
||||
//上级岗位
|
||||
const depPostValue = ref<any>([]);
|
||||
//上级岗位名称映射
|
||||
const orgNameMap = ref<Record<string, string>>({});
|
||||
|
||||
//注册表单
|
||||
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
|
||||
schemas: useBasicFormSchema().basicFormSchema,
|
||||
schemas: useBasicFormSchema(treeData).basicFormSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
|
||||
@ -59,6 +77,11 @@
|
||||
record = {};
|
||||
}
|
||||
model.value = record;
|
||||
if (record.depPostParentId) {
|
||||
orgNameMap.value[record.depPostParentId] = await getDepartPathNameByOrgCode('', '', record.depPostParentId);
|
||||
depPostValue.value = [record.depPostParentId];
|
||||
}
|
||||
positionChange(record.positionId, record, treeData);
|
||||
await resetFields();
|
||||
await setFieldsValue({ ...record });
|
||||
},
|
||||
@ -104,6 +127,11 @@
|
||||
loading.value = true;
|
||||
let values = await validate();
|
||||
values = Object.assign({}, model.value, values);
|
||||
if (depPostValue.value && depPostValue.value.length > 0) {
|
||||
values.depPostParentId = depPostValue.value[0];
|
||||
} else {
|
||||
values.depPostParentId = '';
|
||||
}
|
||||
//提交表单
|
||||
await saveOrUpdateDepart(values, isUpdate.value);
|
||||
//刷新列表
|
||||
@ -113,6 +141,22 @@
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 树选中事件
|
||||
*
|
||||
* @param info
|
||||
* @param keys
|
||||
*/
|
||||
async function treeSelect(keys, info) {
|
||||
if (info.checkable) {
|
||||
orgNameMap.value[info.id] = '';
|
||||
depPostValue.value = [info.value];
|
||||
orgNameMap.value[info.id] = await getDepartPathNameByOrgCode(info.orgCode, info.label, info.id);
|
||||
} else {
|
||||
depPostValue.value = [];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
// update-begin-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
|
||||
@ -126,3 +170,10 @@
|
||||
/*end 兼容暗夜模式*/
|
||||
// update-end-author:liusq date:20230625 for: [issues/563]暗色主题部分失效
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-select-selector .ant-select-selection-item){
|
||||
svg{
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,14 +1,12 @@
|
||||
<template>
|
||||
<a-card :bordered="false" style="height: 100%">
|
||||
<div class="j-table-operator" style="width: 100%">
|
||||
<div class="j-table-operator" style="width: 100%;display: flex;align-items: center">
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="onAddDepart">新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="onAddChildDepart()">添加下级</a-button>
|
||||
<a-upload name="file" :showUploadList="false" :customRequest="onImportXls">
|
||||
<a-upload name="file" :showUploadList="false" :customRequest="onImportXls" v-if="!isTenantDepart">
|
||||
<a-button type="primary" preIcon="ant-design:import-outlined">导入</a-button>
|
||||
</a-upload>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls">导出</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:sync-outlined">同步企微?</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:sync-outlined">同步钉钉?</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls" v-if="!isTenantDepart">导出</a-button>
|
||||
<template v-if="checkedKeys.length > 0">
|
||||
<a-dropdown>
|
||||
<template #overlay>
|
||||
@ -25,6 +23,10 @@
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<Icon icon="ant-design:question-circle-outlined" style="margin-left: 10px;cursor: pointer" @click="tipShow = true"></Icon>
|
||||
<div v-if="loginTenantName" style="margin-left: 10px;"
|
||||
>当前登录租户: <span class="tenant-name">{{ loginTenantName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a-alert type="info" show-icon class="alert" style="margin-bottom: 8px">
|
||||
<template #message>
|
||||
@ -54,8 +56,9 @@
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
@check="onCheck"
|
||||
@select="onSelect"
|
||||
style="overflow-y: auto;height: calc(100vh - 330px);"
|
||||
>
|
||||
<template #title="{ key: treeKey, title, dataRef }">
|
||||
<template #title="{ key: treeKey, title, dataRef, data }">
|
||||
<a-dropdown :trigger="['contextmenu']">
|
||||
<Popconfirm
|
||||
:open="visibleTreeKey === treeKey"
|
||||
@ -66,12 +69,12 @@
|
||||
@confirm="onDelete(dataRef)"
|
||||
@openChange="onVisibleChange"
|
||||
>
|
||||
<span>{{ title }}</span>
|
||||
<TreeIcon :orgCategory="dataRef.orgCategory" :title="title"></TreeIcon>
|
||||
</Popconfirm>
|
||||
|
||||
<template #overlay>
|
||||
<a-menu @click="">
|
||||
<a-menu-item key="1" @click="onAddChildDepart(dataRef)">添加子级</a-menu-item>
|
||||
<a-menu-item key="1" @click="onAddChildDepart(dataRef)" v-if="data.orgCategory !== '3'">添加下级</a-menu-item>
|
||||
<a-menu-item key="2" @click="visibleTreeKey = treeKey">
|
||||
<span style="color: red">删除</span>
|
||||
</a-menu-item>
|
||||
@ -85,22 +88,44 @@
|
||||
</a-spin>
|
||||
<DepartFormModal :rootTreeData="treeData" @register="registerModal" @success="loadRootTreeData" />
|
||||
</a-card>
|
||||
<a-modal v-model:open="tipShow" :footer="null" title="部门规则说明" :width="800">
|
||||
<ul class="departmentalRulesTip">
|
||||
<li>当前部门机构设置支持集团组织架构,第一级默认为公司,下级可创建子公司、部门和岗位。</li>
|
||||
<li><br/></li>
|
||||
<li>1、岗位下不能添加下级。</li>
|
||||
<li>2、部门下不能直接添加子公司。</li>
|
||||
<li>3、子公司下可继续添加子公司。</li>
|
||||
<li>4、岗位需配置职务级别,岗位的级别高低和上下级关系均以职务级别及上级岗位设置为准。</li>
|
||||
<li>5、董事长岗位仅可选择上级公司(子公司或总公司)各部门的所有岗位为上级岗位。</li>
|
||||
<li>6、非董事长岗位仅可选择当前父级部门及本部门内级别更高的岗位为上级岗位。</li>
|
||||
<li><br/></li>
|
||||
<li><b>特别说明:</b>董事长相关逻辑为固定写死,职务等级“董事长”的表述请勿修改。</li>
|
||||
</ul>
|
||||
<div style="height: 10px"></div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, ref, unref } from 'vue';
|
||||
import { inject, nextTick, ref, unref, defineEmits } from 'vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useMethods } from '/@/hooks/system/useMethods';
|
||||
import { Api, deleteBatchDepart, queryDepartTreeSync } from '../depart.api';
|
||||
import { Api, deleteBatchDepart, queryDepartAndPostTreeSync } from '../depart.api';
|
||||
import { searchByKeywords } from '/@/views/system/departUser/depart.user.api';
|
||||
import DepartFormModal from '/@/views/system/depart/components/DepartFormModal.vue';
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
import TreeIcon from "@/components/Form/src/jeecg/components/TreeIcon/TreeIcon.vue";
|
||||
|
||||
const prefixCls = inject('prefixCls');
|
||||
const emit = defineEmits(['select', 'rootTreeData']);
|
||||
const { createMessage } = useMessage();
|
||||
const { handleImportXls, handleExportXls } = useMethods();
|
||||
const props = defineProps({
|
||||
//是否为租户部门
|
||||
isTenantDepart: { default: false, type: Boolean },
|
||||
//当前登录租户
|
||||
loginTenantName: { default: "", type: String },
|
||||
})
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
// 部门树列表数据
|
||||
@ -121,6 +146,8 @@
|
||||
const visibleTreeKey = ref<any>(null);
|
||||
// 搜索关键字
|
||||
const searchKeyword = ref('');
|
||||
// 提示弹窗是否显示
|
||||
const tipShow = ref<boolean>(false);
|
||||
|
||||
// 注册 modal
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
@ -130,7 +157,7 @@
|
||||
try {
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
const result = await queryDepartTreeSync();
|
||||
const result = await queryDepartAndPostTreeSync();
|
||||
if (Array.isArray(result)) {
|
||||
treeData.value = result;
|
||||
}
|
||||
@ -158,7 +185,7 @@
|
||||
// 加载子级部门信息
|
||||
async function loadChildrenTreeData(treeNode) {
|
||||
try {
|
||||
const result = await queryDepartTreeSync({
|
||||
const result = await queryDepartAndPostTreeSync({
|
||||
pid: treeNode.dataRef.id,
|
||||
});
|
||||
if (result.length == 0) {
|
||||
@ -231,7 +258,11 @@
|
||||
createMessage.warning('请先选择一个部门');
|
||||
return;
|
||||
}
|
||||
const record = { parentId: data.id };
|
||||
if(data.orgCategory === '3'){
|
||||
createMessage.warning('岗位下无法添加子级!');
|
||||
return;
|
||||
}
|
||||
const record = { parentId: data.id, orgCategory: data.orgCategory };
|
||||
openModal(true, { isUpdate: false, isChild: true, record });
|
||||
}
|
||||
|
||||
@ -241,7 +272,7 @@
|
||||
try {
|
||||
loading.value = true;
|
||||
treeData.value = [];
|
||||
let result = await searchByKeywords({ keyWord: value });
|
||||
let result = await searchByKeywords({ keyWord: value, orgCategory: "1,2,3,4" });
|
||||
if (Array.isArray(result)) {
|
||||
treeData.value = result;
|
||||
}
|
||||
@ -336,3 +367,18 @@
|
||||
loadRootTreeData,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.departmentalRulesTip{
|
||||
margin: 20px;
|
||||
background-color: #f8f9fb;
|
||||
color: #99a1a9;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
}
|
||||
.tenant-name {
|
||||
text-decoration: underline;
|
||||
margin: 5px;
|
||||
font-size: 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<template v-if="treeData && treeData.length > 0">
|
||||
<BasicTree ref="basicTree" :treeData="treeData" :checkStrictly="true" style="height: 500px; overflow: auto"></BasicTree>
|
||||
</template>
|
||||
<a-empty v-else description="无岗位消息" />
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { BasicTree } from '/@/components/Tree/index';
|
||||
import { getRankRelation } from '../depart.api';
|
||||
|
||||
const props = defineProps({
|
||||
data: { type: Object, default: () => ({}) },
|
||||
});
|
||||
// 当前选中的部门ID,可能会为空,代表未选择部门
|
||||
const departId = computed(() => props.data?.id);
|
||||
|
||||
const basicTree = ref();
|
||||
const loading = ref<boolean>(false);
|
||||
//树的全部节点信息
|
||||
const treeData = ref<any[]>([]);
|
||||
|
||||
watch(departId, (val) => loadData(val), { immediate: true });
|
||||
|
||||
async function loadData(val) {
|
||||
try {
|
||||
loading.value = true;
|
||||
await getRankRelation({ departId: val }).then((res) => {
|
||||
if (res.success) {
|
||||
treeData.value = res.result;
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.depart-rule-tree :deep(.scrollbar__bar) {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<!--引用表格-->
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="createUser" :disabled="!orgCode">新建用户</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="selectAddUser" :disabled="!orgCode || props.data?.orgCategory === '3'"
|
||||
>添加已有用户</a-button
|
||||
>
|
||||
</template>
|
||||
<!-- 插槽:行内操作按钮 -->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<UserDrawer @register="registerDrawer" @success="onUserDrawerSuccess" />
|
||||
<UserSelectModal ref="userSelectModalRef" rowKey="id" @register="registerSelUserModal" @getSelectResult="onSelectUserOk" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref, watch } from 'vue';
|
||||
import { ActionItem, BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
|
||||
import UserDrawer from '/@/views/system/user/UserDrawer.vue';
|
||||
import UserSelectModal from '/@/components/Form/src/jeecg/components/modal/UserSelectModal.vue';
|
||||
import { queryByOrgCodeForAddressList } from '../depart.api';
|
||||
import { ColEx } from '/@/components/Form/src/types';
|
||||
import { userColumns } from '@/views/system/depart/depart.data';
|
||||
import { linkDepartUserBatch } from '@/views/system/departUser/depart.user.api';
|
||||
|
||||
const prefixCls = inject('prefixCls');
|
||||
const props = defineProps({
|
||||
data: { require: true, type: Object },
|
||||
});
|
||||
const userSelectModalRef: any = ref(null);
|
||||
// 当前选中的部门code,可能会为空,代表未选择部门
|
||||
const orgCode = computed(() => props.data?.orgCode);
|
||||
// 当前部门id
|
||||
const departId = computed(() => props.data?.id);
|
||||
|
||||
// 自适应列配置
|
||||
const adaptiveColProps: Partial<ColEx> = {
|
||||
xs: 24, // <576px
|
||||
sm: 24, // ≥576px
|
||||
md: 24, // ≥768px
|
||||
lg: 12, // ≥992px
|
||||
xl: 12, // ≥1200px
|
||||
xxl: 8, // ≥1600px
|
||||
};
|
||||
// 列表页面公共参数、方法
|
||||
const { tableContext, createMessage } = useListPage({
|
||||
tableProps: {
|
||||
api: queryByOrgCodeForAddressList,
|
||||
columns: userColumns,
|
||||
canResize: false,
|
||||
rowKey: 'id',
|
||||
formConfig: {
|
||||
// schemas: userInfoSearchFormSchema,
|
||||
baseColProps: adaptiveColProps,
|
||||
labelAlign: 'left',
|
||||
labelCol: {
|
||||
xs: 24,
|
||||
sm: 24,
|
||||
md: 24,
|
||||
lg: 9,
|
||||
xl: 7,
|
||||
xxl: 5,
|
||||
},
|
||||
// 操作按钮配置
|
||||
actionColOptions: {
|
||||
...adaptiveColProps,
|
||||
style: { textAlign: 'left' },
|
||||
},
|
||||
},
|
||||
tableSetting: { cacheKey: 'depart_user_userInfo' },
|
||||
// 请求之前对参数做处理
|
||||
beforeFetch(params) {
|
||||
return Object.assign(params, { orgCode: orgCode.value });
|
||||
},
|
||||
immediate: !!orgCode.value,
|
||||
},
|
||||
});
|
||||
|
||||
// 注册 ListTable
|
||||
const [registerTable, { reload, setProps, setLoading, updateTableDataRecord }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
() => reload()
|
||||
);
|
||||
//注册drawer
|
||||
const [registerDrawer, { openDrawer, setDrawerProps }] = useDrawer();
|
||||
const [registerUserAuthDrawer, userAuthDrawer] = useDrawer();
|
||||
// 注册用户选择 modal
|
||||
const [registerSelUserModal, selUserModal] = useModal();
|
||||
|
||||
// 清空选择的行
|
||||
function clearSelection() {
|
||||
selectedRowKeys.value = [];
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
async function createUser() {
|
||||
if (!departId.value) {
|
||||
createMessage.warning('请先选择一个部门');
|
||||
} else {
|
||||
let mainDepPostId = '';
|
||||
let selecteddeparts = departId.value;
|
||||
if (props.data?.orgCategory === '3') {
|
||||
mainDepPostId = departId.value;
|
||||
selecteddeparts = props.data.parentId;
|
||||
}
|
||||
openDrawer(true, {
|
||||
isUpdate: false,
|
||||
// 初始化负责部门
|
||||
nextDepartOptions: { value: props.data?.key, label: props.data?.title },
|
||||
//初始化岗位
|
||||
record: {
|
||||
mainDepPostId: mainDepPostId,
|
||||
activitiSync: 1,
|
||||
userIdentity: 1,
|
||||
selecteddeparts: selecteddeparts,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 查看用户详情
|
||||
function showUserDetail(record) {
|
||||
openDrawer(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
showFooter: false,
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑用户信息
|
||||
function editUserInfo(record) {
|
||||
openDrawer(true, { isUpdate: true, record, departDisabled: true, departPostDisabled: true });
|
||||
}
|
||||
|
||||
// 选择添加已有用户
|
||||
function selectAddUser() {
|
||||
userSelectModalRef.value.rowSelection.selectedRowKeys = [];
|
||||
selUserModal.openModal();
|
||||
}
|
||||
|
||||
// 选择用户成功
|
||||
async function onSelectUserOk(options, userIdList) {
|
||||
if (userIdList.length > 0) {
|
||||
try {
|
||||
setLoading(true);
|
||||
await linkDepartUserBatch(departId.value, userIdList);
|
||||
reload();
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户抽屉表单成功回调
|
||||
*/
|
||||
function onUserDrawerSuccess({ isUpdate, values }) {
|
||||
isUpdate ? updateTableDataRecord(values.id, values) : reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record): ActionItem[] {
|
||||
return [
|
||||
{ label: '编辑', onClick: editUserInfo.bind(null, record) },
|
||||
{ label: '详情', onClick: showUserDetail.bind(null, record) },
|
||||
];
|
||||
}
|
||||
</script>
|
||||
@ -24,6 +24,14 @@ export enum Api {
|
||||
getUpdateDepartInfo = '/sys/user/getUpdateDepartInfo',
|
||||
doUpdateDepartInfo = '/sys/user/doUpdateDepartInfo',
|
||||
changeDepartChargePerson = '/sys/user/changeDepartChargePerson',
|
||||
//根据部门id获取岗位信息
|
||||
getPositionByDepartId = '/sys/sysDepart/getPositionByDepartId',
|
||||
//根据部门id获取岗位上下级关系
|
||||
getRankRelation = '/sys/sysDepart/getRankRelation',
|
||||
//异步获取部门和岗位
|
||||
queryDepartAndPostTreeSync = '/sys/sysDepart/queryDepartAndPostTreeSync',
|
||||
//获取部门和岗位下的成员
|
||||
queryByOrgCodeForAddressList = '/sys/user/queryByOrgCodeForAddressList',
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,6 +39,11 @@ export enum Api {
|
||||
*/
|
||||
export const queryDepartTreeSync = (params?) => defHttp.get({ url: Api.queryDepartTreeSync, params });
|
||||
|
||||
/**
|
||||
* 获取部门和岗位树列表
|
||||
*/
|
||||
export const queryDepartAndPostTreeSync = (params?) => defHttp.get({ url: Api.queryDepartAndPostTreeSync, params });
|
||||
|
||||
/**
|
||||
* 保存或者更新部门角色
|
||||
*/
|
||||
@ -120,3 +133,21 @@ export const deleteDepart = (id) => defHttp.delete({ url: Api.delete, params:{ i
|
||||
* @param params
|
||||
*/
|
||||
export const changeDepartChargePerson = (params) => defHttp.put({ url: Api.changeDepartChargePerson, params });
|
||||
|
||||
/**
|
||||
* 根据部门id获取岗位信息
|
||||
*/
|
||||
export const getPositionByDepartId = (params) => defHttp.get({ url: Api.getPositionByDepartId, params }, { isTransformResponse: false });
|
||||
|
||||
/**
|
||||
* 根据部门id获取岗位上下级关系
|
||||
* @param params
|
||||
*/
|
||||
export const getRankRelation = (params) => defHttp.get({ url: Api.getRankRelation, params }, { isTransformResponse: false });
|
||||
|
||||
/**
|
||||
* 根据部门或岗位编码获取通讯录成员
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
export const queryByOrgCodeForAddressList = (params) => defHttp.get({ url: Api.queryByOrgCodeForAddressList, params });
|
||||
@ -1,7 +1,16 @@
|
||||
import { FormSchema } from '/@/components/Form';
|
||||
import { getPositionByDepartId } from "./depart.api";
|
||||
import { useMessage } from "@/hooks/web/useMessage";
|
||||
import { BasicColumn } from "@/components/Table";
|
||||
import { getDepartPathNameByOrgCode } from '@/utils/common/compUtils';
|
||||
import { h, ref } from 'vue';
|
||||
|
||||
const { createMessage: $message } = useMessage();
|
||||
//部门名称
|
||||
const departNamePath = ref<Record<string, string>>({});
|
||||
|
||||
// 部门基础表单
|
||||
export function useBasicFormSchema() {
|
||||
export function useBasicFormSchema(treeData) {
|
||||
const basicFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'departName',
|
||||
@ -19,7 +28,21 @@ export function useBasicFormSchema() {
|
||||
componentProps: {
|
||||
treeData: [],
|
||||
placeholder: '无',
|
||||
treeCheckAble: true,
|
||||
multiple: true,
|
||||
dropdownStyle: { maxHeight: '200px', overflow: 'auto' },
|
||||
tagRender: (options) => {
|
||||
const { value, label, option } = options;
|
||||
if (departNamePath.value[value]) {
|
||||
return h(
|
||||
'span', { style: { marginLeft: '10px' } },
|
||||
departNamePath.value[value]
|
||||
);
|
||||
}
|
||||
getDepartPathNameByOrgCode('', label, option.id).then((data) => {
|
||||
departNamePath.value[value] = data;
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -36,6 +59,34 @@ export function useBasicFormSchema() {
|
||||
component: 'RadioButtonGroup',
|
||||
componentProps: { options: [] },
|
||||
},
|
||||
{
|
||||
field: 'positionId',
|
||||
label: '职务级别',
|
||||
component: 'JDictSelectTag',
|
||||
componentProps: ({ formModel, formActionType }) => {
|
||||
return {
|
||||
dictCode: "sys_position,name,id, 1=1 order by post_level asc",
|
||||
getPopupContainer: ()=> document.body,
|
||||
onChange: (value) => {
|
||||
formModel.depPostParentId = "";
|
||||
return positionChange(value, formModel, treeData);
|
||||
},
|
||||
}
|
||||
},
|
||||
ifShow:({ values })=>{
|
||||
return values.orgCategory === '3'
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
field: 'depPostParentId',
|
||||
label: '上级岗位',
|
||||
component: 'TreeSelect',
|
||||
ifShow:({ values })=>{
|
||||
return values.orgCategory === '3'
|
||||
},
|
||||
slot: 'depPostParentId',
|
||||
},
|
||||
{
|
||||
field: 'departOrder',
|
||||
label: '排序',
|
||||
@ -49,6 +100,9 @@ export function useBasicFormSchema() {
|
||||
componentProps: {
|
||||
placeholder: '请输入电话',
|
||||
},
|
||||
ifShow:({ values })=>{
|
||||
return values.orgCategory !== '3'
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'fax',
|
||||
@ -57,6 +111,9 @@ export function useBasicFormSchema() {
|
||||
componentProps: {
|
||||
placeholder: '请输入传真',
|
||||
},
|
||||
ifShow:({ values })=>{
|
||||
return values.orgCategory !== '3'
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'address',
|
||||
@ -65,6 +122,9 @@ export function useBasicFormSchema() {
|
||||
componentProps: {
|
||||
placeholder: '请输入地址',
|
||||
},
|
||||
ifShow:({ values })=>{
|
||||
return values.orgCategory !== '3'
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'memo',
|
||||
@ -73,6 +133,9 @@ export function useBasicFormSchema() {
|
||||
componentProps: {
|
||||
placeholder: '请输入备注',
|
||||
},
|
||||
ifShow:({ values })=>{
|
||||
return values.orgCategory !== '3'
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'id',
|
||||
@ -90,7 +153,59 @@ export const orgCategoryOptions = {
|
||||
root: [{ value: '1', label: '公司' }],
|
||||
// 子级部门
|
||||
child: [
|
||||
{ value: '4', label: '子公司' },
|
||||
{ value: '2', label: '部门' },
|
||||
{ value: '3', label: '岗位' },
|
||||
],
|
||||
//部门岗位
|
||||
childDepartPost: [
|
||||
{ value: '2', label: '部门' },
|
||||
{ value: '3', label: '岗位' },
|
||||
],
|
||||
//岗位
|
||||
childPost: [
|
||||
{ value: '3', label: '岗位' },
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户列表
|
||||
*/
|
||||
export const userColumns: BasicColumn[] = [
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'realname',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '手机',
|
||||
width: 150,
|
||||
dataIndex: 'phone',
|
||||
},
|
||||
{
|
||||
title: '主岗位',
|
||||
dataIndex: 'mainDepPostId_dictText',
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 职位改变事件
|
||||
* @param value
|
||||
* @param model
|
||||
* @param treeData
|
||||
*/
|
||||
export function positionChange(value, model, treeData) {
|
||||
if(value && model.parentId){
|
||||
getPositionByDepartId({ parentId: model.parentId, departId: model.id ? model.id:'', positionId: value }).then((res) =>{
|
||||
if(res.success){
|
||||
treeData.value = res.result;
|
||||
}else{
|
||||
treeData.value = [];
|
||||
$message.warning(res.message);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
treeData.value = [];
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<a-row :class="['p-4', `${prefixCls}--box`]" type="flex" :gutter="10">
|
||||
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<a-col :xl="10" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<DepartLeftTree ref="leftTree" @select="onTreeSelect" @rootTreeData="onRootTreeData" />
|
||||
</a-col>
|
||||
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<a-col :xl="14" :lg="24" :md="24" style="margin-bottom: 10px">
|
||||
<div style="height: 100%;" :class="[`${prefixCls}`]">
|
||||
<a-tabs v-show="departData != null" defaultActiveKey="base-info">
|
||||
<a-tab-pane tab="基本信息" key="base-info" forceRender style="position: relative">
|
||||
@ -16,6 +16,16 @@
|
||||
<DepartRuleTab :data="departData" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="职级汇报关系" key="rank">
|
||||
<div style="padding: 0 20px 20px">
|
||||
<DepartRankRelation :data="departData" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="用户列表" key="user">
|
||||
<div style="padding: 0 20px 20px">
|
||||
<DepartUserList :data="departData" :key="reRender"></DepartUserList>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<div v-show="departData == null" style="padding-top: 40px">
|
||||
<a-empty description="尚未选择部门" />
|
||||
@ -31,6 +41,8 @@
|
||||
import DepartLeftTree from './components/DepartLeftTree.vue';
|
||||
import DepartFormTab from './components/DepartFormTab.vue';
|
||||
import DepartRuleTab from './components/DepartRuleTab.vue';
|
||||
import DepartRankRelation from './components/DepartRankRelation.vue';
|
||||
import DepartUserList from './components/DepartUserList.vue';
|
||||
|
||||
const { prefixCls } = useDesign('depart-manage');
|
||||
provide('prefixCls', prefixCls);
|
||||
@ -41,10 +53,15 @@
|
||||
// 当前选中的部门信息
|
||||
const departData = ref({});
|
||||
const rootTreeData = ref<any[]>([]);
|
||||
const reRender = ref(-1);
|
||||
|
||||
// 左侧树选择后触发
|
||||
function onTreeSelect(data) {
|
||||
console.log('onTreeSelect: ', data);
|
||||
if (reRender.value == -1) {
|
||||
// 重新渲染组件
|
||||
reRender.value = Math.random();
|
||||
}
|
||||
departData.value = data;
|
||||
}
|
||||
|
||||
|
||||
@ -168,6 +168,8 @@ export function useBaseInfoForm(treeData: Ref<any[]>) {
|
||||
return '部门';
|
||||
} else if (val === '3') {
|
||||
return '岗位';
|
||||
} else if(val === '4'){
|
||||
return '子公司';
|
||||
}
|
||||
return val;
|
||||
},
|
||||
|
||||
@ -27,12 +27,17 @@
|
||||
setModalProps({ confirmLoading: false });
|
||||
isUpdate.value = !!data?.isUpdate;
|
||||
if (unref(isUpdate)) {
|
||||
const record = {...data.values}
|
||||
//表单赋值
|
||||
if (data.values.relationType == 'USER') {
|
||||
data.values.userCode = data.values.roleCode;
|
||||
if (record.relationType == 'USER') {
|
||||
record.userCode = record.roleCode;
|
||||
}
|
||||
//表单赋值
|
||||
if (record.relationType == 'DEFAULT') {
|
||||
record.roleCode = '';
|
||||
}
|
||||
await setFieldsValue({
|
||||
...data.values,
|
||||
...record,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -46,6 +51,9 @@
|
||||
if(values.relationType == 'USER'){
|
||||
values.roleCode = values.userCode;
|
||||
}
|
||||
if(values.relationType == 'DEFAULT'){
|
||||
values.roleCode = 'DEF_INDEX_ALL';
|
||||
}
|
||||
await saveOrUpdate(values, isUpdate.value);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
<a-tag color="#87d068" v-if="text == 1">启用</a-tag>
|
||||
</template>
|
||||
<template #relationType="{ text, record }">
|
||||
<span>{{ record.roleCode == 'DEF_INDEX_ALL' ? '--' : text }}</span>
|
||||
<span>{{ record.roleCode == 'DEF_INDEX_ALL' ? '全局默认' : text }}</span>
|
||||
</template>
|
||||
<template #roleCode="{ text, record }">
|
||||
<span>{{ record.roleCode == 'DEF_INDEX_ALL' ? '菜单默认首页' : text }}</span>
|
||||
|
||||
@ -335,7 +335,7 @@
|
||||
searchParams.realname = options[0].label;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function openSelectPerson(){
|
||||
openModal(true, {})
|
||||
}
|
||||
|
||||
@ -161,7 +161,7 @@ export function useSysMessage(setLocaleText) {
|
||||
}
|
||||
}
|
||||
return '去处理'
|
||||
}else{
|
||||
} else {
|
||||
return '查看详情'
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<a v-if="noticeFiles.length > 1" :href="downLoadFiles + '?id=' + noticeId + '&token=' + getToken()" target="_blank" style="margin: 15px 6px;color: #5ac0fa;">
|
||||
<download-outlined class="item-icon" style="margin-right: 5px" /><span>批量下载所有附件</span>
|
||||
</a>
|
||||
</template>
|
||||
</BasicModal>
|
||||
</template>
|
||||
@ -40,16 +43,25 @@
|
||||
import { DownloadOutlined, EyeOutlined, PaperClipOutlined } from '@ant-design/icons-vue';
|
||||
import { encryptByBase64 } from '@/utils/cipher';
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
import { getToken } from "@/utils/auth";
|
||||
const glob = useGlobSetting();
|
||||
// 获取props
|
||||
defineProps({
|
||||
frameSrc: propTypes.string.def(''),
|
||||
});
|
||||
/**
|
||||
* 下载文件路径
|
||||
*/
|
||||
const downLoadFiles = `${glob.domainUrl}/sys/annountCement/downLoadFiles`;
|
||||
|
||||
//附件内容
|
||||
const noticeFiles = ref([]);
|
||||
//数据ID
|
||||
const noticeId = ref('');
|
||||
//表单赋值
|
||||
const [registerModal] = useModalInner((data) => {
|
||||
noticeFiles.value = [];
|
||||
noticeId.value = data.record.id;
|
||||
if (data.record?.files && data.record?.files.length > 0) {
|
||||
noticeFiles.value = data.record.files.split(',').map((item) => {
|
||||
return {
|
||||
@ -96,8 +108,8 @@
|
||||
<style scoped lang="less">
|
||||
.print-btn {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
top: 80px;
|
||||
right: 40px;
|
||||
cursor: pointer;
|
||||
color: #a3a3a5;
|
||||
z-index: 999;
|
||||
|
||||
@ -52,6 +52,13 @@
|
||||
...data.record,
|
||||
});
|
||||
record.value = data.record;
|
||||
} else {
|
||||
// update-begin--author:liaozhiyang---date:20250807---for:【JHHB-128】转公告
|
||||
//表单赋值
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20250807---for:【JHHB-128】转公告
|
||||
}
|
||||
});
|
||||
//设置标题
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="system-notice" setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import NoticeModal from './NoticeModal.vue';
|
||||
@ -40,6 +40,9 @@
|
||||
import { columns, searchFormSchema } from './notice.data';
|
||||
import { getList, deleteNotice, batchDeleteNotice,editIzTop, getExportUrl, getImportUrl, doReleaseData, doReovkeData } from './notice.api';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { useAppStore } from '/@/store/modules/app';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const glob = useGlobSetting();
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
const [register, { openModal: openDetail }] = useModal();
|
||||
@ -71,9 +74,10 @@
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
function handleAdd(record = {}) {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
record,
|
||||
});
|
||||
}
|
||||
|
||||
@ -192,4 +196,15 @@
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// update-begin--author:liaozhiyang---date:20250807---for:【JHHB-128】转公告
|
||||
const params = appStore.getMessageHrefParams;
|
||||
if (params?.add) {
|
||||
delete params.add;
|
||||
handleAdd(params);
|
||||
appStore.setMessageHrefParams('');
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20250807---for:【JHHB-128】转公告
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -12,9 +12,7 @@ enum Api {
|
||||
releaseData = '/sys/annountCement/doReleaseData',
|
||||
reovkeData = '/sys/annountCement/doReovkeData',
|
||||
editIzTop = '/sys/annountCement/editIzTop',
|
||||
|
||||
addVisitsNum = '/sys/annountCement/addVisitsNumber',
|
||||
|
||||
tempList = '/sys/message/sysMessageTemplate/list',
|
||||
}
|
||||
|
||||
@ -88,12 +86,6 @@ export const addVisitsNum = (params) => defHttp.get({ url: Api.addVisitsNum, par
|
||||
* @param id
|
||||
*/
|
||||
export const queryById = (params) => defHttp.get({ url: Api.queryById, params }, { isTransformResponse: false });
|
||||
/**
|
||||
* 发起流程
|
||||
* import { startProcess } from '/@/api/common/api';
|
||||
* @param params
|
||||
*/
|
||||
export const startProcess = (params) => defHttp.post({ url: Api.startProcess, params }, { isTransformResponse: false });
|
||||
/**
|
||||
* 查询模板列表
|
||||
* @param params
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
}
|
||||
});
|
||||
//设置标题
|
||||
const getTitle = computed(() => (!unref(isUpdate) ? '新增职务' : '编辑职务'));
|
||||
const getTitle = computed(() => (!unref(isUpdate) ? '新增职务级别' : '编辑职务级别'));
|
||||
//表单提交事件
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
@ -51,3 +51,8 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-input-number){
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
133
jeecgboot-vue3/src/views/system/position/TenantPositionList.vue
Normal file
133
jeecgboot-vue3/src/views/system/position/TenantPositionList.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleAdd">新增</a-button>
|
||||
<a-dropdown v-if="selectedRowKeys.length > 0">
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="1" @click="batchHandleDelete">
|
||||
<Icon icon="ant-design:delete-outlined"></Icon>
|
||||
删除
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button
|
||||
>批量操作
|
||||
<Icon icon="ant-design:down-outlined"></Icon>
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getActions(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<PositionModal @register="registerModal" @success="reload" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="TenantPositionList" setup>
|
||||
import { onMounted } from 'vue';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { getPositionList, deletePosition, batchDeletePosition, getExportUrl, getImportUrl } from './position.api';
|
||||
import { columns, searchFormSchema } from './position.data';
|
||||
import PositionModal from './PositionModal.vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { tenantSaasMessage } from '@/utils/common/compUtils';
|
||||
import { getTenantId } from '@/utils/auth';
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { onExportXls, onImportXls, tableContext } = useListPage({
|
||||
tableProps: {
|
||||
title: '租户职务列表',
|
||||
api: getPositionList,
|
||||
columns: columns,
|
||||
formConfig: {
|
||||
schemas: searchFormSchema,
|
||||
},
|
||||
actionColumn: {
|
||||
width: 180,
|
||||
},
|
||||
showIndexColumn: true,
|
||||
defSort: {
|
||||
column: '',
|
||||
order: '',
|
||||
},
|
||||
beforeFetch(params) {
|
||||
return Object.assign({ tenantId: getTenantId() }, params);
|
||||
},
|
||||
},
|
||||
exportConfig: {
|
||||
name: '租户职务列表',
|
||||
url: getExportUrl,
|
||||
},
|
||||
importConfig: {
|
||||
url: getImportUrl,
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
/**
|
||||
* 操作列定义
|
||||
* @param record
|
||||
*/
|
||||
function getActions(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
popConfirm: {
|
||||
title: '是否确认删除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增事件
|
||||
*/
|
||||
function handleAdd() {
|
||||
openModal(true, {
|
||||
isUpdate: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑事件
|
||||
*/
|
||||
function handleEdit(record) {
|
||||
openModal(true, {
|
||||
record,
|
||||
isUpdate: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除事件
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
await deletePosition({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除事件
|
||||
*/
|
||||
async function batchHandleDelete() {
|
||||
await batchDeletePosition({ ids: selectedRowKeys.value }, () => {
|
||||
selectedRowKeys.value = [];
|
||||
reload();
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
//提示信息
|
||||
tenantSaasMessage('租户职务');
|
||||
});
|
||||
</script>
|
||||
@ -53,6 +53,10 @@
|
||||
width: 180,
|
||||
},
|
||||
showIndexColumn: true,
|
||||
defSort: {
|
||||
column: "",
|
||||
order: ""
|
||||
}
|
||||
},
|
||||
exportConfig: {
|
||||
name: '职务列表',
|
||||
|
||||
@ -9,22 +9,21 @@ export const columns: BasicColumn[] = [
|
||||
// align: 'left',
|
||||
// },
|
||||
{
|
||||
title: '职务名称',
|
||||
title: '职务级别名称',
|
||||
dataIndex: 'name',
|
||||
align: 'left'
|
||||
// width: 200,
|
||||
},
|
||||
// {
|
||||
// title: '职务等级',
|
||||
// dataIndex: 'postRank_dictText',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: '职务级别(越小级别越高)',
|
||||
dataIndex: 'postLevel',
|
||||
},
|
||||
];
|
||||
|
||||
export const searchFormSchema: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
label: '职务名称',
|
||||
label: '职务级别名称',
|
||||
component: 'Input',
|
||||
colProps: { span: 8 },
|
||||
},
|
||||
@ -37,25 +36,25 @@ export const formSchema: FormSchema[] = [
|
||||
component: 'Input',
|
||||
show: false,
|
||||
},
|
||||
// {
|
||||
// label: '职级',
|
||||
// field: 'postRank',
|
||||
// component: 'JDictSelectTag',
|
||||
// required: true,
|
||||
// componentProps: {
|
||||
// dictCode: 'position_rank',
|
||||
// dropdownStyle: {
|
||||
// maxHeight: '100vh',
|
||||
// },
|
||||
// getPopupContainer: () => document.body,
|
||||
// },
|
||||
// },
|
||||
{
|
||||
field: 'name',
|
||||
label: '职务名称',
|
||||
label: '职务级别名称',
|
||||
component: 'Input',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: '职务级别',
|
||||
field: 'postLevel',
|
||||
component: 'InputNumber',
|
||||
required: true,
|
||||
componentProps: {
|
||||
min: 1,
|
||||
max: 99
|
||||
},
|
||||
dynamicRules: ({ model, schema }) => {
|
||||
return [{ required: true, message: '请输入职务级别!' }];
|
||||
},
|
||||
},
|
||||
// {
|
||||
// field: 'code',
|
||||
// label: '职务编码',
|
||||
|
||||
@ -3,7 +3,10 @@
|
||||
<template #title>
|
||||
角色权限配置
|
||||
<a-dropdown>
|
||||
<Icon icon="ant-design:more-outlined" class="more-icon" />
|
||||
<a-button class="more-icon">
|
||||
更多操作
|
||||
<Icon icon="ant-design:down-outlined" size="14px" style="position: relative;top: 1px;right: 5px"></Icon>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="treeMenuClick">
|
||||
<a-menu-item key="checkAll">选择全部</a-menu-item>
|
||||
@ -294,9 +297,9 @@
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.more-icon {
|
||||
font-size: 20px !important;
|
||||
/* font-size: 20px !important;
|
||||
color: black;
|
||||
display: inline-flex;
|
||||
display: inline-flex;*/
|
||||
float: right;
|
||||
margin-right: 2px;
|
||||
cursor: pointer;
|
||||
|
||||
@ -57,7 +57,14 @@ export const searchUserFormSchema: FormSchema[] = [
|
||||
field: 'username',
|
||||
label: '用户账号',
|
||||
component: 'Input',
|
||||
colProps: { span: 12 },
|
||||
colProps: { span: 8 },
|
||||
labelWidth: 74,
|
||||
},
|
||||
{
|
||||
field: 'realname',
|
||||
label: '用户名称',
|
||||
component: 'Input',
|
||||
colProps: { span: 8 },
|
||||
labelWidth: 74,
|
||||
},
|
||||
];
|
||||
|
||||
@ -4,10 +4,20 @@
|
||||
<BasicTable @register="registerTable" :rowSelection="rowSelection">
|
||||
<!--插槽:table标题-->
|
||||
<template #tableTitle>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增</a-button>
|
||||
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate">新增</a-button>
|
||||
<a-button
|
||||
preIcon="ant-design:user-add-outlined"
|
||||
type="primary"
|
||||
@click="handleInvitation"
|
||||
style="margin-right: 5px">
|
||||
邀请用户加入
|
||||
</a-button>
|
||||
<JThirdAppButton biz-type="user" :selected-row-keys="selectedRowKeys" syncToApp syncToLocal @sync-finally="onSyncFinally" />
|
||||
<a-button type="primary" @click="openQuitModal(true, {})" preIcon="ant-design:user-delete-outlined">离职信息</a-button>
|
||||
<a-button type="primary" @click="openQuitModal(true, {})" preIcon="ant-design:user-delete-outlined">离职人员</a-button>
|
||||
<div style="margin-left: 10px;margin-top: 5px"> 当前登录租户: <span class="tenant-name">{{loginTenantName}}</span> </div>
|
||||
<a-tooltip title="租户用户更多操作说明">
|
||||
<a-icon type="question-circle" style="margin-left: 8px; cursor: pointer " @click="tipShow = true"/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
@ -16,12 +26,23 @@
|
||||
</BasicTable>
|
||||
<!--用户抽屉-->
|
||||
<TenantUserDrawer @register="registerDrawer" @success="handleSuccess" />
|
||||
<!-- 离职受理人弹窗 -->
|
||||
<UserQuitAgentModal @register="registerQuitAgentModal" @success="handleQuitSuccess" />
|
||||
<!-- 离职人员列弹窗 -->
|
||||
<UserQuitModal @register="registerQuitModal" @success="reload" />
|
||||
<!-- 变更拥有者弹窗 -->
|
||||
<UserSelectModal @register="registerUserModal" :excludeUserIdList="excludeUserIdList" :maxSelectCount="1" @getSelectResult="selectResult" />
|
||||
<!-- 套餐分配弹窗 -->
|
||||
<TenantPackAllotModal @register="registerPackAllotModal"></TenantPackAllotModal>
|
||||
<!-- 邀请人弹窗 -->
|
||||
<TenantInviteUserModal @register="registerSelUserModal" @inviteOk="handleInviteUserOk" />
|
||||
<a-modal v-model:open="tipShow" :footer="null" title="租户用户更多操作说明" :width="800">
|
||||
<ul class="user-tenant-tip">
|
||||
<li>移除:将用户从当前租户中移除</li>
|
||||
<li>删除:仅可删除当天创建的用户,删除后可在系统用户回收站恢复</li>
|
||||
<li>离职:非租户创建者可进行离职操作,离职员工可在离职人员列表查看</li>
|
||||
<li>交接:租户创建者可进行租户交接,交接后员工信息可在离职人员列表查看</li>
|
||||
</ul>
|
||||
<div style="height: 10px"></div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -31,14 +52,13 @@
|
||||
import { BasicTable, TableAction, ActionItem } from '/@/components/Table';
|
||||
import UserDrawer from '../user/UserDrawer.vue';
|
||||
import JThirdAppButton from '/@/components/jeecg/thirdApp/JThirdAppButton.vue';
|
||||
import UserQuitAgentModal from '../user/UserQuitAgentModal.vue';
|
||||
import UserQuitModal from '../user/UserQuitModal.vue';
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { columns, searchFormSchema } from '../user/user.data';
|
||||
import { list , deleteUser, batchDeleteUser, getImportUrl, getExportUrl, frozenBatch , getUserTenantPageList, updateUserTenantStatus } from '../user/user.api';
|
||||
import { list , deleteUser, batchDeleteUser, getImportUrl, getExportUrl, frozenBatch, getUserTenantPageList, updateUserTenantStatus } from '../user/user.api';
|
||||
// import { usePermission } from '/@/hooks/web/usePermission'
|
||||
// const { hasPermission } = usePermission();
|
||||
import { userTenantColumns, userTenantFormSchema } from '../user/user.data';
|
||||
@ -46,9 +66,11 @@
|
||||
import UserSelectModal from '/@/components/Form/src/jeecg/components/modal/UserSelectModal.vue';
|
||||
import { getTenantId } from "/@/utils/auth";
|
||||
import { changeOwenUserTenant } from "/@/views/system/usersetting/UserSetting.api";
|
||||
import { getLoginTenantName } from "/@/views/system/tenant/tenant.api";
|
||||
import {getLoginTenantName, invitationUserJoin, leaveTenant} from "/@/views/system/tenant/tenant.api";
|
||||
import TenantUserDrawer from './components/TenantUserDrawer.vue';
|
||||
import { tenantSaasMessage } from "@/utils/common/compUtils";
|
||||
import { sameDay, tenantSaasMessage } from "@/utils/common/compUtils";
|
||||
import TenantPackAllotModal from './components/TenantPackAllotModal.vue'
|
||||
import TenantInviteUserModal from "@/views/system/tenant/components/TenantInviteUserModal.vue";
|
||||
|
||||
const { createMessage, createConfirm } = useMessage();
|
||||
|
||||
@ -58,8 +80,12 @@
|
||||
const [registerQuitAgentModal, { openModal: openQuitAgentModal }] = useModal();
|
||||
//离职用户列表model
|
||||
const [registerQuitModal, { openModal: openQuitModal }] = useModal();
|
||||
//分配套餐弹窗
|
||||
const [registerPackAllotModal, { openModal: openPackAllotModal }] = useModal();
|
||||
const userStore = useUserStore();
|
||||
const createBy = userStore.getUserInfo.username;
|
||||
//弹窗提示显示
|
||||
const tipShow = ref<boolean>(false);
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext, onExportXls, onImportXls } = useListPage({
|
||||
@ -153,22 +179,23 @@
|
||||
function getDropDownAction(record): ActionItem[] {
|
||||
return [
|
||||
{
|
||||
label: '详情',
|
||||
label: '查看详情',
|
||||
onClick: handleDetail.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '离职',
|
||||
//update-begin---author:wangshuai---date:2023-10-25---for:【QQYUN-6822】9.离职交接人选的是自己,完成之后数据没了---
|
||||
onClick: handleQuit.bind(null,record.username, record.id),
|
||||
//update-end---author:wangshuai---date:2023-10-25---for:【QQYUN-6822】9.离职交接人选的是自己,完成之后数据没了---
|
||||
//update-begin---author:wangshuai ---date:20230130 for:[QQYUN-3974]租户的创建人 不应该有离职按钮------------
|
||||
ifShow: () =>{
|
||||
return record.status === '1' && record.username!== record.createBy;
|
||||
}
|
||||
//update-end---author:wangshuai ---date:20230130 for:[QQYUN-3974]租户的创建人 不应该有离职按钮------------
|
||||
label: '移除用户',
|
||||
onClick: handleLeave.bind(null, record.id),
|
||||
},
|
||||
{
|
||||
label: '交接',
|
||||
label: '删除用户',
|
||||
popConfirm: {
|
||||
title: '是否确认删除该用户',
|
||||
confirm: handleDeleteUser.bind(null, record),
|
||||
},
|
||||
ifShow: () => record.username!== userStore.getUserInfo?.username && sameDay(record.createTime),
|
||||
},
|
||||
{
|
||||
label: '变更拥有者',
|
||||
onClick: handleHandover.bind(null, record),
|
||||
ifShow: () =>{
|
||||
return record.username === record.createBy;
|
||||
@ -191,19 +218,13 @@
|
||||
return record.status === '3' && record.createBy === createBy;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '用户套餐',
|
||||
onClick: handleAllotPack.bind(null, record),
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 离职
|
||||
* @param userName
|
||||
* @param userId
|
||||
*/
|
||||
function handleQuit(userName, userId) {
|
||||
//打开离职代理人弹窗
|
||||
openQuitAgentModal(true, { userName, userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户租户状态
|
||||
* @param id
|
||||
@ -228,6 +249,8 @@
|
||||
const excludeUserIdList = ref<any>([]);
|
||||
//离职代理人model
|
||||
const [registerUserModal, { openModal: openUserModal }] = useModal();
|
||||
//邀请用户加入弹窗
|
||||
const [registerSelUserModal, { openModal: userOpenModal }] = useModal();
|
||||
const handOverUserName = ref<string>('');
|
||||
|
||||
/**
|
||||
@ -277,21 +300,59 @@
|
||||
loginTenantName.value = await getLoginTenantName();
|
||||
}
|
||||
//update-end---author:wangshuai ---date:20230710 for:【QQYUN-5723】4、显示当前登录租户------------
|
||||
|
||||
|
||||
/**
|
||||
* 离职成功之后需要判断一下是否为当前用户,当前用户需要刷新浏览器
|
||||
* @param userName
|
||||
* 分配套餐
|
||||
*
|
||||
* @param record
|
||||
*/
|
||||
function handleQuitSuccess(userName) {
|
||||
//判断如果当前离职的是当前登录用户,需要刷新页面,便将租户id设置成null
|
||||
let username = userStore.getUserInfo.username;
|
||||
if (username && userName === username) {
|
||||
userStore.setTenant(null);
|
||||
window.location.reload();
|
||||
}else{
|
||||
function handleAllotPack(record) {
|
||||
openPackAllotModal(true,{
|
||||
record
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
function handleDeleteUser(record) {
|
||||
deleteUser({ id: record.id }, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 邀请用户加入租户
|
||||
*/
|
||||
function handleInvitation() {
|
||||
userOpenModal(true, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户选择回调事件
|
||||
* @param username
|
||||
* @param phone
|
||||
* @param userSelectId
|
||||
*/
|
||||
async function handleInviteUserOk(phone, username) {
|
||||
let tId = getTenantId();
|
||||
if (phone) {
|
||||
await invitationUserJoin({ ids: tId, phone: phone });
|
||||
reload();
|
||||
}
|
||||
if (username) {
|
||||
await invitationUserJoin({ ids: tId, username: username });
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 请离
|
||||
* @param id
|
||||
*/
|
||||
async function handleLeave(id) {
|
||||
await leaveTenant({ userIds: id, tenantId: getTenantId() }, reload)
|
||||
}
|
||||
|
||||
|
||||
onMounted(()=>{
|
||||
tenantSaasMessage('租户用户')
|
||||
@ -304,4 +365,11 @@
|
||||
margin: 5px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.user-tenant-tip{
|
||||
margin: 20px;
|
||||
background-color: #f8f9fb;
|
||||
color: #99a1a9;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -0,0 +1,77 @@
|
||||
<!-- 用户套餐分配页面 -->
|
||||
<template>
|
||||
<BasicModal @register="registerModal" :width="500" title="分配套餐" @ok="handleSubmit">
|
||||
<BasicForm @register="registerForm">
|
||||
<template #packId="{ model, field }">
|
||||
<a-checkbox-group v-model:value="model[field]" :options="packOption"></a-checkbox-group>
|
||||
</template>
|
||||
</BasicForm>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { BasicModal, useModalInner } from '@/components/Modal';
|
||||
import { BasicForm, useForm } from '@/components/Form';
|
||||
import { packUserAllotSchemas } from '@/views/system/tenant/tenant.data';
|
||||
import { listPackByTenantUserId } from '@/views/system/tenant/tenant.api';
|
||||
import { getTenantId } from '@/utils/auth';
|
||||
import { ref, defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TenantCurrentPackList',
|
||||
components: {
|
||||
BasicModal,
|
||||
BasicForm,
|
||||
},
|
||||
setup() {
|
||||
//产品包的option
|
||||
const packOption = ref<any>([]);
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, setProps }] = useForm({
|
||||
schemas: packUserAllotSchemas,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
setModalProps({ confirmLoading: false, showCancelBtn: false, showOkBtn: false });
|
||||
//重置表单
|
||||
await resetFields();
|
||||
let tenantId = getTenantId();
|
||||
let result = await listPackByTenantUserId({ tenantId: tenantId, userId: data.record.id });
|
||||
if (result) {
|
||||
if (result.packList) {
|
||||
for (const item of result.packList) {
|
||||
if (item.packCode) {
|
||||
item.label = item.packName + "(默认产品包)";
|
||||
} else {
|
||||
item.label = item.packName;
|
||||
}
|
||||
item.value = item.id;
|
||||
item.key = item.id;
|
||||
}
|
||||
packOption.value = result.packList;
|
||||
} else {
|
||||
packOption.value = [];
|
||||
}
|
||||
if(result.userPackIdList){
|
||||
data.record.packId = result.userPackIdList;
|
||||
}
|
||||
}
|
||||
setModalProps({ confirmLoading: false });
|
||||
await setFieldsValue({
|
||||
...data.record,
|
||||
userId: data.record.id,
|
||||
});
|
||||
setProps({ disabled: true });
|
||||
})
|
||||
|
||||
return {
|
||||
registerForm,
|
||||
registerModal,
|
||||
packOption,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<BasicTable @register="registerTable" style="padding-top: 10px">
|
||||
<template #departNames="{ text, record }">
|
||||
<template v-if="text && text.length > 0">
|
||||
{{ getName(text) }}
|
||||
</template>
|
||||
</template>
|
||||
<template #positionNames="{ text, record }">
|
||||
<template v-if="text && text.length > 0">
|
||||
{{ getName(text) }}
|
||||
</template>
|
||||
</template>
|
||||
<template #tableTitle>
|
||||
<a-button preIcon="ant-design:usergroup-add-outlined" type="primary" @click="addUser" :disabled="tenantPackData.izSysn == '1'">邀请成员</a-button>
|
||||
<a-button preIcon="ant-design:rollback-outlined" @click="cancel">关闭</a-button>
|
||||
</template>
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
<tenant-user-select-modal :multi="true" @register="registerUserModal" @on-select="onSelected" :tenantId="getTenantId"></tenant-user-select-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, defineEmits } from 'vue';
|
||||
import { BasicModal } from '@/components/Modal';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import TenantUserSelectModal from '@/views/system/tenant/components/TenantUserSelectModal.vue';
|
||||
import { addTenantPackUser, deleteTenantPackUser } from '@/views/system/tenant/tenant.api';
|
||||
//注册table数据
|
||||
import { useListPage } from '@/hooks/system/useListPage';
|
||||
import { queryTenantPackUserList } from '@/views/system/tenant/tenant.api';
|
||||
import { tenantPackUserColumns } from '@/views/system/tenant/tenant.data';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import { computed, reactive } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TenantUserRightList',
|
||||
components: { BasicModal, BasicTable, TableAction, TenantUserSelectModal },
|
||||
emits: ['cancel'],
|
||||
setup(props, { emit }) {
|
||||
const [registerUserModal, { openModal: openUserModal, closeModal: closeUserModal }] = useModal();
|
||||
//获取租户id
|
||||
const getTenantId = computed(() => {
|
||||
return tenantPackData.tenantId;
|
||||
});
|
||||
|
||||
//套餐包信息
|
||||
const tenantPackData = reactive<any>({});
|
||||
const { tableContext } = useListPage({
|
||||
tableProps: {
|
||||
api: queryTenantPackUserList,
|
||||
immediate: false,
|
||||
columns: tenantPackUserColumns,
|
||||
canResize: false,
|
||||
useSearchForm: false,
|
||||
beforeFetch: (params) => {
|
||||
params.tenantId = tenantPackData.tenantId;
|
||||
params.packId = tenantPackData.id;
|
||||
params.status = 1;
|
||||
return params;
|
||||
},
|
||||
actionColumn: {
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys }] = tableContext;
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
* @param value
|
||||
*/
|
||||
function initData(value) {
|
||||
console.log('value::', value);
|
||||
Object.assign(tenantPackData, value);
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格操作列
|
||||
* @param record
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '移除',
|
||||
popConfirm: {
|
||||
title: '是否确认移除',
|
||||
confirm: handleDelete.bind(null, record),
|
||||
},
|
||||
ifShow:() => tenantPackData.izSysn != '1'
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
async function handleDelete(record) {
|
||||
let params = {
|
||||
packId: record.packId,
|
||||
packName: record.packName,
|
||||
tenantId: tenantPackData.tenantId,
|
||||
userId: record.id,
|
||||
realname: record.realname,
|
||||
};
|
||||
await deleteTenantPackUser(params);
|
||||
await reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户弹窗
|
||||
*/
|
||||
function addUser() {
|
||||
openUserModal(true, {
|
||||
list: [],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 邀请人回调事件
|
||||
* @param arr
|
||||
*/
|
||||
async function onSelected(arr) {
|
||||
if (arr && arr.length > 0) {
|
||||
let names: any[] = [];
|
||||
let ids: any[] = [];
|
||||
for (let u of arr) {
|
||||
names.push(u.realname);
|
||||
ids.push(u.id);
|
||||
}
|
||||
let params = {
|
||||
packId: tenantPackData.id,
|
||||
packName: tenantPackData.packName,
|
||||
tenantId: tenantPackData.tenantId,
|
||||
userId: ids.join(','),
|
||||
realname: names.join(','),
|
||||
};
|
||||
await addTenantPackUser(params);
|
||||
await reload();
|
||||
closeUserModal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门/职务名称
|
||||
* @param value
|
||||
*/
|
||||
function getName(value) {
|
||||
return value.join(',');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭
|
||||
*/
|
||||
function cancel() {
|
||||
emit('cancel');
|
||||
}
|
||||
|
||||
return {
|
||||
getTableAction,
|
||||
onSelected,
|
||||
registerTable,
|
||||
registerUserModal,
|
||||
getTenantId,
|
||||
rowSelection,
|
||||
addUser,
|
||||
initData,
|
||||
getName,
|
||||
cancel,
|
||||
tenantPackData,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
296
jeecgboot-vue3/src/views/system/tenant/my/MyTenantDetail.vue
Normal file
296
jeecgboot-vue3/src/views/system/tenant/my/MyTenantDetail.vue
Normal file
@ -0,0 +1,296 @@
|
||||
<!--我的租户详情-->
|
||||
<template>
|
||||
<div class="message-set-container">
|
||||
<div class="message-set-box">
|
||||
<div class="message-set-header">
|
||||
<span class="font17">组织信息</span>
|
||||
</div>
|
||||
<a-form :model="formState">
|
||||
<div class="message-set-content">
|
||||
<div class="common-info">
|
||||
<div class="common-info-row">
|
||||
<div class="common-info-row-label">组织LOGO</div>
|
||||
<div class="common-info-row-content">
|
||||
<img :src="getImageSrc()" style="width: 100px;cursor: pointer" @click="previewImage">
|
||||
</div>
|
||||
</div>
|
||||
<div class="common-info-row m-top24">
|
||||
<div class="common-info-row-label">组织名称</div>
|
||||
<span class="m-right16">{{ formState.name }}</span>
|
||||
</div>
|
||||
<div class="common-info-row m-top24">
|
||||
<div class="common-info-row-label">组织门牌号</div>
|
||||
<div class="common-info-row-content">
|
||||
<span class="pointer">
|
||||
<span>{{ formState.houseNumber }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="common-info-row m-top24">
|
||||
<div class="common-info-row-label">组织编号(ID)</div>
|
||||
<div class="common-info-row-content">
|
||||
<span class="pointer">
|
||||
<span>{{ formState.id }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="split-line"></div>
|
||||
<div class="common-info-row">
|
||||
<div class="common-info-row-label">所在地</div>
|
||||
<span class="m-right16">{{ formState.companyAddress_dictText }}</span>
|
||||
</div>
|
||||
<div class="common-info-row m-top24">
|
||||
<div class="common-info-row-label">所在行业</div>
|
||||
<span class="m-right16">{{ formState.trade_dictText }}</span>
|
||||
</div>
|
||||
<div class="common-info-row m-top24">
|
||||
<div class="common-info-row-label">工作地点</div>
|
||||
<span class="m-right16">{{ formState.workPlace }}</span>
|
||||
</div>
|
||||
<div class="cancel-split-line"></div>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" name="tenant-my-tenant-list" setup>
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import {getFileAccessHttpUrl, tenantSaasMessage} from '@/utils/common/compUtils';
|
||||
import { getTenantById } from '@/views/system/tenant/tenant.api';
|
||||
import { getTenantId } from '@/utils/auth';
|
||||
import { getDataByCode, getRealCode, provinceOptions } from '@/components/Form/src/utils/areaDataUtil';
|
||||
import { initDictOptions } from '@/utils/dict';
|
||||
import {createImgPreview} from "@/components/Preview";
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
const formState = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
houseNumber: '',
|
||||
companyAddress_dictText: '',
|
||||
trade_dictText: '',
|
||||
workPlace: '',
|
||||
createBy: '',
|
||||
companyLogo: '',
|
||||
});
|
||||
let tradeOptions: any[] = [];
|
||||
|
||||
/**
|
||||
* 初始化租户信息
|
||||
*/
|
||||
async function initTenant() {
|
||||
let result = await getTenantById({ id: getTenantId() });
|
||||
if (result) {
|
||||
if (result.companyAddress) {
|
||||
formState.companyAddress_dictText = getPcaText(result.companyAddress);
|
||||
} else {
|
||||
formState.companyAddress_dictText = '';
|
||||
}
|
||||
if (result.trade) {
|
||||
formState.trade_dictText = await getTradeText(result.trade);
|
||||
} else {
|
||||
formState.trade_dictText = '';
|
||||
}
|
||||
Object.assign(formState, result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取省市区文本
|
||||
* @param code
|
||||
*/
|
||||
function getPcaText(code) {
|
||||
let arr = getRealCode(code, 3);
|
||||
console.log("arr:::",arr)
|
||||
let provinces: any = provinceOptions.filter((item) => item.value == arr[0]);
|
||||
let cities: any[] = getDataByCode(arr[0]);
|
||||
let areas: any[] = getDataByCode(arr[1]);
|
||||
let str = '';
|
||||
if (provinces && provinces.length > 0) {
|
||||
str = provinces[0].label;
|
||||
if (cities && cities.length > 0) {
|
||||
let temp1 = cities.filter((item) => item.value == arr[1]);
|
||||
str = str + '/' + temp1[0].label;
|
||||
if (areas && areas.length > 0) {
|
||||
let temp2 = areas.filter((item) => item.value == arr[2]);
|
||||
str = str + '/' + temp2[0].label;
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取行业文本
|
||||
*
|
||||
* @param trade
|
||||
*/
|
||||
async function getTradeText(trade) {
|
||||
if (tradeOptions.length == 0) {
|
||||
let options: any = await initDictOptions('trade');
|
||||
tradeOptions = options;
|
||||
}
|
||||
let arr = tradeOptions.filter((item) => item.value == trade);
|
||||
if (arr.length > 0) {
|
||||
return arr[0].label;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片路径
|
||||
*/
|
||||
function getImageSrc() {
|
||||
return getFileAccessHttpUrl(formState.companyLogo) || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览图片
|
||||
*/
|
||||
function previewImage() {
|
||||
let fileAccessHttpUrl = getFileAccessHttpUrl(formState.companyLogo);
|
||||
createImgPreview({ imageList: [fileAccessHttpUrl], defaultWidth: 700, rememberState: true });
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
//提示信息
|
||||
tenantSaasMessage('我的租户');
|
||||
initTenant();
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.message-set-container {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
margin: 16px;
|
||||
min-height: 0;
|
||||
}
|
||||
.message-set-box {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 750px;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
.message-set-header {
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
box-sizing: border-box;
|
||||
color: #333;
|
||||
display: flex;
|
||||
font-weight: 600;
|
||||
height: 57px;
|
||||
line-height: 57px;
|
||||
padding: 0 18px 0 24px;
|
||||
}
|
||||
.message-set-content {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.font17 {
|
||||
font-size: 17px;
|
||||
}
|
||||
.common-info {
|
||||
padding: 20px 24px;
|
||||
background: #ffffff;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.common-info-row {
|
||||
color: #333;
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
}
|
||||
.common-info-row-label {
|
||||
color: #757575;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: 140px;
|
||||
}
|
||||
.common-info-row-content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.delete-color {
|
||||
color: #f51744;
|
||||
cursor: pointer;
|
||||
}
|
||||
.set-describe {
|
||||
color: #757575;
|
||||
margin-top: 10px !important;
|
||||
}
|
||||
.m-top24 {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.edit-name {
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
color: #1e88e5;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
outline: none;
|
||||
text-shadow: none;
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.m-right16 {
|
||||
margin-right: 16px;
|
||||
}
|
||||
.split-line {
|
||||
background: #eaeaea;
|
||||
height: 1px;
|
||||
margin: 40px 0;
|
||||
width: 100%;
|
||||
}
|
||||
.cancel-split-line {
|
||||
background: #eaeaea;
|
||||
height: 1px;
|
||||
margin: 40px 0 20px;
|
||||
width: 100%;
|
||||
}
|
||||
.form-item-padding {
|
||||
padding: 0 24px 22px;
|
||||
}
|
||||
.form-group {
|
||||
display: table;
|
||||
font-size: 13px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.form-label {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
line-height: 29px;
|
||||
}
|
||||
.txt-middle {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
}
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
.domain-background {
|
||||
height: 56px;
|
||||
margin-top: 6px;
|
||||
width: 100px;
|
||||
margin-left: 142px;
|
||||
}
|
||||
.cancellation {
|
||||
color: #333333;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
@ -10,14 +10,14 @@
|
||||
:disabled="selectedRowKeys.length === 0"
|
||||
>邀请用户加入</a-button
|
||||
>
|
||||
<a-button
|
||||
<!-- <a-button
|
||||
preIcon="ant-design:plus-outlined"
|
||||
type="primary"
|
||||
@click="handlePack"
|
||||
style="margin-right: 5px"
|
||||
:disabled="selectedRowKeys.length === 0"
|
||||
>套餐</a-button
|
||||
>
|
||||
>-->
|
||||
</template>
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getActions(record)" />
|
||||
|
||||
@ -0,0 +1,188 @@
|
||||
<!-- 当前租户下套餐 -->
|
||||
<template>
|
||||
<div style="display: flex; width: 100%">
|
||||
<div :style="leftStyle">
|
||||
<BasicTable @register="registerTable">
|
||||
<template #tableTitle>
|
||||
<div style="margin-left: 10px; margin-top: 5px"
|
||||
>当前登录租户: <span class="tenant-name">{{ loginTenantName }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)" />
|
||||
</template>
|
||||
</BasicTable>
|
||||
</div>
|
||||
<div v-if="showRight" style="width: 49%; transition: width 0.3s">
|
||||
<TenantUserRightList ref="rightListRef" @cancel="handleCancel"></TenantUserRightList>
|
||||
</div>
|
||||
</div>
|
||||
<TenantPackMenuModal @register="registerPackMenu" @success="success" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref, unref, defineComponent, defineEmits } from 'vue';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { packFormSchema, tenantPackColumns } from '../tenant.data';
|
||||
import { getLoginTenantName, packList } from '../tenant.api';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { BasicTable, TableAction } from '/@/components/Table';
|
||||
import TenantPackMenuModal from './TenantPackMenuModal.vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { getTenantId } from '/@/utils/auth';
|
||||
import TenantUserRightList from '../components/TenantUserRightList.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TenantCurrentPackList',
|
||||
components: {
|
||||
TenantUserRightList,
|
||||
BasicTable,
|
||||
TableAction,
|
||||
TenantPackMenuModal,
|
||||
},
|
||||
setup() {
|
||||
const [registerPackMenu, { openModal }] = useModal();
|
||||
const [registerPackUser, { openModal: packUserOpenModal }] = useModal();
|
||||
|
||||
const tenantId = ref<number>();
|
||||
const rightListRef = ref<any>();
|
||||
//是否显示右侧用户列表
|
||||
const showRight = ref<any>();
|
||||
//左侧样式
|
||||
const leftStyle = ref<any>({
|
||||
width: '100%',
|
||||
transition: 'width 0.3s',
|
||||
});
|
||||
|
||||
// 列表页面公共参数、方法
|
||||
const { prefixCls, tableContext } = useListPage({
|
||||
designScope: 'tenant-template',
|
||||
tableProps: {
|
||||
api: packList,
|
||||
columns: tenantPackColumns,
|
||||
immediate: false,
|
||||
formConfig: {
|
||||
schemas: packFormSchema,
|
||||
labelCol: {
|
||||
xxl: 8,
|
||||
},
|
||||
actionColOptions: {
|
||||
xs: 24,
|
||||
sm: 8,
|
||||
md: 8,
|
||||
lg: 8,
|
||||
xl: 8,
|
||||
xxl: 8,
|
||||
},
|
||||
},
|
||||
beforeFetch: (params) => {
|
||||
return Object.assign(params, { tenantId: tenantId.value, status: '1', packType: 'custom' });
|
||||
},
|
||||
},
|
||||
});
|
||||
const [registerTable, { reload }, { rowSelection, selectedRowKeys, selectedRows }] = tableContext;
|
||||
//当前登录的租户名称
|
||||
const loginTenantName = ref<string>('');
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
/**
|
||||
* 获取租户名称
|
||||
*/
|
||||
getTenantName();
|
||||
|
||||
async function getTenantName() {
|
||||
let id = getTenantId();
|
||||
if (id) {
|
||||
loginTenantName.value = await getLoginTenantName();
|
||||
tenantId.value = Number(id);
|
||||
await reload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格操作
|
||||
* @param record
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '用户',
|
||||
onClick: seeTenantPackUser.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '权限详情',
|
||||
onClick: handleDetail.bind(null, record),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
function success() {
|
||||
(selectedRowKeys.value = []) && reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 套餐包下面的用户
|
||||
* @param record
|
||||
*/
|
||||
function seeTenantPackUser(record) {
|
||||
showRight.value = true;
|
||||
leftStyle.value = {
|
||||
width: '50%',
|
||||
transition: 'width 0.3s',
|
||||
};
|
||||
setTimeout(() => {
|
||||
rightListRef.value.initData(record);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
* @param record
|
||||
*/
|
||||
function handleDetail(record) {
|
||||
openModal(true, {
|
||||
isUpdate: true,
|
||||
record: record,
|
||||
tenantId: unref(tenantId),
|
||||
packType: 'custom',
|
||||
showFooter: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭回调
|
||||
*/
|
||||
function handleCancel() {
|
||||
showRight.value = false;
|
||||
leftStyle.value = {
|
||||
width: '100%',
|
||||
transition: 'width 0.3s',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
registerPackMenu,
|
||||
success,
|
||||
getTableAction,
|
||||
registerTable,
|
||||
rowSelection,
|
||||
rightListRef,
|
||||
showRight,
|
||||
registerPackUser,
|
||||
loginTenantName,
|
||||
leftStyle,
|
||||
handleCancel,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.tenant-name {
|
||||
text-decoration: underline;
|
||||
margin: 5px;
|
||||
font-size: 15px;
|
||||
}
|
||||
</style>
|
||||
@ -137,6 +137,12 @@
|
||||
return;
|
||||
}
|
||||
//update-end---author:wangshuai ---date:20230222 for:系统默认套餐包不允许删除------------
|
||||
//update-begin---author:wangshuai---date:2025-09-03---for:默认套餐不允许删除---
|
||||
if(record.packCode && record.packCode.indexOf("default") != -1){
|
||||
createMessage.warning("默认套餐包不允许删除");
|
||||
return;
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-09-03---for:默认套餐不允许删除---
|
||||
await deleteTenantPack({ ids: record.id }, success);
|
||||
}
|
||||
|
||||
@ -151,6 +157,12 @@
|
||||
createMessage.warning("默认系统套餐包不允许删除");
|
||||
return;
|
||||
}
|
||||
//update-begin---author:wangshuai---date:2025-09-03---for:默认套餐不允许删除---
|
||||
if(value[i].packCode && value[i].packCode.indexOf("default") != -1){
|
||||
createMessage.warning("默认套餐包不允许删除");
|
||||
return;
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-09-03---for:默认套餐不允许删除---
|
||||
}
|
||||
}
|
||||
Modal.confirm({
|
||||
|
||||
@ -29,8 +29,10 @@ enum Api {
|
||||
getTenantPageListByUserId = '/sys/tenant/getTenantPageListByUserId',
|
||||
|
||||
//新增、编辑用户租户
|
||||
saveUser = '/sys/user/add',
|
||||
saveUser = '/sys/user/addTenantUser',
|
||||
editUser = '/sys/user/editTenantUser',
|
||||
//根据租户id和用户获取用户的产品包列表和当前用户下的产品包id
|
||||
listPackByTenantUserId = '/sys/tenant/listPackByTenantUserId',
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,7 +114,7 @@ export const getTenantUserList = (params) => {
|
||||
export const leaveTenant = (params, handleSuccess) => {
|
||||
Modal.confirm({
|
||||
title: '请离',
|
||||
content: '是否请离该用户',
|
||||
content: '是否将此用户请离当前租户',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
@ -253,3 +255,11 @@ export const saveOrUpdateTenantUser = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.editUser : Api.saveUser;
|
||||
return defHttp.post({ url: url, params },{ joinParamsToUrl: true });
|
||||
};
|
||||
/**
|
||||
* 根据租户id和用户获取用户的产品包列表和当前用户下的产品包id
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
export const listPackByTenantUserId = (params) => {
|
||||
return defHttp.get({ url: Api.listPackByTenantUserId, params });
|
||||
}
|
||||
|
||||
@ -147,12 +147,21 @@ export const formSchema: FormSchema[] = [
|
||||
}, {
|
||||
field: 'companyAddress',
|
||||
label: '公司地址',
|
||||
component: 'InputTextArea',
|
||||
component: 'JAreaSelect',
|
||||
componentProps: {
|
||||
placeholder: '请输入公司地址',
|
||||
rows: 4,
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'workPlace',
|
||||
label: '工作地点',
|
||||
component: 'InputTextArea',
|
||||
componentProps: {
|
||||
placeholder: '请输入工作地点',
|
||||
rows: 4,
|
||||
}
|
||||
},
|
||||
/* {
|
||||
field: 'beginDate',
|
||||
label: '开始时间',
|
||||
@ -257,6 +266,25 @@ export const packColumns: BasicColumn[] = [
|
||||
title: '套餐包名称',
|
||||
dataIndex: 'packName',
|
||||
width: 100,
|
||||
customRender: ( { record, text }) => {
|
||||
if(record.packCode && record.packCode.indexOf('default') != -1) {
|
||||
return text + '(默认产品包)';
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '是否自动分配用户',
|
||||
dataIndex: 'izSysn',
|
||||
width: 100,
|
||||
customRender: ( { text }) => {
|
||||
if(text === '1') {
|
||||
return '是';
|
||||
} else {
|
||||
return '否';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
@ -277,6 +305,39 @@ export const packColumns: BasicColumn[] = [
|
||||
},
|
||||
];
|
||||
|
||||
//套餐包列表
|
||||
export const tenantPackColumns: BasicColumn[] = [
|
||||
{
|
||||
title: '套餐包名称',
|
||||
dataIndex: 'packName',
|
||||
width: 100,
|
||||
customRender: ( { record, text }) => {
|
||||
if(record.packCode && record.packCode.indexOf('default') != -1) {
|
||||
return text + '(默认产品包)';
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '是否自动分配用户',
|
||||
dataIndex: 'izSysn',
|
||||
width: 100,
|
||||
customRender: ( { text }) => {
|
||||
if(text === '1') {
|
||||
return '是';
|
||||
} else {
|
||||
return '否';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '备注说明',
|
||||
dataIndex: 'remarks',
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
|
||||
//套餐包列表
|
||||
export const defalutPackColumns: BasicColumn[] = [
|
||||
{
|
||||
@ -337,9 +398,11 @@ export const packMenuFormSchema: FormSchema[] = [
|
||||
componentProps: {
|
||||
dict: 'sys_permission,name,id',
|
||||
pidField: 'parent_id',
|
||||
hasChildField:'is_leaf',
|
||||
multiple: true,
|
||||
treeCheckAble:true,
|
||||
treeCheckStrictly: true,
|
||||
converIsLeafVal: 0,
|
||||
getPopupContainer: () => document.body,
|
||||
},
|
||||
},
|
||||
@ -348,6 +411,19 @@ export const packMenuFormSchema: FormSchema[] = [
|
||||
label: '备注说明',
|
||||
component: 'InputTextArea',
|
||||
},
|
||||
{
|
||||
field: 'izSysn',
|
||||
label: '自动分配用户',
|
||||
component: 'Switch',
|
||||
componentProps: {
|
||||
checkedValue: "1",
|
||||
checkedChildren: '是',
|
||||
unCheckedValue: "0",
|
||||
unCheckedChildren: '否',
|
||||
},
|
||||
defaultValue: "1",
|
||||
helpMessage: "默认会自动分配给用户,个性高级套餐,需要租户管理员手工分配人员(拥有更灵活性权限控制)"
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: '开启状态',
|
||||
@ -366,6 +442,18 @@ export const packMenuFormSchema: FormSchema[] = [
|
||||
component: 'Input',
|
||||
show: false
|
||||
},
|
||||
{
|
||||
field: 'packCode',
|
||||
label: '产品包编码',
|
||||
component: 'Input',
|
||||
show: false
|
||||
},
|
||||
{
|
||||
field: 'packType',
|
||||
label: '产品包类型',
|
||||
component: 'Input',
|
||||
show: false
|
||||
},
|
||||
];
|
||||
|
||||
//回收站列表
|
||||
@ -479,7 +567,7 @@ export const tenantUserSchema: FormSchema[] = [
|
||||
},
|
||||
},
|
||||
{ field: 'selecteddeparts', label: '部门', component: 'JSelectDept', componentProps: { checkStrictly: true } },
|
||||
{
|
||||
/* {
|
||||
field: 'post',
|
||||
label: '职位',
|
||||
component: 'JSelectPosition',
|
||||
@ -489,9 +577,33 @@ export const tenantUserSchema: FormSchema[] = [
|
||||
label: '工号',
|
||||
component: 'Input',
|
||||
dynamicRules: ({ model, schema }) => {
|
||||
return [{ required: true, message: '请输入工号' }, { ...rules.duplicateCheckRule('sys_user', 'work_no', model, schema, false)[0] }];
|
||||
return [{ required: false, message: '请输入工号' }, { ...rules.duplicateCheckRule('sys_user', 'work_no', model, schema, false)[0] }];
|
||||
},
|
||||
},
|
||||
},*/
|
||||
{ field: 'relTenantIds', label: '租户', component: 'Input',show:false },
|
||||
{ field: 'selectedroles', label: '角色', component: 'Input',show:false },
|
||||
];
|
||||
|
||||
// 分配用户套餐
|
||||
export const packUserAllotSchemas: FormSchema[] = [
|
||||
{
|
||||
field: 'userId',
|
||||
label: '用户id',
|
||||
component: 'Input',
|
||||
show: false
|
||||
},
|
||||
{
|
||||
field: 'realname',
|
||||
label: '用户姓名',
|
||||
component: 'Input',
|
||||
componentProps:{
|
||||
readonly : true
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'packId',
|
||||
label: '套餐',
|
||||
component: 'Select',
|
||||
slot: 'packId'
|
||||
}
|
||||
];
|
||||
@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" @register="registerModal" :width="800" title="用户代理" @ok="handleSubmit" destroyOnClose>
|
||||
<BasicForm @register="registerForm" />
|
||||
<template #insertFooter>
|
||||
<Popconfirm title="确定删除当前配置的代理吗?" @confirm="handleDel">
|
||||
<a-button v-if="agentData.id"><Icon icon="ant-design:clear-outlined" />删除代理</a-button>
|
||||
</Popconfirm>
|
||||
</template>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, unref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { BasicForm, useForm } from '/@/components/Form/index';
|
||||
import { formAgentSchema } from './user.data';
|
||||
import { deleteAgent, getUserAgent, saveOrUpdateAgent } from './user.api';
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
// 声明Emits
|
||||
const emit = defineEmits(['success', 'register']);
|
||||
//表单配置
|
||||
const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
|
||||
schemas: formAgentSchema,
|
||||
showActionButtonGroup: false,
|
||||
});
|
||||
//表单数据
|
||||
const agentData = ref<any>({});
|
||||
//表单赋值
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
|
||||
//重置表单
|
||||
await resetFields();
|
||||
setModalProps({ confirmLoading: false });
|
||||
//查询获取表单数据
|
||||
const res = await getUserAgent({ userName: data.userName });
|
||||
data = res.result ? res.result : data;
|
||||
//代理数据赋值
|
||||
agentData.value = { ...data };
|
||||
//表单赋值
|
||||
await setFieldsValue({ ...data });
|
||||
});
|
||||
//表单提交事件
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const values = await validate();
|
||||
setModalProps({ confirmLoading: true });
|
||||
//提交表单
|
||||
await saveOrUpdateAgent(values);
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
//刷新列表
|
||||
emit('success');
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除代理
|
||||
*/
|
||||
async function handleDel() {
|
||||
const reload = async () => {
|
||||
await resetFields();
|
||||
await setFieldsValue({ userName: agentData.value.userName });
|
||||
//关闭弹窗
|
||||
closeModal();
|
||||
emit('success');
|
||||
};
|
||||
if (agentData.value.id) {
|
||||
await deleteAgent({ id: agentData.value.id }, reload);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -139,6 +139,26 @@
|
||||
//update-begin-author:taoyan date:2022-5-24 for: VUEN-1117【issue】0523周开源问题
|
||||
setProps({ disabled: !showFooter.value });
|
||||
//update-end-author:taoyan date:2022-5-24 for: VUEN-1117【issue】0523周开源问题
|
||||
if(unref(isUpdate)){
|
||||
updateSchema([
|
||||
//修改主岗位和兼职岗位的参数
|
||||
{
|
||||
field: 'mainDepPostId',
|
||||
componentProps: { params: { departIds: data.record.selecteddeparts, parentId: data.record.selecteddeparts } },
|
||||
},
|
||||
{
|
||||
field: 'otherDepPostId',
|
||||
componentProps: { params: { departIds: data.record.selecteddeparts, parentId: data.record.selecteddeparts } },
|
||||
}
|
||||
]);
|
||||
}
|
||||
//部门管理,新增用户,在岗位下添加人员的时候默认当前岗位为主岗位
|
||||
updateSchema([
|
||||
{
|
||||
field: 'mainDepPostId',
|
||||
defaultValue: data?.mainDepPostId || '',
|
||||
}
|
||||
])
|
||||
});
|
||||
//获取标题
|
||||
const getTitle = computed(() => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user