refactor permission
This commit is contained in:
+4
-4
@@ -14,17 +14,17 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@zxing/browser": "^0.1.5",
|
"@zxing/browser": "^0.1.5",
|
||||||
"@zxing/library": "^0.21.3",
|
"@zxing/library": "^0.21.3",
|
||||||
"echarts": "^6.0.0",
|
|
||||||
"vue-echarts": "^8.0.1",
|
|
||||||
"axios": "1.15.0",
|
"axios": "1.15.0",
|
||||||
|
"browser-image-compression": "^2.0.2",
|
||||||
|
"echarts": "^6.0.0",
|
||||||
"less": "^4.3.0",
|
"less": "^4.3.0",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
"tdesign-mobile-vue": "^1.16.0",
|
"tdesign-mobile-vue": "^1.16.0",
|
||||||
"timi-tdesign-mobile": "0.0.10",
|
"timi-tdesign-mobile": "0.0.10",
|
||||||
"browser-image-compression": "^2.0.2",
|
"timi-web": "link:E:/WebProject/timi-web",
|
||||||
"timi-web": "0.0.24",
|
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"vue": "^3.5.16",
|
"vue": "^3.5.16",
|
||||||
|
"vue-echarts": "^8.0.1",
|
||||||
"vue-router": "4.5.1",
|
"vue-router": "4.5.1",
|
||||||
"vue-virtual-scroller": "2.0.0-beta.8"
|
"vue-virtual-scroller": "2.0.0-beta.8"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+11
-174
@@ -36,8 +36,8 @@ importers:
|
|||||||
specifier: 0.0.10
|
specifier: 0.0.10
|
||||||
version: 0.0.10(typescript@5.8.3)
|
version: 0.0.10(typescript@5.8.3)
|
||||||
timi-web:
|
timi-web:
|
||||||
specifier: 0.0.24
|
specifier: link:E:/WebProject/timi-web
|
||||||
version: 0.0.24(typescript@5.8.3)(vue@3.5.40(typescript@5.8.3))
|
version: link:../timi-web
|
||||||
ts-node:
|
ts-node:
|
||||||
specifier: ^10.9.2
|
specifier: ^10.9.2
|
||||||
version: 10.9.2(@types/node@24.13.3)(typescript@5.8.3)
|
version: 10.9.2(@types/node@24.13.3)(typescript@5.8.3)
|
||||||
@@ -755,10 +755,6 @@ packages:
|
|||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
agent-base@6.0.2:
|
|
||||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
|
||||||
engines: {node: '>= 6.0.0'}
|
|
||||||
|
|
||||||
ajv-formats@3.0.1:
|
ajv-formats@3.0.1:
|
||||||
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -796,9 +792,6 @@ packages:
|
|||||||
axios@1.15.0:
|
axios@1.15.0:
|
||||||
resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
|
resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
|
||||||
|
|
||||||
axios@1.18.0:
|
|
||||||
resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==}
|
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
balanced-match@1.0.2:
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
@@ -907,9 +900,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
|
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
copy-anything@2.0.6:
|
|
||||||
resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
|
|
||||||
|
|
||||||
copy-anything@3.0.5:
|
copy-anything@3.0.5:
|
||||||
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
|
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
|
||||||
engines: {node: '>=12.13'}
|
engines: {node: '>=12.13'}
|
||||||
@@ -1224,9 +1214,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
github-slugger@2.0.0:
|
|
||||||
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
|
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -1281,10 +1268,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
https-proxy-agent@5.0.1:
|
|
||||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
|
|
||||||
iconv-lite@0.4.24:
|
iconv-lite@0.4.24:
|
||||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -1305,11 +1288,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==}
|
resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
image-size@0.5.5:
|
|
||||||
resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
import-fresh@3.3.1:
|
import-fresh@3.3.1:
|
||||||
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -1348,9 +1326,6 @@ packages:
|
|||||||
is-promise@4.0.0:
|
is-promise@4.0.0:
|
||||||
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
|
||||||
|
|
||||||
is-what@3.14.1:
|
|
||||||
resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
|
|
||||||
|
|
||||||
is-what@4.1.16:
|
is-what@4.1.16:
|
||||||
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
|
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
|
||||||
engines: {node: '>=12.13'}
|
engines: {node: '>=12.13'}
|
||||||
@@ -1390,11 +1365,6 @@ packages:
|
|||||||
keyv@4.5.4:
|
keyv@4.5.4:
|
||||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||||
|
|
||||||
less@4.5.1:
|
|
||||||
resolution: {integrity: sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==}
|
|
||||||
engines: {node: '>=14'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
less@4.8.0:
|
less@4.8.0:
|
||||||
resolution: {integrity: sha512-7Y7DJBMbsW29UGjOG6NGvxQEx71AaDcrryBwYCMaFwn0kj8FkSueKN9r9WexVOetKEhXh38kL++WJncfkEpS+g==}
|
resolution: {integrity: sha512-7Y7DJBMbsW29UGjOG6NGvxQEx71AaDcrryBwYCMaFwn0kj8FkSueKN9r9WexVOetKEhXh38kL++WJncfkEpS+g==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -1424,10 +1394,6 @@ packages:
|
|||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
make-dir@2.1.0:
|
|
||||||
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
make-dir@5.1.0:
|
make-dir@5.1.0:
|
||||||
resolution: {integrity: sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==}
|
resolution: {integrity: sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -1435,26 +1401,6 @@ packages:
|
|||||||
make-error@1.3.6:
|
make-error@1.3.6:
|
||||||
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
||||||
|
|
||||||
marked-gfm-heading-id@4.1.4:
|
|
||||||
resolution: {integrity: sha512-CspnvVfHSkb/znqdPS4jUR8HtCjq3M/DnrsJCrfLBLvdrgbemmoINKpeWKQYkBiXAoBGejw0cV7xzqrPdup3WA==}
|
|
||||||
peerDependencies:
|
|
||||||
marked: '>=13 <19'
|
|
||||||
|
|
||||||
marked-highlight@2.2.4:
|
|
||||||
resolution: {integrity: sha512-PZxisNMJDduSjc0q6uvjsnqqHCXc9s0eyzxDO9sB1eNGJnd/H1/Fu+z6g/liC1dfJdFW4SftMwMlLvsBhUPrqQ==}
|
|
||||||
peerDependencies:
|
|
||||||
marked: '>=4 <19'
|
|
||||||
|
|
||||||
marked-mangle@1.1.13:
|
|
||||||
resolution: {integrity: sha512-phz1W/nYMr1T08Q7wqH2aj+PPiK85E69WQGfId+prvryfgjY/Idibx4YUvKaYMDV9rK1qo+/yC+Quu/3gdaBeA==}
|
|
||||||
peerDependencies:
|
|
||||||
marked: '>=4 <19'
|
|
||||||
|
|
||||||
marked@17.0.6:
|
|
||||||
resolution: {integrity: sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==}
|
|
||||||
engines: {node: '>= 20'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
math-intrinsics@1.1.0:
|
math-intrinsics@1.1.0:
|
||||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1624,10 +1570,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
|
resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
pify@4.0.1:
|
|
||||||
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
pinia@3.0.4:
|
pinia@3.0.4:
|
||||||
resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==}
|
resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1659,10 +1601,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
prismjs@1.30.0:
|
|
||||||
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
probe-image-size@7.3.0:
|
probe-image-size@7.3.0:
|
||||||
resolution: {integrity: sha512-7CaDeBwiAbh6ohXsvLbAZhO7wzsZAmaevfxe39qvCwRh8LyaZfDlBGGLU1CCTgrTLtCOdwBBhjOrIHaIIimHfQ==}
|
resolution: {integrity: sha512-7CaDeBwiAbh6ohXsvLbAZhO7wzsZAmaevfxe39qvCwRh8LyaZfDlBGGLU1CCTgrTLtCOdwBBhjOrIHaIIimHfQ==}
|
||||||
|
|
||||||
@@ -1740,10 +1678,6 @@ packages:
|
|||||||
scule@1.3.0:
|
scule@1.3.0:
|
||||||
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
||||||
|
|
||||||
semver@5.7.2:
|
|
||||||
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
semver@7.8.5:
|
semver@7.8.5:
|
||||||
resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
|
resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -1841,10 +1775,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-fv83++YkMkeKTkReR/2+RoaSacj6fv5JHJfJ1P3C4p/KmLn9XTe+3jHYlFEWjQdxbehz9BifX1L8rVHmgScP5g==}
|
resolution: {integrity: sha512-fv83++YkMkeKTkReR/2+RoaSacj6fv5JHJfJ1P3C4p/KmLn9XTe+3jHYlFEWjQdxbehz9BifX1L8rVHmgScP5g==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
timi-web@0.0.24:
|
|
||||||
resolution: {integrity: sha512-qzcm72W9EE3BrYiUzc8a20J8xqxH17moEm/ovdijhRbIJtJUr81sslXFe2+OjNtx14meEIPJm3IfRooFzRswLg==}
|
|
||||||
engines: {node: '>=16.0.0'}
|
|
||||||
|
|
||||||
tinycolor2@1.6.0:
|
tinycolor2@1.6.0:
|
||||||
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
||||||
|
|
||||||
@@ -1887,9 +1817,6 @@ packages:
|
|||||||
tslib@2.3.0:
|
tslib@2.3.0:
|
||||||
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
|
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
|
||||||
|
|
||||||
tslib@2.8.1:
|
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@@ -2309,6 +2236,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/gen-mapping': 0.3.13
|
'@jridgewell/gen-mapping': 0.3.13
|
||||||
'@jridgewell/trace-mapping': 0.3.31
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||||
|
|
||||||
@@ -2726,12 +2654,6 @@ snapshots:
|
|||||||
|
|
||||||
acorn@8.17.0: {}
|
acorn@8.17.0: {}
|
||||||
|
|
||||||
agent-base@6.0.2:
|
|
||||||
dependencies:
|
|
||||||
debug: 4.4.3
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
ajv-formats@3.0.1(ajv@8.20.0):
|
ajv-formats@3.0.1(ajv@8.20.0):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
ajv: 8.20.0
|
ajv: 8.20.0
|
||||||
@@ -2775,16 +2697,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
|
|
||||||
axios@1.18.0:
|
|
||||||
dependencies:
|
|
||||||
follow-redirects: 1.16.0
|
|
||||||
form-data: 4.0.6
|
|
||||||
https-proxy-agent: 5.0.1
|
|
||||||
proxy-from-env: 2.1.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
balanced-match@1.0.2: {}
|
balanced-match@1.0.2: {}
|
||||||
|
|
||||||
balanced-match@4.0.4: {}
|
balanced-match@4.0.4: {}
|
||||||
@@ -2830,7 +2742,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
uzip: 0.20201231.0
|
uzip: 0.20201231.0
|
||||||
|
|
||||||
buffer-from@1.1.2: {}
|
buffer-from@1.1.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
bytes@3.1.2: {}
|
bytes@3.1.2: {}
|
||||||
|
|
||||||
@@ -2873,7 +2786,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
delayed-stream: 1.0.0
|
delayed-stream: 1.0.0
|
||||||
|
|
||||||
commander@2.20.3: {}
|
commander@2.20.3:
|
||||||
|
optional: true
|
||||||
|
|
||||||
concat-map@0.0.1: {}
|
concat-map@0.0.1: {}
|
||||||
|
|
||||||
@@ -2891,10 +2805,6 @@ snapshots:
|
|||||||
|
|
||||||
cookie@0.7.2: {}
|
cookie@0.7.2: {}
|
||||||
|
|
||||||
copy-anything@2.0.6:
|
|
||||||
dependencies:
|
|
||||||
is-what: 3.14.1
|
|
||||||
|
|
||||||
copy-anything@3.0.5:
|
copy-anything@3.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-what: 4.1.16
|
is-what: 4.1.16
|
||||||
@@ -3277,8 +3187,6 @@ snapshots:
|
|||||||
dunder-proto: 1.0.1
|
dunder-proto: 1.0.1
|
||||||
es-object-atoms: 1.1.2
|
es-object-atoms: 1.1.2
|
||||||
|
|
||||||
github-slugger@2.0.0: {}
|
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
@@ -3324,13 +3232,6 @@ snapshots:
|
|||||||
statuses: 2.0.2
|
statuses: 2.0.2
|
||||||
toidentifier: 1.0.1
|
toidentifier: 1.0.1
|
||||||
|
|
||||||
https-proxy-agent@5.0.1:
|
|
||||||
dependencies:
|
|
||||||
agent-base: 6.0.2
|
|
||||||
debug: 4.4.3
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
iconv-lite@0.4.24:
|
iconv-lite@0.4.24:
|
||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer: 2.1.2
|
safer-buffer: 2.1.2
|
||||||
@@ -3349,9 +3250,6 @@ snapshots:
|
|||||||
|
|
||||||
ignore@7.0.6: {}
|
ignore@7.0.6: {}
|
||||||
|
|
||||||
image-size@0.5.5:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
import-fresh@3.3.1:
|
import-fresh@3.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
parent-module: 1.0.1
|
parent-module: 1.0.1
|
||||||
@@ -3379,8 +3277,6 @@ snapshots:
|
|||||||
|
|
||||||
is-promise@4.0.0: {}
|
is-promise@4.0.0: {}
|
||||||
|
|
||||||
is-what@3.14.1: {}
|
|
||||||
|
|
||||||
is-what@4.1.16: {}
|
is-what@4.1.16: {}
|
||||||
|
|
||||||
is-what@5.5.0: {}
|
is-what@5.5.0: {}
|
||||||
@@ -3409,20 +3305,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
json-buffer: 3.0.1
|
json-buffer: 3.0.1
|
||||||
|
|
||||||
less@4.5.1:
|
|
||||||
dependencies:
|
|
||||||
copy-anything: 2.0.6
|
|
||||||
parse-node-version: 1.0.1
|
|
||||||
tslib: 2.8.1
|
|
||||||
optionalDependencies:
|
|
||||||
errno: 0.1.8
|
|
||||||
graceful-fs: 4.2.11
|
|
||||||
image-size: 0.5.5
|
|
||||||
make-dir: 2.1.0
|
|
||||||
mime: 1.6.0
|
|
||||||
needle: 3.5.0
|
|
||||||
source-map: 0.6.1
|
|
||||||
|
|
||||||
less@4.8.0:
|
less@4.8.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
copy-anything: 3.0.5
|
copy-anything: 3.0.5
|
||||||
@@ -3463,32 +3345,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
make-dir@2.1.0:
|
|
||||||
dependencies:
|
|
||||||
pify: 4.0.1
|
|
||||||
semver: 5.7.2
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
make-dir@5.1.0:
|
make-dir@5.1.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
make-error@1.3.6: {}
|
make-error@1.3.6: {}
|
||||||
|
|
||||||
marked-gfm-heading-id@4.1.4(marked@17.0.6):
|
|
||||||
dependencies:
|
|
||||||
github-slugger: 2.0.0
|
|
||||||
marked: 17.0.6
|
|
||||||
|
|
||||||
marked-highlight@2.2.4(marked@17.0.6):
|
|
||||||
dependencies:
|
|
||||||
marked: 17.0.6
|
|
||||||
|
|
||||||
marked-mangle@1.1.13(marked@17.0.6):
|
|
||||||
dependencies:
|
|
||||||
marked: 17.0.6
|
|
||||||
|
|
||||||
marked@17.0.6: {}
|
|
||||||
|
|
||||||
math-intrinsics@1.1.0: {}
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
media-typer@1.1.1: {}
|
media-typer@1.1.1: {}
|
||||||
@@ -3629,9 +3490,6 @@ snapshots:
|
|||||||
|
|
||||||
picomatch@4.0.5: {}
|
picomatch@4.0.5: {}
|
||||||
|
|
||||||
pify@4.0.1:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
pinia@3.0.4(typescript@5.8.3)(vue@3.5.40(typescript@5.8.3)):
|
pinia@3.0.4(typescript@5.8.3)(vue@3.5.40(typescript@5.8.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/devtools-api': 7.7.10
|
'@vue/devtools-api': 7.7.10
|
||||||
@@ -3666,8 +3524,6 @@ snapshots:
|
|||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
prismjs@1.30.0: {}
|
|
||||||
|
|
||||||
probe-image-size@7.3.0:
|
probe-image-size@7.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash.merge: 4.6.2
|
lodash.merge: 4.6.2
|
||||||
@@ -3771,9 +3627,6 @@ snapshots:
|
|||||||
|
|
||||||
scule@1.3.0: {}
|
scule@1.3.0: {}
|
||||||
|
|
||||||
semver@5.7.2:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
semver@7.8.5: {}
|
semver@7.8.5: {}
|
||||||
|
|
||||||
send@1.2.1:
|
send@1.2.1:
|
||||||
@@ -3843,8 +3696,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
buffer-from: 1.1.2
|
buffer-from: 1.1.2
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
source-map@0.6.1: {}
|
source-map@0.6.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
speakingurl@14.0.1: {}
|
speakingurl@14.0.1: {}
|
||||||
|
|
||||||
@@ -3896,6 +3751,7 @@ snapshots:
|
|||||||
acorn: 8.17.0
|
acorn: 8.17.0
|
||||||
commander: 2.20.3
|
commander: 2.20.3
|
||||||
source-map-support: 0.5.21
|
source-map-support: 0.5.21
|
||||||
|
optional: true
|
||||||
|
|
||||||
timi-tdesign-mobile@0.0.10(typescript@5.8.3):
|
timi-tdesign-mobile@0.0.10(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3904,23 +3760,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
timi-web@0.0.24(typescript@5.8.3)(vue@3.5.40(typescript@5.8.3)):
|
|
||||||
dependencies:
|
|
||||||
axios: 1.18.0
|
|
||||||
less: 4.5.1
|
|
||||||
marked: 17.0.6
|
|
||||||
marked-gfm-heading-id: 4.1.4(marked@17.0.6)
|
|
||||||
marked-highlight: 2.2.4(marked@17.0.6)
|
|
||||||
marked-mangle: 1.1.13(marked@17.0.6)
|
|
||||||
pinia: 3.0.4(typescript@5.8.3)(vue@3.5.40(typescript@5.8.3))
|
|
||||||
prismjs: 1.30.0
|
|
||||||
terser: 5.49.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
- supports-color
|
|
||||||
- typescript
|
|
||||||
- vue
|
|
||||||
|
|
||||||
tinycolor2@1.6.0: {}
|
tinycolor2@1.6.0: {}
|
||||||
|
|
||||||
tinyglobby@0.2.17:
|
tinyglobby@0.2.17:
|
||||||
@@ -3960,8 +3799,6 @@ snapshots:
|
|||||||
|
|
||||||
tslib@2.3.0: {}
|
tslib@2.3.0: {}
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card-title">
|
||||||
|
<h3 v-if="title || $slots.title" class="title">
|
||||||
|
<slot name="title">
|
||||||
|
<span v-text="title" />
|
||||||
|
</slot>
|
||||||
|
</h3>
|
||||||
|
<p v-if="description || $slots.description" class="description gray">
|
||||||
|
<slot name="description">
|
||||||
|
<span v-text="description" />
|
||||||
|
</slot>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>();
|
||||||
|
|
||||||
|
defineSlots<{
|
||||||
|
title?: () => unknown;
|
||||||
|
description?: () => unknown;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "CardTitle"
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.card-title {
|
||||||
|
margin: 1rem;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin: .5rem 0 0 0;
|
||||||
|
font-size: .875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-role="permissionManagementRoleAccess" v-can="pageAccess" class="permission-edit">
|
<div v-can="pageAccess" class="permission-edit">
|
||||||
<t-form class="form" label-align="left">
|
<t-form class="form" label-align="left">
|
||||||
<t-form-item label="名称">
|
<t-form-item label="名称">
|
||||||
<t-input v-model.trim="formModel.name" placeholder="权限名称" borderless />
|
<t-input v-model.trim="formModel.name" placeholder="权限名称" borderless />
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Toast } from "tdesign-mobile-vue";
|
import { Toast } from "tdesign-mobile-vue";
|
||||||
import { moduleCode, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
import { moduleCode, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||||
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/permission";
|
import { corePermissionCodes } from "@/utils/permission";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "PermissionEdit"
|
name: "PermissionEdit"
|
||||||
|
|||||||
@@ -1,58 +1,131 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-role="permissionManagementRoleAccess" v-can="{ value: [corePermissionCodes.role.read, corePermissionCodes.permission.read], mode: 'and' }" class="permission-list">
|
<div v-can="{ value: [corePermissionCodes.role.read, corePermissionCodes.permission.read], mode: 'and' }" class="permission-list">
|
||||||
<t-pull-down-refresh
|
<t-pull-down-refresh
|
||||||
|
class="list-wrapper"
|
||||||
v-model="isRefreshing"
|
v-model="isRefreshing"
|
||||||
:refresh-timeout="30000"
|
:refresh-timeout="30000"
|
||||||
@refresh="refresh"
|
@refresh="refresh"
|
||||||
>
|
>
|
||||||
|
<div class="content">
|
||||||
|
<t-cell-group class="header" theme="card">
|
||||||
|
<t-cell title="当前账号角色聚合权限 P">
|
||||||
|
<template #description>
|
||||||
|
<div class="description">
|
||||||
|
<div>你拥有以下权限,可执行权限对应的功能,这些权限来自上级角色直接授权或子角色继承。</div>
|
||||||
|
<div class="legends">
|
||||||
|
<div class="legend">
|
||||||
|
<span class="block direct"></span>
|
||||||
|
<span>上级角色直接授权</span>
|
||||||
|
</div>
|
||||||
|
<div class="legend">
|
||||||
|
<span class="block inherited"></span>
|
||||||
|
<span>子角色继承授权</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
|
</t-cell-group>
|
||||||
<t-cell-group v-if="sortedPermissions.length" theme="card">
|
<t-cell-group v-if="sortedPermissions.length" theme="card">
|
||||||
<t-swipe-cell
|
<t-swipe-cell
|
||||||
v-for="item in sortedPermissions"
|
v-for="item in sortedPermissions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="item"
|
class="item"
|
||||||
>
|
>
|
||||||
<t-cell
|
<t-cell :title="item.name">
|
||||||
:title="item.name"
|
<template #leftIcon>
|
||||||
>
|
<span class="source" :class="`source-${item.source}`"/>
|
||||||
|
</template>
|
||||||
<template #note>
|
<template #note>
|
||||||
<span class="code" v-text="item.code"></span>
|
<span class="code" v-text="formatPermissionCode(item)" />
|
||||||
</template>
|
</template>
|
||||||
</t-cell>
|
</t-cell>
|
||||||
<template #right>
|
|
||||||
<t-button v-can="corePermissionCodes.permission.delete" class="delete" theme="danger" @click="removePermission(item.id)">删除</t-button>
|
|
||||||
</template>
|
|
||||||
</t-swipe-cell>
|
</t-swipe-cell>
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<t-empty v-else-if="!permissionStore.loading" description="暂无权限" />
|
<t-empty v-else-if="!permissionStore.loading && !isSourceLoading" description="暂无权限" />
|
||||||
|
</div>
|
||||||
</t-pull-down-refresh>
|
</t-pull-down-refresh>
|
||||||
<t-button v-can="corePermissionCodes.permission.create" class="add" theme="primary" type="button" @click="router.push('/management/permissions/items/create')">
|
|
||||||
<template #icon>
|
|
||||||
<t-icon name="add" />
|
|
||||||
</template>
|
|
||||||
新增权限
|
|
||||||
</t-button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { type Role as UserRole, RoleAPI, useUserStore } from "timi-web";
|
||||||
import type { Permission } from "@/store/permissionSettingsStore";
|
import type { Permission } from "@/store/permissionSettingsStore";
|
||||||
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
import { moduleCode, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||||
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/permission";
|
import { corePermissionCodes } from "@/utils/permission";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "PermissionList"
|
name: "PermissionList"
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const permissionStore = usePermissionSettingsStore();
|
const permissionStore = usePermissionSettingsStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
const isRefreshing = ref(false);
|
const isRefreshing = ref(false);
|
||||||
|
const isSourceLoading = ref(false);
|
||||||
|
|
||||||
// ---------- 列表数据 ----------
|
// ---------- 列表数据 ----------
|
||||||
|
|
||||||
const sortedPermissions = computed<Permission[]>(() => [...permissionStore.permissionList].sort((left, right) => left.code.localeCompare(right.code)));
|
type PermissionSource = "direct" | "inherited" | "unknown";
|
||||||
|
type PermissionWithSource = Permission & {
|
||||||
|
source: PermissionSource;
|
||||||
|
};
|
||||||
|
|
||||||
async function removePermission(id: string): Promise<void> {
|
const sourceRoleList = ref<UserRole[]>([]);
|
||||||
await permissionStore.removePermission(id);
|
const sortedPermissions = computed<PermissionWithSource[]>(() => {
|
||||||
|
const permissionMap = new Map<string, PermissionWithSource>();
|
||||||
|
for (const role of sourceRoleList.value) {
|
||||||
|
for (const permission of getRoleAggregatePermissions(role)) {
|
||||||
|
permissionMap.set(permission.id, {
|
||||||
|
...permission,
|
||||||
|
source: getPermissionSource(permission.id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const permission of getLoginUserPermissions()) {
|
||||||
|
if (!permissionMap.has(permission.id)) {
|
||||||
|
permissionMap.set(permission.id, {
|
||||||
|
...permission,
|
||||||
|
source: "unknown"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(permissionMap.values()).sort((left, right) => formatPermissionCode(left).localeCompare(formatPermissionCode(right)));
|
||||||
|
});
|
||||||
|
|
||||||
|
function getRoleAggregatePermissions(role: UserRole): Permission[] {
|
||||||
|
if (role.allPermissionList?.length) {
|
||||||
|
return role.allPermissionList;
|
||||||
|
}
|
||||||
|
if (role.permissionList?.length) {
|
||||||
|
return role.permissionList;
|
||||||
|
}
|
||||||
|
return (role.permissionIdList || []).map(permissionStore.getPermissionById).filter(Boolean) as Permission[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoginUserPermissions(): Permission[] {
|
||||||
|
const permissionCodeSet = new Set(userStore.getPermissionList());
|
||||||
|
return permissionStore.permissionList.filter(item => permissionCodeSet.has(formatPermissionCode(item)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPermissionSource(permissionId: string): PermissionSource {
|
||||||
|
if (sourceRoleList.value.some(role => getDirectPermissionIdSet(role).has(permissionId))) {
|
||||||
|
return "direct";
|
||||||
|
}
|
||||||
|
if (sourceRoleList.value.some(role => role.allPermissionList?.some(permission => permission.id === permissionId))) {
|
||||||
|
return "inherited";
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDirectPermissionIdSet(role: UserRole): Set<string> {
|
||||||
|
if (role.permissionList?.length) {
|
||||||
|
return new Set(role.permissionList.map(item => item.id));
|
||||||
|
}
|
||||||
|
return new Set(role.permissionIdList || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPermissionCode(permission: Permission): string {
|
||||||
|
return `${permission.moduleCode}:${permission.code}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 页面数据 ----------
|
// ---------- 页面数据 ----------
|
||||||
@@ -61,6 +134,7 @@ async function refresh(): Promise<void> {
|
|||||||
isRefreshing.value = true;
|
isRefreshing.value = true;
|
||||||
try {
|
try {
|
||||||
await permissionStore.loadAll(true);
|
await permissionStore.loadAll(true);
|
||||||
|
await loadSourceRoles();
|
||||||
} finally {
|
} finally {
|
||||||
isRefreshing.value = false;
|
isRefreshing.value = false;
|
||||||
}
|
}
|
||||||
@@ -68,26 +142,97 @@ async function refresh(): Promise<void> {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await permissionStore.loadAll();
|
await permissionStore.loadAll();
|
||||||
|
await loadSourceRoles();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function loadSourceRoles(): Promise<void> {
|
||||||
|
isSourceLoading.value = true;
|
||||||
|
try {
|
||||||
|
sourceRoleList.value = await RoleAPI.currentDetailList(moduleCode);
|
||||||
|
} finally {
|
||||||
|
isSourceLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.permission-list {
|
.permission-list {
|
||||||
padding: calc(var(--app-nav-height) + 1rem) 0 6rem 0;
|
--source-direct-color: #2563EB;
|
||||||
|
--source-inherited-color: #14B8A6;
|
||||||
|
--source-unknown-color: #94A3B8;
|
||||||
|
|
||||||
|
padding: calc(var(--app-nav-height) + 1rem) 0 1rem 0;
|
||||||
|
|
||||||
|
.list-wrapper {
|
||||||
|
|
||||||
|
.content {
|
||||||
|
gap: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
|
||||||
|
.description {
|
||||||
|
gap: .5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.legends {
|
||||||
|
gap: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
gap: .25rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.block {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
display: block;
|
||||||
|
border-radius: .25rem;
|
||||||
|
|
||||||
|
&.direct {
|
||||||
|
background: var(--source-direct-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.inherited {
|
||||||
|
background: var(--source-inherited-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
:deep(.t-cell__title) {
|
|
||||||
|
&:last-child {
|
||||||
|
|
||||||
|
:deep(.t-cell:after) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.t-cell) {
|
||||||
|
|
||||||
|
&__left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.t-cell__title-text) {
|
&__title-text {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.t-cell__note) {
|
&__note {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
max-width: 56%;
|
max-width: 56%;
|
||||||
@@ -95,8 +240,23 @@ onMounted(async () => {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.t-cell__right) {
|
.source {
|
||||||
flex: 0 0 auto;
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
display: block;
|
||||||
|
border-radius: .25rem;
|
||||||
|
|
||||||
|
&-direct {
|
||||||
|
background: var(--source-direct-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-inherited {
|
||||||
|
background: var(--source-inherited-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-unknown {
|
||||||
|
background: var(--source-unknown-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.code {
|
.code {
|
||||||
@@ -106,26 +266,35 @@ onMounted(async () => {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
&:last-child {
|
}
|
||||||
|
}
|
||||||
:deep(.t-cell:after) {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete {
|
:global(html.theme-dark) {
|
||||||
height: 100%;
|
.permission-list {
|
||||||
padding: 0 1rem;
|
.list-wrapper {
|
||||||
border-radius: 0;
|
.content {
|
||||||
}
|
.item {
|
||||||
|
:deep(.t-cell) {
|
||||||
|
.source {
|
||||||
|
&-direct {
|
||||||
|
background: #60a5fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add {
|
&-inherited {
|
||||||
left: 1rem;
|
background: #2dd4bf;
|
||||||
right: 1rem;
|
}
|
||||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
|
||||||
position: fixed;
|
&-unknown {
|
||||||
|
background: #64748b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,41 +4,67 @@
|
|||||||
class="permission-settings"
|
class="permission-settings"
|
||||||
>
|
>
|
||||||
<t-cell-group title="权限配置" theme="card">
|
<t-cell-group title="权限配置" theme="card">
|
||||||
<t-cell v-can="corePermissionCodes.role.read" title="角色管理" :note="`${permissionStore.roleList.length} 个角色`" arrow hover @click="router.push('/management/permissions/roles')">
|
<t-cell
|
||||||
|
v-can="corePermissionCodes.role.read"
|
||||||
|
title="角色管理"
|
||||||
|
:note="`${roleCount} 个角色`"
|
||||||
|
arrow
|
||||||
|
@click="router.push('/management/permissions/roles')"
|
||||||
|
>
|
||||||
<template #leftIcon>
|
<template #leftIcon>
|
||||||
<t-icon name="user-setting" size="1.75rem" />
|
<t-icon name="user-setting" size="1.75rem" />
|
||||||
</template>
|
</template>
|
||||||
</t-cell>
|
</t-cell>
|
||||||
<t-cell v-can="corePermissionCodes.permission.read" title="权限管理" :note="`${permissionStore.permissionList.length} 个权限`" arrow hover @click="router.push('/management/permissions/items')">
|
<t-cell
|
||||||
|
v-can="corePermissionCodes.permission.read"
|
||||||
|
title="权限管理"
|
||||||
|
:note="`${permissionCount} 个权限`"
|
||||||
|
arrow
|
||||||
|
@click="router.push('/management/permissions/items')"
|
||||||
|
>
|
||||||
<template #leftIcon>
|
<template #leftIcon>
|
||||||
<t-icon name="secured" size="1.75rem" />
|
<t-icon name="secured" size="1.75rem" />
|
||||||
</template>
|
</template>
|
||||||
</t-cell>
|
</t-cell>
|
||||||
<t-cell v-can="rolePermissionAssignAccess" title="角色权限授权" note="为角色分配可用权限" arrow hover @click="router.push('/management/permissions/assign')">
|
<t-cell
|
||||||
|
v-can="rolePermissionAssignAccess"
|
||||||
|
title="角色直接授权 P"
|
||||||
|
arrow
|
||||||
|
@click="router.push('/management/permissions/assign/p')"
|
||||||
|
>
|
||||||
|
<template #leftIcon>
|
||||||
|
<t-icon name="secured" size="1.75rem" />
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
|
<t-cell
|
||||||
|
v-can="rolePermissionAssignAccess"
|
||||||
|
title="角色转授权限 D"
|
||||||
|
arrow
|
||||||
|
@click="router.push('/management/permissions/assign/d')"
|
||||||
|
>
|
||||||
<template #leftIcon>
|
<template #leftIcon>
|
||||||
<t-icon name="user-checked" size="1.75rem" />
|
<t-icon name="user-checked" size="1.75rem" />
|
||||||
</template>
|
</template>
|
||||||
</t-cell>
|
</t-cell>
|
||||||
<t-cell v-can="corePermissionCodes.userRole.read" title="用户角色授权" note="为用户分配业务角色" arrow hover @click="router.push('/management/permissions/user-roles')">
|
<t-cell
|
||||||
|
v-can="corePermissionCodes.userRole.read"
|
||||||
|
title="用户角色授权"
|
||||||
|
arrow
|
||||||
|
@click="router.push('/management/permissions/user-roles')"
|
||||||
|
>
|
||||||
<template #leftIcon>
|
<template #leftIcon>
|
||||||
<t-icon name="usergroup" size="1.75rem" />
|
<t-icon name="usergroup" size="1.75rem" />
|
||||||
</template>
|
</template>
|
||||||
</t-cell>
|
</t-cell>
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<div class="struct">
|
<t-cell-group title="当前角色结构" theme="card">
|
||||||
<div class="header">
|
|
||||||
<span class="title">当前角色结构</span>
|
|
||||||
<span class="tips orange">上级角色包含子级角色所有权限</span>
|
|
||||||
</div>
|
|
||||||
<t-cell-group theme="card">
|
|
||||||
<t-cell
|
<t-cell
|
||||||
v-can="{ value: [corePermissionCodes.role.read, corePermissionCodes.permission.read], mode: 'and' }"
|
v-can="{ value: [corePermissionCodes.role.read, corePermissionCodes.permission.read], mode: 'and' }"
|
||||||
v-for="item in permissionStore.roleTree"
|
v-for="item in visibleRoleTree"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:note="`${item.permissionIdList.length} 项权限`"
|
:arrow="canAssignRole(item)"
|
||||||
arrow
|
:hover="canAssignRole(item)"
|
||||||
hover
|
@click="canAssignRole(item) && toAssign(item.id)"
|
||||||
@click="toAssign(item.id)"
|
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="title" v-text="item.name" :style="{ paddingLeft: `${item.level * 1.25}rem` }" />
|
<div class="title" v-text="item.name" :style="{ paddingLeft: `${item.level * 1.25}rem` }" />
|
||||||
@@ -46,19 +72,50 @@
|
|||||||
</t-cell>
|
</t-cell>
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useUserStore } from "timi-web";
|
||||||
|
import type { Role } from "@/store/permissionSettingsStore";
|
||||||
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||||
import { corePermissionCodes } from "@/utils/permission";
|
import { corePermissionCodes, coreRoleCodes } from "@/utils/permission";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "PermissionSettings"
|
name: "PermissionSettings"
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const userStore = useUserStore();
|
||||||
const permissionStore = usePermissionSettingsStore();
|
const permissionStore = usePermissionSettingsStore();
|
||||||
|
const authorizationRoleList = ref<Role[]>([]);
|
||||||
|
const isSystemAdmin = computed(() => userStore.hasRoles(coreRoleCodes.admin));
|
||||||
|
const roleCount = computed(() => authorizationRoleList.value.length);
|
||||||
|
const permissionCount = computed(() => permissionStore.permissionList.length);
|
||||||
|
type RoleTreeItem = Pick<Role, "id" | "name" | "parentRoleId" | "protectedRole"> & { level: number };
|
||||||
|
|
||||||
|
const visibleRoleTree = computed<RoleTreeItem[]>(() => {
|
||||||
|
if (isSystemAdmin.value) {
|
||||||
|
return permissionStore.roleTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleMap = new Map(authorizationRoleList.value.map(item => [item.id, item]));
|
||||||
|
const roleIdSet = new Set(authorizationRoleList.value.map(item => item.id));
|
||||||
|
const roots = authorizationRoleList.value.filter(item => !item.parentRoleId || !roleIdSet.has(item.parentRoleId));
|
||||||
|
const buildTree = (roleId: string, level: number, visitedRoleIds: Set<string>): RoleTreeItem[] => {
|
||||||
|
const role = roleMap.get(roleId);
|
||||||
|
if (!role || visitedRoleIds.has(roleId)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextVisitedRoleIds = new Set(visitedRoleIds);
|
||||||
|
nextVisitedRoleIds.add(roleId);
|
||||||
|
const childRoleIdList = authorizationRoleList.value
|
||||||
|
.filter(item => item.parentRoleId === roleId)
|
||||||
|
.map(item => item.id);
|
||||||
|
return [{ ...role, level }, ...childRoleIdList.flatMap(childRoleId => buildTree(childRoleId, level + 1, nextVisitedRoleIds))];
|
||||||
|
};
|
||||||
|
return roots.flatMap(item => buildTree(item.id, 0, new Set<string>()));
|
||||||
|
});
|
||||||
|
|
||||||
// ---------- 权限入口 ----------
|
// ---------- 权限入口 ----------
|
||||||
|
|
||||||
@@ -66,25 +123,36 @@ const rolePermissionAssignAccess = {
|
|||||||
value: [
|
value: [
|
||||||
corePermissionCodes.role.read,
|
corePermissionCodes.role.read,
|
||||||
corePermissionCodes.permission.read,
|
corePermissionCodes.permission.read,
|
||||||
corePermissionCodes.rolePermission.read,
|
corePermissionCodes.rolePermission.read
|
||||||
corePermissionCodes.roleRelation.read
|
|
||||||
],
|
],
|
||||||
mode: "and"
|
mode: "and"
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
async function toAssign(roleId: string): Promise<void> {
|
async function toAssign(roleId: string): Promise<void> {
|
||||||
await router.push({
|
await router.push({
|
||||||
path: "/management/permissions/assign",
|
path: "/management/permissions/assign/p",
|
||||||
query: {
|
query: {
|
||||||
roleId
|
roleId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function canAssignRole(role: Pick<Role, "protectedRole">): boolean {
|
||||||
|
return isSystemAdmin.value || role.protectedRole !== true;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 页面数据 ----------
|
// ---------- 页面数据 ----------
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
if (isSystemAdmin.value) {
|
||||||
await permissionStore.loadAll();
|
await permissionStore.loadAll();
|
||||||
|
authorizationRoleList.value = permissionStore.roleList;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await permissionStore.loadAll();
|
||||||
|
const roleList = await permissionStore.loadManageableRoles();
|
||||||
|
authorizationRoleList.value = roleList;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,277 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-can="{ value: [corePermissionCodes.role.read, corePermissionCodes.permission.read, corePermissionCodes.rolePermission.read], mode: 'and' }"
|
||||||
|
class="role-delegation-permission-assign"
|
||||||
|
>
|
||||||
|
<div class="header bg-white">
|
||||||
|
<t-dropdown-menu class="role-selector">
|
||||||
|
<t-dropdown-item
|
||||||
|
:label="activeRoleLabel"
|
||||||
|
:options="roleOptions"
|
||||||
|
:value="activeRoleId"
|
||||||
|
borderless
|
||||||
|
@change="handleRoleChange"
|
||||||
|
/>
|
||||||
|
</t-dropdown-menu>
|
||||||
|
</div>
|
||||||
|
<t-cell-group v-if="activeRole && grantablePermissionList.length" title="可配置的转授权限 D" theme="card">
|
||||||
|
<t-cell
|
||||||
|
v-for="item in grantablePermissionList"
|
||||||
|
:key="item.id"
|
||||||
|
:title="item.name || item.code"
|
||||||
|
>
|
||||||
|
<template #description>
|
||||||
|
<span class="code" v-text="item.code"></span>
|
||||||
|
</template>
|
||||||
|
<template #right-icon>
|
||||||
|
<t-switch
|
||||||
|
:disabled="!canUpdateRolePermission || !canEditActiveRole"
|
||||||
|
:model-value="isPermissionChecked(item.id)"
|
||||||
|
@change="value => setPermissionChecked(item.id, value === true)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
|
</t-cell-group>
|
||||||
|
<t-empty v-else-if="!permissionStore.loading" description="暂无可配置的转授权限 D" />
|
||||||
|
<t-button
|
||||||
|
v-if="canEditActiveRole"
|
||||||
|
v-can="rolePermissionWriteAccess"
|
||||||
|
class="save"
|
||||||
|
theme="primary"
|
||||||
|
@click="savePermissionList"
|
||||||
|
>
|
||||||
|
保存 D
|
||||||
|
</t-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
|
||||||
|
import { Toast } from "tdesign-mobile-vue";
|
||||||
|
import { type Permission, useUserStore } from "timi-web";
|
||||||
|
import { type Role, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||||
|
import { corePermissionCodes, coreRoleCodes } from "@/utils/permission";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "RoleDelegationPermissionAssign"
|
||||||
|
});
|
||||||
|
|
||||||
|
const permissionStore = usePermissionSettingsStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
// ---------- 角色选择 ----------
|
||||||
|
|
||||||
|
const activeRoleId = ref(String(route.query.roleId || route.params.id || ""));
|
||||||
|
const checkedPermissionIds = ref<string[]>([]);
|
||||||
|
const unmanagedPermissionIds = ref<string[]>([]);
|
||||||
|
const grantablePermissionList = ref<Permission[]>([]);
|
||||||
|
const candidateRoleList = ref<Role[]>([]);
|
||||||
|
let permissionSyncSeq = 0;
|
||||||
|
|
||||||
|
const activeRole = computed(() => permissionStore.getRoleById(activeRoleId.value));
|
||||||
|
const activeRoleLabel = computed(() => activeRole.value ? `当前角色:${activeRole.value.name}` : "选择角色");
|
||||||
|
const canUpdateRolePermission = computed(() => userStore.hasPermissions([
|
||||||
|
corePermissionCodes.rolePermission.create,
|
||||||
|
corePermissionCodes.rolePermission.delete
|
||||||
|
], "and"));
|
||||||
|
const isSystemAdmin = computed(() => userStore.hasRoles(coreRoleCodes.admin));
|
||||||
|
const canEditActiveRole = computed(() => Boolean(activeRole.value)
|
||||||
|
&& candidateRoleList.value.some(item => item.id === activeRoleId.value)
|
||||||
|
&& (isSystemAdmin.value || activeRole.value?.protectedRole !== true));
|
||||||
|
const roleOptions = computed<DropdownOption[]>(() => candidateRoleList.value
|
||||||
|
.filter(item => !!item.id)
|
||||||
|
.map(item => ({
|
||||||
|
label: item.name || item.code,
|
||||||
|
value: item.id,
|
||||||
|
disabled: false
|
||||||
|
})));
|
||||||
|
|
||||||
|
const rolePermissionWriteAccess = {
|
||||||
|
value: [
|
||||||
|
corePermissionCodes.rolePermission.create,
|
||||||
|
corePermissionCodes.rolePermission.delete
|
||||||
|
],
|
||||||
|
mode: "and"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// ---------- 权限状态 ----------
|
||||||
|
|
||||||
|
const checkedPermissionIdSet = computed(() => new Set(checkedPermissionIds.value));
|
||||||
|
|
||||||
|
// ---------- 角色切换 ----------
|
||||||
|
|
||||||
|
async function handleRoleChange(value: DropdownValue): Promise<void> {
|
||||||
|
activeRoleId.value = Array.isArray(value) ? String(value[0] || "") : String(value);
|
||||||
|
await loadActiveRolePermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadActiveRolePermissions(): Promise<void> {
|
||||||
|
const roleId = activeRoleId.value;
|
||||||
|
if (!roleId) {
|
||||||
|
clearPermissionState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSeq = permissionSyncSeq + 1;
|
||||||
|
permissionSyncSeq = currentSeq;
|
||||||
|
const [role, permissionList] = await Promise.all([
|
||||||
|
permissionStore.loadRoleDetail(roleId),
|
||||||
|
permissionStore.loadGrantableDelegationPermissions(roleId)
|
||||||
|
]);
|
||||||
|
if (currentSeq !== permissionSyncSeq || activeRoleId.value !== roleId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
grantablePermissionList.value = permissionList;
|
||||||
|
const grantablePermissionIdSet = new Set(grantablePermissionList.value.map(item => item.id));
|
||||||
|
checkedPermissionIds.value = role.delegationPermissionIdList.filter(id => grantablePermissionIdSet.has(id));
|
||||||
|
unmanagedPermissionIds.value = role.delegationPermissionIdList.filter(id => !grantablePermissionIdSet.has(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 保存 ----------
|
||||||
|
|
||||||
|
async function savePermissionList(): Promise<void> {
|
||||||
|
if (!activeRoleId.value || !canEditActiveRole.value || !canUpdateRolePermission.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await permissionStore.setRoleDelegationPermissionList(activeRoleId.value, Array.from(new Set([
|
||||||
|
...checkedPermissionIds.value,
|
||||||
|
...unmanagedPermissionIds.value
|
||||||
|
])));
|
||||||
|
Toast.success("D 已保存");
|
||||||
|
await loadActiveRolePermissions();
|
||||||
|
} catch (error) {
|
||||||
|
clearPermissionState();
|
||||||
|
try {
|
||||||
|
await loadAuthorizationScope();
|
||||||
|
await loadActiveRolePermissions();
|
||||||
|
} finally {
|
||||||
|
Toast.error(getErrorMessage(error, "D 保存失败"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 权限勾选 ----------
|
||||||
|
|
||||||
|
function isPermissionChecked(permissionId: string): boolean {
|
||||||
|
return checkedPermissionIdSet.value.has(permissionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPermissionChecked(permissionId: string, value: boolean): void {
|
||||||
|
if (!canUpdateRolePermission.value || !canEditActiveRole.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value && !checkedPermissionIds.value.includes(permissionId)) {
|
||||||
|
checkedPermissionIds.value.push(permissionId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
checkedPermissionIds.value = checkedPermissionIds.value.filter(item => item !== permissionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearPermissionState(): void {
|
||||||
|
checkedPermissionIds.value = [];
|
||||||
|
unmanagedPermissionIds.value = [];
|
||||||
|
grantablePermissionList.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 授权范围 ----------
|
||||||
|
|
||||||
|
async function loadAuthorizationScope(): Promise<void> {
|
||||||
|
const roleList = isSystemAdmin.value ? permissionStore.roleList : await permissionStore.loadManageableRoles();
|
||||||
|
candidateRoleList.value = roleList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorMessage(error: unknown, fallback: string): string {
|
||||||
|
if (error instanceof Error && error.message) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
if (typeof error === "string" && error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await permissionStore.loadAll();
|
||||||
|
await loadAuthorizationScope();
|
||||||
|
if (!activeRoleId.value) {
|
||||||
|
activeRoleId.value = candidateRoleList.value[0]?.id || "";
|
||||||
|
}
|
||||||
|
await loadActiveRolePermissions();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.role-delegation-permission-assign {
|
||||||
|
gap: 1rem;
|
||||||
|
display: flex;
|
||||||
|
padding: calc(var(--app-nav-height) + 4.5rem) 0 6rem 0;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
top: var(--app-nav-height);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 20;
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: var(--td-shadow-1);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.role-selector::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.t-cell__title) {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.t-cell__title-text) {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.t-cell__description) {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.t-cell__right) {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-left: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save {
|
||||||
|
left: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.theme-dark) {
|
||||||
|
.role-delegation-permission-assign {
|
||||||
|
.header {
|
||||||
|
background: #0f172a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,45 +1,116 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-role="permissionManagementRoleAccess" v-can="pageAccess" class="role-edit">
|
<div v-can="pageAccess" class="role-edit">
|
||||||
<t-form class="form" label-align="left">
|
<t-form class="form" label-align="left">
|
||||||
<t-form-item label="名称">
|
<t-form-item label="名称">
|
||||||
<t-input v-model.trim="formModel.name" placeholder="角色名称" borderless />
|
<t-input v-model.trim="formModel.name" placeholder="角色名称" borderless :disabled="!canEditRoleInfo" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="代码">
|
<t-form-item label="代码">
|
||||||
<t-input v-model.trim="formModel.code" placeholder="如 front-desk" borderless />
|
<t-input v-model.trim="formModel.code" placeholder="如 front-desk" borderless :disabled="!canEditRoleCode" />
|
||||||
|
<div v-if="isBuiltinRole" class="hint" v-text="'内建角色代码不可修改'" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="模块">
|
<t-form-item label="模块">
|
||||||
<t-input v-model.trim="formModel.moduleCode" placeholder="模块代码" borderless />
|
<t-input v-model.trim="formModel.moduleCode" placeholder="模块代码" borderless disabled />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="上级" :arrow="canEditParentRole" @click="openParentRolePicker">
|
||||||
|
<div class="picker-text" v-text="selectedParentRoleName" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
<t-form-item label="说明">
|
<t-form-item label="说明">
|
||||||
<t-textarea v-model.trim="formModel.description" class="textarea" placeholder="角色说明" autosize borderless />
|
<t-textarea v-model.trim="formModel.description" class="textarea" placeholder="角色说明" autosize borderless :disabled="!canEditRoleInfo" />
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item label="继承角色" :arrow="canEditRoleRelation" @click="openChildRolePicker">
|
||||||
|
<div class="picker-text" v-text="selectedChildRoleName" />
|
||||||
</t-form-item>
|
</t-form-item>
|
||||||
</t-form>
|
</t-form>
|
||||||
<section class="children">
|
<t-cell-group v-if="!isCreateMode" title="权限授权" theme="card">
|
||||||
<div class="title">
|
<t-cell
|
||||||
<span>子角色</span>
|
v-can="rolePermissionReadAccess"
|
||||||
<span class="hint">父角色会继承子角色权限</span>
|
title="直接授权权限 P"
|
||||||
|
arrow
|
||||||
|
@click="toRolePermissionPage"
|
||||||
|
>
|
||||||
|
<template #leftIcon>
|
||||||
|
<t-icon name="secured" size="1.5rem" />
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
|
<t-cell
|
||||||
|
v-can="rolePermissionReadAccess"
|
||||||
|
title="可转授权限 D"
|
||||||
|
arrow
|
||||||
|
@click="toRoleDelegationPage"
|
||||||
|
>
|
||||||
|
<template #leftIcon>
|
||||||
|
<t-icon name="user-checked" size="1.5rem" />
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
|
</t-cell-group>
|
||||||
|
<template v-if="aggregatePermissionList.length">
|
||||||
|
<section>
|
||||||
|
<card-title title="聚合权限 P" description="该角色拥有以下权限,可执行权限对应的功能,这些权限来自上级角色直接授权或子角色继承。" />
|
||||||
|
<t-cell-group class="permission-list" theme="card">
|
||||||
|
<t-cell
|
||||||
|
v-for="item in aggregatePermissionList"
|
||||||
|
:key="item.id"
|
||||||
|
:title="item.name"
|
||||||
|
>
|
||||||
|
<template #leftIcon>
|
||||||
|
<span class="source" :class="`source-${item.source}`" />
|
||||||
|
</template>
|
||||||
|
<template #note>
|
||||||
|
<span class="code" v-text="formatPermissionCode(item)" />
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
|
</t-cell-group>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<t-checkbox-group class="group" v-model="formModel.childRoleIdList">
|
|
||||||
|
<t-popup v-model="parentRolePickerVisible" placement="bottom">
|
||||||
|
<t-picker
|
||||||
|
v-model="parentRolePickerDraft"
|
||||||
|
:columns="parentRolePickerColumns"
|
||||||
|
title="选择上级"
|
||||||
|
@cancel="cancelParentRolePicker"
|
||||||
|
@confirm="confirmParentRolePicker"
|
||||||
|
/>
|
||||||
|
</t-popup>
|
||||||
|
<t-popup v-model="childRolePickerVisible" placement="bottom">
|
||||||
|
<div class="role-picker bg-white">
|
||||||
|
<div class="picker-header">
|
||||||
|
<t-button variant="text" size="small" @click="cancelChildRolePicker">取消</t-button>
|
||||||
|
<span class="title" v-text="'继承角色'" />
|
||||||
|
<t-button theme="primary" variant="text" size="small" :loading="savingTarget === 'inherit'" @click="saveRoleInherit">保存</t-button>
|
||||||
|
</div>
|
||||||
|
<div class="picker-body">
|
||||||
|
<t-checkbox-group v-if="childOptions.length" v-model="childRolePickerDraft" class="group">
|
||||||
<t-checkbox
|
<t-checkbox
|
||||||
class="item bg-transparent"
|
|
||||||
v-for="item in childOptions"
|
v-for="item in childOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
|
class="item"
|
||||||
:value="item.id"
|
:value="item.id"
|
||||||
:label="item.name"
|
:label="item.name || item.code"
|
||||||
icon="line"
|
icon="line"
|
||||||
borderless
|
borderless
|
||||||
default-checked
|
|
||||||
/>
|
/>
|
||||||
</t-checkbox-group>
|
</t-checkbox-group>
|
||||||
</section>
|
<t-empty v-else description="暂无可继承角色" />
|
||||||
<t-button v-can="savePermissionCode" class="save" theme="primary" @click="saveRole">保存</t-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</t-popup>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Toast } from "tdesign-mobile-vue";
|
import { Toast } from "tdesign-mobile-vue";
|
||||||
import { moduleCode, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
import { useUserStore } from "timi-web";
|
||||||
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/permission";
|
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||||
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
|
import {
|
||||||
|
moduleCode,
|
||||||
|
type Permission,
|
||||||
|
type Role,
|
||||||
|
type RoleDraft,
|
||||||
|
usePermissionSettingsStore
|
||||||
|
} from "@/store/permissionSettingsStore";
|
||||||
|
import { corePermissionCodes, coreRoleCodes } from "@/utils/permission";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "RoleEdit"
|
name: "RoleEdit"
|
||||||
@@ -47,7 +118,10 @@ defineOptions({
|
|||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const navBarStore = useNavBarStore();
|
||||||
const permissionStore = usePermissionSettingsStore();
|
const permissionStore = usePermissionSettingsStore();
|
||||||
|
const currentStoreStore = useCurrentStoreStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
// ---------- 路由权限 ----------
|
// ---------- 路由权限 ----------
|
||||||
|
|
||||||
@@ -55,10 +129,21 @@ const routeId = computed(() => String(route.params.id || ""));
|
|||||||
const isCreateMode = computed(() => !routeId.value);
|
const isCreateMode = computed(() => !routeId.value);
|
||||||
const savePermissionCode = computed(() => isCreateMode.value ? corePermissionCodes.role.create : corePermissionCodes.role.update);
|
const savePermissionCode = computed(() => isCreateMode.value ? corePermissionCodes.role.create : corePermissionCodes.role.update);
|
||||||
const pageAccess = computed(() => ({
|
const pageAccess = computed(() => ({
|
||||||
value: [corePermissionCodes.role.read, corePermissionCodes.permission.read, savePermissionCode.value],
|
value: isCreateMode.value
|
||||||
|
? [corePermissionCodes.role.read, corePermissionCodes.role.create]
|
||||||
|
: [corePermissionCodes.role.read],
|
||||||
mode: "and" as const
|
mode: "and" as const
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const rolePermissionReadAccess = {
|
||||||
|
value: [
|
||||||
|
corePermissionCodes.role.read,
|
||||||
|
corePermissionCodes.permission.read,
|
||||||
|
corePermissionCodes.rolePermission.read
|
||||||
|
],
|
||||||
|
mode: "and"
|
||||||
|
} as const;
|
||||||
|
|
||||||
// ---------- 表单 ----------
|
// ---------- 表单 ----------
|
||||||
|
|
||||||
const formModel = reactive({
|
const formModel = reactive({
|
||||||
@@ -67,19 +152,133 @@ const formModel = reactive({
|
|||||||
code: "",
|
code: "",
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
|
parentRoleId: "",
|
||||||
childRoleIdList: [] as string[]
|
childRoleIdList: [] as string[]
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------- 子角色 ----------
|
const currentRole = ref<Role>();
|
||||||
|
const candidateRoleList = ref<Role[]>([]);
|
||||||
|
const inheritanceRoleList = ref<Role[]>([]);
|
||||||
|
const parentRoleOptions = ref<Role[]>([]);
|
||||||
|
const savingTarget = ref("");
|
||||||
|
|
||||||
const childOptions = computed(() => permissionStore.roleList.filter(item => item.id !== formModel.id && !hasChildRole(item.id, formModel.id)));
|
const isSystemAdmin = computed(() => userStore.hasRoles(coreRoleCodes.admin));
|
||||||
|
const isBuiltinRole = computed(() => currentRole.value?.builtin === true);
|
||||||
|
const isProtectedRole = computed(() => !isSystemAdmin.value && currentRole.value?.protectedRole === true);
|
||||||
|
const canManageRole = computed(() => isCreateMode.value || Boolean(currentRole.value && candidateRoleList.value.some(item => item.id === formModel.id)));
|
||||||
|
const canEditRoleInfo = computed(() => userStore.hasPermissions(savePermissionCode.value) && (isCreateMode.value || (
|
||||||
|
canManageRole.value
|
||||||
|
&& !isProtectedRole.value
|
||||||
|
)));
|
||||||
|
const canEditRoleCode = computed(() => canEditRoleInfo.value && !isBuiltinRole.value);
|
||||||
|
const canEditParentRole = computed(() => canEditRoleInfo.value && !isBuiltinRole.value);
|
||||||
|
const canUpdateRoleRelation = computed(() => userStore.hasPermissions([
|
||||||
|
corePermissionCodes.rolePermissionInherit.create,
|
||||||
|
corePermissionCodes.rolePermissionInherit.delete
|
||||||
|
], "and"));
|
||||||
|
const canEditRoleRelation = computed(() => !isCreateMode.value && canUpdateRoleRelation.value && (
|
||||||
|
canManageRole.value
|
||||||
|
&& !isProtectedRole.value
|
||||||
|
));
|
||||||
|
const canSaveBaseInfo = computed(() => canEditRoleInfo.value);
|
||||||
|
const canShowSaveAction = computed(() => canSaveBaseInfo.value && userStore.hasPermissions(savePermissionCode.value));
|
||||||
|
type PermissionSource = "direct" | "inherited";
|
||||||
|
type PermissionWithSource = Permission & {
|
||||||
|
source: PermissionSource;
|
||||||
|
};
|
||||||
|
|
||||||
|
const aggregatePermissionList = computed<PermissionWithSource[]>(() => {
|
||||||
|
const current = currentRole.value;
|
||||||
|
const directPermissionIdList = current?.permissionList?.length
|
||||||
|
? current.permissionList.map(item => item.id)
|
||||||
|
: current?.permissionIdList || [];
|
||||||
|
const directPermissionIdSet = new Set(directPermissionIdList);
|
||||||
|
return (current?.allPermissionList || []).map(permission => ({
|
||||||
|
...permission,
|
||||||
|
source: directPermissionIdSet.has(permission.id) ? "direct" : "inherited"
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
function formatPermissionCode(permission: Permission): string {
|
||||||
|
return `${permission.moduleCode}:${permission.code}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 选择器 ----------
|
||||||
|
type PickerValue = string | number;
|
||||||
|
const parentRolePickerVisible = ref(false);
|
||||||
|
const parentRolePickerDraft = ref<PickerValue[]>([]);
|
||||||
|
const childRolePickerVisible = ref(false);
|
||||||
|
const childRolePickerDraft = ref<string[]>([]);
|
||||||
|
|
||||||
|
const parentRolePickerColumns = computed(() => [parentRoleOptions.value
|
||||||
|
.map(item => ({
|
||||||
|
label: item.name || item.code,
|
||||||
|
value: item.id
|
||||||
|
}))
|
||||||
|
.filter(item => item.value)]);
|
||||||
|
const selectedParentRoleName = computed(() => {
|
||||||
|
const parentRole = parentRoleOptions.value.find(item => item.id === formModel.parentRoleId);
|
||||||
|
return parentRole?.name || parentRole?.code || (formModel.parentRoleId ? "已选择上级" : "未设置上级");
|
||||||
|
});
|
||||||
|
const selectedChildRoleName = computed(() => {
|
||||||
|
const nameList = childOptions.value
|
||||||
|
.filter(item => formModel.childRoleIdList.includes(item.id))
|
||||||
|
.map(item => item.name || item.code);
|
||||||
|
return nameList.length ? nameList.join("、") : "未设置继承角色";
|
||||||
|
});
|
||||||
|
|
||||||
|
function openParentRolePicker(): void {
|
||||||
|
if (!canEditParentRole.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!parentRoleOptions.value.length) {
|
||||||
|
Toast.warning("暂无可选上级");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
parentRolePickerDraft.value = [formModel.parentRoleId];
|
||||||
|
parentRolePickerVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelParentRolePicker(): void {
|
||||||
|
parentRolePickerDraft.value = [formModel.parentRoleId];
|
||||||
|
parentRolePickerVisible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmParentRolePicker(): void {
|
||||||
|
const parentRoleId = String(parentRolePickerDraft.value[0] || "");
|
||||||
|
if (!parentRoleId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
formModel.parentRoleId = parentRoleId;
|
||||||
|
parentRolePickerVisible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openChildRolePicker(): void {
|
||||||
|
if (!canEditRoleRelation.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
childRolePickerDraft.value = [...formModel.childRoleIdList];
|
||||||
|
childRolePickerVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelChildRolePicker(): void {
|
||||||
|
childRolePickerDraft.value = [...formModel.childRoleIdList];
|
||||||
|
childRolePickerVisible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 角色选项 ----------
|
||||||
|
|
||||||
|
const childOptions = computed(() => inheritanceRoleList.value.filter(item => item.id !== formModel.id
|
||||||
|
&& isManagementChildRole(item.id, formModel.id)
|
||||||
|
&& !hasChildRole(item.id, formModel.id)));
|
||||||
|
|
||||||
function hasChildRole(roleId: string, targetRoleId: string, visitedRoleIds = new Set<string>()): boolean {
|
function hasChildRole(roleId: string, targetRoleId: string, visitedRoleIds = new Set<string>()): boolean {
|
||||||
if (visitedRoleIds.has(roleId)) {
|
if (visitedRoleIds.has(roleId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const current = permissionStore.getRoleById(roleId);
|
const current = inheritanceRoleList.value.find(item => item.id === roleId);
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -88,45 +287,202 @@ function hasChildRole(roleId: string, targetRoleId: string, visitedRoleIds = new
|
|||||||
return current.childRoleIdList.some(childRoleId => childRoleId === targetRoleId || hasChildRole(childRoleId, targetRoleId, visitedRoleIds));
|
return current.childRoleIdList.some(childRoleId => childRoleId === targetRoleId || hasChildRole(childRoleId, targetRoleId, visitedRoleIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isManagementChildRole(roleId: string, parentRoleId: string, visitedRoleIds = new Set<string>()): boolean {
|
||||||
|
if (!roleId || !parentRoleId || visitedRoleIds.has(roleId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = inheritanceRoleList.value.find(item => item.id === roleId);
|
||||||
|
if (!current?.parentRoleId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.parentRoleId === parentRoleId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitedRoleIds.add(roleId);
|
||||||
|
return isManagementChildRole(current.parentRoleId, parentRoleId, visitedRoleIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 页面跳转 ----------
|
||||||
|
|
||||||
|
async function toRolePermissionPage(): Promise<void> {
|
||||||
|
if (!formModel.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await router.push(`/management/permissions/roles/${formModel.id}/assign/p`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toRoleDelegationPage(): Promise<void> {
|
||||||
|
if (!formModel.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await router.push(`/management/permissions/roles/${formModel.id}/assign/d`);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 页面数据 ----------
|
// ---------- 页面数据 ----------
|
||||||
|
|
||||||
async function fillForm(): Promise<void> {
|
async function fillForm(): Promise<void> {
|
||||||
await permissionStore.loadAll();
|
await permissionStore.loadAll();
|
||||||
const current = await permissionStore.loadRoleDetail(routeId.value);
|
await loadAuthorizationScope();
|
||||||
|
if (isCreateMode.value) {
|
||||||
|
parentRoleOptions.value = candidateRoleList.value;
|
||||||
|
formModel.parentRoleId = parentRoleOptions.value[0]?.id || "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await reloadCurrentRole();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reloadCurrentRole(): Promise<void> {
|
||||||
|
const current = await permissionStore.loadRoleDetail(routeId.value || formModel.id);
|
||||||
|
currentRole.value = current.id ? current : undefined;
|
||||||
|
const childOptionIdSet = new Set(inheritanceRoleList.value
|
||||||
|
.filter(item => item.id !== current.id && isManagementChildRole(item.id, current.id) && !hasChildRole(item.id, current.id))
|
||||||
|
.map(item => item.id));
|
||||||
|
const childRoleIdList = current.childRoleIdList.filter(id => childOptionIdSet.has(id));
|
||||||
|
parentRoleOptions.value = await permissionStore.loadParentGrantableRoles(current.id);
|
||||||
|
if (current.parentRole?.id && !parentRoleOptions.value.some(item => item.id === current.parentRoleId)) {
|
||||||
|
parentRoleOptions.value.unshift({
|
||||||
|
...current.parentRole,
|
||||||
|
id: current.parentRole.id,
|
||||||
|
permissionIdList: current.parentRole.permissionIdList || current.parentRole.permissionList?.map(item => item.id) || [],
|
||||||
|
childRoleIdList: current.parentRole.childRoleIdList || current.parentRole.childRoleList?.map(item => item.id).filter((id): id is string => Boolean(id)) || [],
|
||||||
|
delegationPermissionIdList: current.parentRole.delegationPermissionIdList || current.parentRole.delegationPermissionList?.map(item => item.id) || []
|
||||||
|
});
|
||||||
|
}
|
||||||
Object.assign(formModel, {
|
Object.assign(formModel, {
|
||||||
id: current.id,
|
id: current.id,
|
||||||
moduleCode: current.moduleCode || moduleCode,
|
moduleCode: current.moduleCode || moduleCode,
|
||||||
code: current.code,
|
code: current.code,
|
||||||
name: current.name,
|
name: current.name,
|
||||||
description: current.description,
|
description: current.description,
|
||||||
childRoleIdList: [...current.childRoleIdList]
|
parentRoleId: current.parentRoleId || "",
|
||||||
|
childRoleIdList
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadAuthorizationScope(): Promise<void> {
|
||||||
|
candidateRoleList.value = isSystemAdmin.value ? permissionStore.roleList : await permissionStore.loadManageableRoles();
|
||||||
|
inheritanceRoleList.value = mergeRoleList(permissionStore.roleList, candidateRoleList.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeRoleList(...roleLists: Role[][]): Role[] {
|
||||||
|
const roleMap = new Map<string, Role>();
|
||||||
|
for (const role of roleLists.flat()) {
|
||||||
|
roleMap.set(role.id, role);
|
||||||
|
}
|
||||||
|
return Array.from(roleMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 保存 ----------
|
// ---------- 保存 ----------
|
||||||
|
|
||||||
async function saveRole(): Promise<void> {
|
async function saveBaseInfo(): Promise<void> {
|
||||||
const role = await permissionStore.upsertRole({
|
if (!canSaveBaseInfo.value || savingTarget.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
savingTarget.value = "base";
|
||||||
|
try {
|
||||||
|
if (isCreateMode.value && !formModel.parentRoleId) {
|
||||||
|
Toast.warning("请选择管理父级");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const draft: RoleDraft = {
|
||||||
id: formModel.id,
|
id: formModel.id,
|
||||||
moduleCode: moduleCode,
|
moduleCode: moduleCode,
|
||||||
code: formModel.code,
|
code: formModel.code,
|
||||||
name: formModel.name,
|
name: formModel.name,
|
||||||
description: formModel.description,
|
description: formModel.description,
|
||||||
permissionIdList: permissionStore.getRoleById(formModel.id)?.permissionIdList || [],
|
parentRoleId: isCreateMode.value ? formModel.parentRoleId : undefined,
|
||||||
childRoleIdList: [...formModel.childRoleIdList]
|
ownerType: isCreateMode.value ? "STORE" : undefined,
|
||||||
}, isCreateMode.value);
|
ownerId: isCreateMode.value ? await getCurrentStoreId() : undefined,
|
||||||
Toast.success("角色已保存");
|
childRoleIdList: isCreateMode.value ? [] : undefined
|
||||||
await router.push(isCreateMode.value ? "/management/permissions/roles" : `/management/permissions/roles/${role?.id || formModel.id}`);
|
};
|
||||||
|
const role = await permissionStore.upsertRole(draft, isCreateMode.value);
|
||||||
|
if (!isCreateMode.value && formModel.parentRoleId !== currentRole.value?.parentRoleId) {
|
||||||
|
await permissionStore.setRoleParentRole(formModel.id, formModel.parentRoleId);
|
||||||
|
}
|
||||||
|
Toast.success(isCreateMode.value ? "角色已创建" : "基础信息已保存");
|
||||||
|
await router.push(`/management/permissions/roles/${role?.id || formModel.id}`);
|
||||||
|
} catch (error) {
|
||||||
|
await reloadAfterError();
|
||||||
|
Toast.error(getErrorMessage(error, "角色保存失败"));
|
||||||
|
} finally {
|
||||||
|
savingTarget.value = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
async function saveRoleInherit(): Promise<void> {
|
||||||
await fillForm();
|
if (!formModel.id || !canEditRoleRelation.value || savingTarget.value) {
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
savingTarget.value = "inherit";
|
||||||
|
try {
|
||||||
|
const childRoleIdList = [...childRolePickerDraft.value];
|
||||||
|
await permissionStore.setRoleChildRoleList(formModel.id, childRoleIdList);
|
||||||
|
formModel.childRoleIdList = childRoleIdList;
|
||||||
|
childRolePickerVisible.value = false;
|
||||||
|
Toast.success("继承关系已保存");
|
||||||
|
await reloadCurrentRole();
|
||||||
|
} catch (error) {
|
||||||
|
await reloadAfterError();
|
||||||
|
childRolePickerDraft.value = [...formModel.childRoleIdList];
|
||||||
|
Toast.error(getErrorMessage(error, "继承关系保存失败"));
|
||||||
|
} finally {
|
||||||
|
savingTarget.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reloadAfterError(): Promise<void> {
|
||||||
|
await loadAuthorizationScope();
|
||||||
|
if (formModel.id || routeId.value) {
|
||||||
|
await reloadCurrentRole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCurrentStoreId(): Promise<string> {
|
||||||
|
const store = await currentStoreStore.loadLoginStore();
|
||||||
|
const storeId = store?.id || currentStoreStore.currentStoreId;
|
||||||
|
if (!storeId) {
|
||||||
|
throw new Error("未找到当前门店,无法创建门店角色");
|
||||||
|
}
|
||||||
|
return storeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorMessage(error: unknown, fallback: string): string {
|
||||||
|
if (error instanceof Error && error.message) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
if (typeof error === "string" && error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(fillForm);
|
||||||
|
|
||||||
|
function updateNavBar(): void {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
if (canShowSaveAction.value) {
|
||||||
|
navBarStore.setRightText("保存", "var(--td-brand-color)", saveBaseInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(canShowSaveAction, updateNavBar, { immediate: true });
|
||||||
|
|
||||||
|
onUnmounted(() => navBarStore.clearRight());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.role-edit {
|
.role-edit {
|
||||||
padding: calc(var(--app-nav-height) + 1rem) 0 5rem 0;
|
gap: 1rem;
|
||||||
|
display: flex;
|
||||||
|
padding: calc(var(--app-nav-height) + 1rem) 0 2rem 0;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.form {
|
.form {
|
||||||
border-radius: .75rem;
|
border-radius: .75rem;
|
||||||
@@ -136,69 +492,152 @@ onMounted(async () => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.children {
|
.permission-list {
|
||||||
gap: .75rem;
|
--source-direct-color: #2563EB;
|
||||||
display: flex;
|
--source-inherited-color: #14B8A6;
|
||||||
padding: 1rem;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.title {
|
:deep(.t-cell) {
|
||||||
gap: .25rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
span:first-child {
|
&__left {
|
||||||
font-weight: 700;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hint {
|
&__title {
|
||||||
color: #52607a;
|
flex: 1 1 0;
|
||||||
font-size: .8125rem;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__title-text {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__note {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 56%;
|
||||||
|
text-align: right;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
display: block;
|
||||||
|
border-radius: .25rem;
|
||||||
|
|
||||||
|
&-direct {
|
||||||
|
background: var(--source-direct-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-inherited {
|
||||||
|
background: var(--source-inherited-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-text {
|
||||||
|
color: var(--td-text-color-primary);
|
||||||
|
line-height: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group {
|
.group {
|
||||||
--td-checkbox-vertical-padding: 10px;
|
--td-checkbox-vertical-padding: 10px;
|
||||||
|
|
||||||
background: #FFF;
|
background: #fff;
|
||||||
border-radius: 1rem;
|
border-radius: .75rem;
|
||||||
|
|
||||||
.item:not(:last-child)::after {
|
.item:not(:last-child)::after {
|
||||||
content: ' ';
|
|
||||||
left: 16px;
|
left: 16px;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
content: ' ';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-bottom: 1px solid var(--td-component-stroke, var(--td-gray-color-3, #e7e7e7));
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
border-bottom: 1px solid var(--td-component-stroke, var(--td-gray-color-3, #e7e7e7));
|
||||||
-webkit-box-sizing: border-box;
|
-webkit-box-sizing: border-box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.save {
|
.role-picker {
|
||||||
left: 1rem;
|
display: flex;
|
||||||
right: 1rem;
|
height: 70vh;
|
||||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
padding-top: 1rem;
|
||||||
position: fixed;
|
border-radius: 1.25rem 1.25rem 0 0;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.picker-header {
|
||||||
|
padding: 0 1rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 4rem 1fr 4rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-body {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 0;
|
||||||
|
padding: .75rem 1rem env(safe-area-inset-bottom, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: .75rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(html.theme-dark) {
|
:global(html.theme-dark) {
|
||||||
.role-edit {
|
.role-edit {
|
||||||
.children {
|
.block {
|
||||||
.title .hint {
|
.title .hint {
|
||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group .card {
|
.group {
|
||||||
color: #e2e8f0;
|
background: #0f172a;
|
||||||
background: rgba(15, 23, 42, .72);
|
|
||||||
border-color: rgba(148, 163, 184, .16);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-list {
|
||||||
|
.source {
|
||||||
|
&-direct {
|
||||||
|
background: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-inherited {
|
||||||
|
background: #2dd4bf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-picker {
|
||||||
|
background: #0f172a;
|
||||||
|
|
||||||
|
.group {
|
||||||
|
background: #0f172a;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-role="permissionManagementRoleAccess" v-can="{ value: [corePermissionCodes.role.read, corePermissionCodes.permission.read], mode: 'and' }" class="role-list">
|
<div v-can="{ value: [corePermissionCodes.role.read, corePermissionCodes.permission.read], mode: 'and' }" class="role-list">
|
||||||
<t-pull-down-refresh
|
<t-pull-down-refresh
|
||||||
|
class="list-wrapper"
|
||||||
v-model="isRefreshing"
|
v-model="isRefreshing"
|
||||||
:refresh-timeout="30000"
|
:refresh-timeout="30000"
|
||||||
@refresh="refresh"
|
@refresh="refresh"
|
||||||
@@ -17,42 +18,77 @@
|
|||||||
arrow
|
arrow
|
||||||
hover
|
hover
|
||||||
@click="router.push(`/management/permissions/roles/${item.id}`)"
|
@click="router.push(`/management/permissions/roles/${item.id}`)"
|
||||||
/>
|
>
|
||||||
|
<template #title>
|
||||||
|
<div class="title" v-text="item.name" :style="{ paddingLeft: `${item.level * 1.25}rem` }" />
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
<template #right>
|
<template #right>
|
||||||
<t-button v-can="corePermissionCodes.role.delete" class="delete" theme="danger" @click="removeRole(item.id)">删除</t-button>
|
<t-button v-if="canDeleteRole(item)" v-can="corePermissionCodes.role.delete" class="delete" theme="danger" @click="removeRole(item.id)">删除</t-button>
|
||||||
</template>
|
</template>
|
||||||
</t-swipe-cell>
|
</t-swipe-cell>
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<t-empty v-else-if="!permissionStore.loading" description="暂无角色" />
|
<t-empty v-else-if="!permissionStore.loading" description="暂无角色" />
|
||||||
</t-pull-down-refresh>
|
</t-pull-down-refresh>
|
||||||
<t-button v-can="corePermissionCodes.role.create" class="add" theme="primary" type="button" @click="router.push('/management/permissions/roles/create')">
|
|
||||||
<template #icon>
|
|
||||||
<t-icon name="add" />
|
|
||||||
</template>
|
|
||||||
新增角色
|
|
||||||
</t-button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { Toast } from "tdesign-mobile-vue";
|
||||||
|
import { useUserStore } from "timi-web";
|
||||||
import type { Role } from "@/store/permissionSettingsStore";
|
import type { Role } from "@/store/permissionSettingsStore";
|
||||||
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||||
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/permission";
|
import { corePermissionCodes, coreRoleCodes } from "@/utils/permission";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "RoleList"
|
name: "RoleList"
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const navBarStore = useNavBarStore();
|
||||||
const permissionStore = usePermissionSettingsStore();
|
const permissionStore = usePermissionSettingsStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
const isRefreshing = ref(false);
|
const isRefreshing = ref(false);
|
||||||
|
const deletableRoleIdSet = ref(new Set<string>());
|
||||||
|
|
||||||
|
// ---------- 顶部导航 ----------
|
||||||
|
|
||||||
|
const canCreateRole = computed(() => userStore.hasPermissions(corePermissionCodes.role.create));
|
||||||
|
|
||||||
|
async function toCreate(): Promise<void> {
|
||||||
|
if (!canCreateRole.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await router.push("/management/permissions/roles/create");
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 列表数据 ----------
|
// ---------- 列表数据 ----------
|
||||||
|
|
||||||
const sortedRoles = computed<Role[]>(() => [...permissionStore.roleList].sort((left, right) => (left.name || "").localeCompare(right.name || "")));
|
const displayRoleList = ref<Role[]>([]);
|
||||||
|
const sortedRoles = computed<Array<Role & { level: number }>>(() => {
|
||||||
|
if (userStore.hasRoles(coreRoleCodes.admin)) {
|
||||||
|
return permissionStore.roleTree;
|
||||||
|
}
|
||||||
|
return buildManageableRoleTree();
|
||||||
|
});
|
||||||
|
|
||||||
async function removeRole(id: string): Promise<void> {
|
async function removeRole(id: string): Promise<void> {
|
||||||
|
try {
|
||||||
await permissionStore.removeRole(id);
|
await permissionStore.removeRole(id);
|
||||||
|
} catch (error) {
|
||||||
|
await refreshAuthorizationScope();
|
||||||
|
await permissionStore.loadAll(true);
|
||||||
|
Toast.error(getErrorMessage(error, "角色删除失败"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function canDeleteRole(role: Role): boolean {
|
||||||
|
if (userStore.hasRoles(coreRoleCodes.admin)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return role.builtin !== true && role.protectedRole !== true && deletableRoleIdSet.value.has(role.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 页面数据 ----------
|
// ---------- 页面数据 ----------
|
||||||
@@ -61,19 +97,87 @@ async function refresh(): Promise<void> {
|
|||||||
isRefreshing.value = true;
|
isRefreshing.value = true;
|
||||||
try {
|
try {
|
||||||
await permissionStore.loadAll(true);
|
await permissionStore.loadAll(true);
|
||||||
|
displayRoleList.value = getDisplayRoleList();
|
||||||
|
await refreshAuthorizationScope();
|
||||||
} finally {
|
} finally {
|
||||||
isRefreshing.value = false;
|
isRefreshing.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
if (canCreateRole.value) {
|
||||||
|
navBarStore.setRightText("添加", "var(--td-brand-color)", toCreate);
|
||||||
|
}
|
||||||
|
if (userStore.hasRoles(coreRoleCodes.admin)) {
|
||||||
await permissionStore.loadAll();
|
await permissionStore.loadAll();
|
||||||
|
}
|
||||||
|
await refreshAuthorizationScope();
|
||||||
|
if (!displayRoleList.value.length) {
|
||||||
|
displayRoleList.value = getDisplayRoleList();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
navBarStore.clearRight();
|
||||||
|
});
|
||||||
|
|
||||||
|
function getDisplayRoleList(): Role[] {
|
||||||
|
return userStore.hasRoles(coreRoleCodes.admin) ? permissionStore.roleList : permissionStore.manageableRoleList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildManageableRoleTree(): Array<Role & { level: number }> {
|
||||||
|
const roleIdSet = new Set(displayRoleList.value.map(item => item.id));
|
||||||
|
const roleMap = new Map(displayRoleList.value.map(item => [item.id, item]));
|
||||||
|
const roots = displayRoleList.value.filter(item => !item.parentRoleId || !roleIdSet.has(item.parentRoleId));
|
||||||
|
const buildTree = (roleId: string, level: number, visitedRoleIds: Set<string>): Array<Role & { level: number }> => {
|
||||||
|
const role = roleMap.get(roleId);
|
||||||
|
if (!role || visitedRoleIds.has(roleId)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextVisitedRoleIds = new Set(visitedRoleIds);
|
||||||
|
nextVisitedRoleIds.add(roleId);
|
||||||
|
const childRoleIdList = displayRoleList.value
|
||||||
|
.filter(item => item.parentRoleId === roleId)
|
||||||
|
.map(item => item.id);
|
||||||
|
return [{ ...role, level }, ...childRoleIdList.flatMap(childRoleId => buildTree(childRoleId, level + 1, nextVisitedRoleIds))];
|
||||||
|
};
|
||||||
|
return roots.flatMap(item => buildTree(item.id, 0, new Set<string>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshAuthorizationScope(): Promise<void> {
|
||||||
|
if (userStore.hasRoles(coreRoleCodes.admin)) {
|
||||||
|
deletableRoleIdSet.value = new Set(permissionStore.roleList.map(item => item.id));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const roleList = await permissionStore.loadManageableRoles();
|
||||||
|
displayRoleList.value = roleList;
|
||||||
|
deletableRoleIdSet.value = new Set(roleList.map(item => item.id).filter((id): id is string => Boolean(id)));
|
||||||
|
} catch {
|
||||||
|
deletableRoleIdSet.value = new Set<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorMessage(error: unknown, fallback: string): string {
|
||||||
|
if (error instanceof Error && error.message) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
if (typeof error === "string" && error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.role-list {
|
.role-list {
|
||||||
padding: calc(var(--app-nav-height) + 1rem) 0 6rem 0;
|
padding: calc(var(--app-nav-height) + 1rem) 0 1rem 0;
|
||||||
|
|
||||||
|
.list-wrapper {
|
||||||
|
padding-bottom: 6rem;
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
|
|
||||||
@@ -90,12 +194,6 @@ onMounted(async () => {
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.add {
|
|
||||||
left: 1rem;
|
|
||||||
right: 1rem;
|
|
||||||
bottom: calc(.75rem + env(safe-area-inset-bottom, 0));
|
|
||||||
position: fixed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-role="permissionManagementRoleAccess" v-can="{ value: [corePermissionCodes.role.read, corePermissionCodes.permission.read], mode: 'and' }" class="role-permission-assign">
|
<div
|
||||||
|
v-can="{ value: [corePermissionCodes.role.read, corePermissionCodes.permission.read, corePermissionCodes.rolePermission.read], mode: 'and' }"
|
||||||
|
class="role-permission-assign"
|
||||||
|
>
|
||||||
<div class="header bg-white">
|
<div class="header bg-white">
|
||||||
<t-dropdown-menu class="role-selector">
|
<t-dropdown-menu class="role-selector">
|
||||||
<t-dropdown-item
|
<t-dropdown-item
|
||||||
@@ -10,16 +13,16 @@
|
|||||||
@change="handleRoleChange"
|
@change="handleRoleChange"
|
||||||
/>
|
/>
|
||||||
</t-dropdown-menu>
|
</t-dropdown-menu>
|
||||||
|
</div>
|
||||||
<div class="inherit">
|
<div class="inherit">
|
||||||
<span class="label">显示子角色权限</span>
|
<span class="label">显示继承权限</span>
|
||||||
<t-switch class="switch" v-model="showChildRolePermissions" />
|
<t-switch class="switch" v-model="showChildRolePermissions" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<t-cell-group v-if="activeRole && grantablePermissionList.length" title="可配置的直接权限 P" theme="card">
|
||||||
<t-cell-group v-if="activeRole && permissionStore.permissionList.length" theme="card">
|
|
||||||
<t-cell
|
<t-cell
|
||||||
v-for="item in permissionStore.permissionList"
|
v-for="item in grantablePermissionList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:title="item.name"
|
:title="item.name || item.code"
|
||||||
:class="{ inherited: isPermissionInherited(item.id) }"
|
:class="{ inherited: isPermissionInherited(item.id) }"
|
||||||
>
|
>
|
||||||
<template #description>
|
<template #description>
|
||||||
@@ -27,21 +30,22 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #right-icon>
|
<template #right-icon>
|
||||||
<t-switch
|
<t-switch
|
||||||
:disabled="!canUpdateRole || isPermissionInherited(item.id)"
|
:disabled="!canUpdateRolePermission || !canEditActiveRole || isPermissionInherited(item.id)"
|
||||||
:model-value="isPermissionEnabled(item.id)"
|
:model-value="isPermissionEnabled(item.id)"
|
||||||
@change="value => setPermissionChecked(item.id, value === true)"
|
@change="value => setPermissionChecked(item.id, value === true)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</t-cell>
|
</t-cell>
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
<t-empty v-else-if="!permissionStore.loading" description="暂无可授权权限" />
|
<t-empty v-else-if="!permissionStore.loading" description="暂无可配置的直接权限 P" />
|
||||||
<t-button
|
<t-button
|
||||||
v-can="corePermissionCodes.role.update"
|
v-if="canEditActiveRole"
|
||||||
|
v-can="rolePermissionWriteAccess"
|
||||||
class="save"
|
class="save"
|
||||||
theme="primary"
|
theme="primary"
|
||||||
@click="savePermissionList"
|
@click="savePermissionList"
|
||||||
>
|
>
|
||||||
保存授权
|
保存 P
|
||||||
</t-button>
|
</t-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -49,9 +53,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
|
import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
|
||||||
import { Toast } from "tdesign-mobile-vue";
|
import { Toast } from "tdesign-mobile-vue";
|
||||||
import { useUserStore } from "timi-web";
|
import { type Permission, useUserStore } from "timi-web";
|
||||||
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
import { type Role, usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||||
import { corePermissionCodes, permissionManagementRoleAccess } from "@/utils/permission";
|
import { corePermissionCodes, coreRoleCodes } from "@/utils/permission";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "RolePermissionAssign"
|
name: "RolePermissionAssign"
|
||||||
@@ -63,14 +67,26 @@ const route = useRoute();
|
|||||||
|
|
||||||
// ---------- 角色选择 ----------
|
// ---------- 角色选择 ----------
|
||||||
|
|
||||||
const activeRoleId = ref(String(route.query.roleId || ""));
|
const activeRoleId = ref(String(route.query.roleId || route.params.id || ""));
|
||||||
const checkedPermissionIds = ref<string[]>([]);
|
const checkedPermissionIds = ref<string[]>([]);
|
||||||
|
const unmanagedPermissionIds = ref<string[]>([]);
|
||||||
|
const inheritedPermissionIds = ref<string[]>([]);
|
||||||
|
const grantablePermissionList = ref<Permission[]>([]);
|
||||||
|
const candidateRoleList = ref<Role[]>([]);
|
||||||
const showChildRolePermissions = ref(false);
|
const showChildRolePermissions = ref(false);
|
||||||
let permissionSyncSeq = 0;
|
let permissionSyncSeq = 0;
|
||||||
|
|
||||||
const activeRole = computed(() => permissionStore.getRoleById(activeRoleId.value));
|
const activeRole = computed(() => permissionStore.getRoleById(activeRoleId.value));
|
||||||
const activeRoleLabel = computed(() => activeRole.value ? `当前角色:${activeRole.value.name}` : "选择角色");
|
const activeRoleLabel = computed(() => activeRole.value ? `当前角色:${activeRole.value.name}` : "选择角色");
|
||||||
const canUpdateRole = computed(() => userStore.hasPermissions(corePermissionCodes.role.update));
|
const canUpdateRolePermission = computed(() => userStore.hasPermissions([
|
||||||
const roleOptions = computed<DropdownOption[]>(() => permissionStore.roleList
|
corePermissionCodes.rolePermission.create,
|
||||||
|
corePermissionCodes.rolePermission.delete
|
||||||
|
], "and"));
|
||||||
|
const isSystemAdmin = computed(() => userStore.hasRoles(coreRoleCodes.admin));
|
||||||
|
const canEditActiveRole = computed(() => Boolean(activeRole.value)
|
||||||
|
&& candidateRoleList.value.some(item => item.id === activeRoleId.value)
|
||||||
|
&& (isSystemAdmin.value || activeRole.value?.protectedRole !== true));
|
||||||
|
const roleOptions = computed<DropdownOption[]>(() => candidateRoleList.value
|
||||||
.filter(item => !!item.id)
|
.filter(item => !!item.id)
|
||||||
.map(item => ({
|
.map(item => ({
|
||||||
label: item.name || item.code,
|
label: item.name || item.code,
|
||||||
@@ -78,15 +94,18 @@ const roleOptions = computed<DropdownOption[]>(() => permissionStore.roleList
|
|||||||
disabled: false
|
disabled: false
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
const rolePermissionWriteAccess = {
|
||||||
|
value: [
|
||||||
|
corePermissionCodes.rolePermission.create,
|
||||||
|
corePermissionCodes.rolePermission.delete
|
||||||
|
],
|
||||||
|
mode: "and"
|
||||||
|
} as const;
|
||||||
|
|
||||||
// ---------- 权限状态 ----------
|
// ---------- 权限状态 ----------
|
||||||
|
|
||||||
const checkedPermissionIdSet = computed(() => new Set(checkedPermissionIds.value));
|
const checkedPermissionIdSet = computed(() => new Set(checkedPermissionIds.value));
|
||||||
const childRolePermissionIdSet = computed(() => {
|
const inheritedPermissionIdSet = computed(() => new Set(inheritedPermissionIds.value));
|
||||||
if (!activeRole.value) {
|
|
||||||
return new Set<string>();
|
|
||||||
}
|
|
||||||
return collectChildRolePermissionIds(activeRole.value.childRoleIdList, new Set<string>());
|
|
||||||
});
|
|
||||||
|
|
||||||
// ---------- 角色切换 ----------
|
// ---------- 角色切换 ----------
|
||||||
|
|
||||||
@@ -98,25 +117,52 @@ async function handleRoleChange(value: DropdownValue): Promise<void> {
|
|||||||
async function loadActiveRolePermissions(): Promise<void> {
|
async function loadActiveRolePermissions(): Promise<void> {
|
||||||
const roleId = activeRoleId.value;
|
const roleId = activeRoleId.value;
|
||||||
if (!roleId) {
|
if (!roleId) {
|
||||||
checkedPermissionIds.value = [];
|
clearPermissionState();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentSeq = permissionSyncSeq + 1;
|
const currentSeq = permissionSyncSeq + 1;
|
||||||
permissionSyncSeq = currentSeq;
|
permissionSyncSeq = currentSeq;
|
||||||
const role = await permissionStore.loadRoleDetail(roleId);
|
const [role, permissionList] = await Promise.all([
|
||||||
|
permissionStore.loadRoleDetail(roleId),
|
||||||
|
permissionStore.loadGrantableRolePermissions(roleId)
|
||||||
|
]);
|
||||||
if (currentSeq !== permissionSyncSeq || activeRoleId.value !== roleId) {
|
if (currentSeq !== permissionSyncSeq || activeRoleId.value !== roleId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkedPermissionIds.value = [...role.permissionIdList];
|
grantablePermissionList.value = permissionList;
|
||||||
|
const grantablePermissionIdSet = new Set(grantablePermissionList.value.map(item => item.id));
|
||||||
|
checkedPermissionIds.value = role.permissionIdList.filter(id => grantablePermissionIdSet.has(id));
|
||||||
|
unmanagedPermissionIds.value = role.permissionIdList.filter(id => !grantablePermissionIdSet.has(id));
|
||||||
|
inheritedPermissionIds.value = (role.allPermissionList || [])
|
||||||
|
.map(item => item.id)
|
||||||
|
.filter(id => grantablePermissionIdSet.has(id) && !checkedPermissionIdSet.value.has(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 保存 ----------
|
// ---------- 保存 ----------
|
||||||
|
|
||||||
async function savePermissionList(): Promise<void> {
|
async function savePermissionList(): Promise<void> {
|
||||||
await permissionStore.setRolePermissionList(activeRoleId.value, checkedPermissionIds.value);
|
if (!activeRoleId.value || !canEditActiveRole.value || !canUpdateRolePermission.value) {
|
||||||
Toast.success("授权已保存");
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await permissionStore.setRolePermissionList(activeRoleId.value, Array.from(new Set([
|
||||||
|
...checkedPermissionIds.value,
|
||||||
|
...unmanagedPermissionIds.value
|
||||||
|
])));
|
||||||
|
Toast.success("P 已保存");
|
||||||
|
await loadActiveRolePermissions();
|
||||||
|
} catch (error) {
|
||||||
|
clearPermissionState();
|
||||||
|
try {
|
||||||
|
await loadAuthorizationScope();
|
||||||
|
await loadActiveRolePermissions();
|
||||||
|
} finally {
|
||||||
|
Toast.error(getErrorMessage(error, "P 保存失败"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 权限勾选 ----------
|
// ---------- 权限勾选 ----------
|
||||||
@@ -126,7 +172,7 @@ function isPermissionChecked(permissionId: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isPermissionInherited(permissionId: string): boolean {
|
function isPermissionInherited(permissionId: string): boolean {
|
||||||
return showChildRolePermissions.value && !isPermissionChecked(permissionId) && childRolePermissionIdSet.value.has(permissionId);
|
return showChildRolePermissions.value && !isPermissionChecked(permissionId) && inheritedPermissionIdSet.value.has(permissionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPermissionEnabled(permissionId: string): boolean {
|
function isPermissionEnabled(permissionId: string): boolean {
|
||||||
@@ -134,7 +180,7 @@ function isPermissionEnabled(permissionId: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setPermissionChecked(permissionId: string, value: boolean): void {
|
function setPermissionChecked(permissionId: string, value: boolean): void {
|
||||||
if (!canUpdateRole.value || isPermissionInherited(permissionId)) {
|
if (!canUpdateRolePermission.value || !canEditActiveRole.value || isPermissionInherited(permissionId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,36 +194,35 @@ function setPermissionChecked(permissionId: string, value: boolean): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 子角色继承 ----------
|
function clearPermissionState(): void {
|
||||||
|
checkedPermissionIds.value = [];
|
||||||
function collectChildRolePermissionIds(roleIdList: string[], visitedRoleIdSet: Set<string>): Set<string> {
|
unmanagedPermissionIds.value = [];
|
||||||
const permissionIdSet = new Set<string>();
|
inheritedPermissionIds.value = [];
|
||||||
for (const roleId of roleIdList) {
|
grantablePermissionList.value = [];
|
||||||
if (visitedRoleIdSet.has(roleId)) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
visitedRoleIdSet.add(roleId);
|
// ---------- 授权范围 ----------
|
||||||
const role = permissionStore.getRoleById(roleId);
|
|
||||||
if (!role) {
|
async function loadAuthorizationScope(): Promise<void> {
|
||||||
continue;
|
const roleList = isSystemAdmin.value ? permissionStore.roleList : await permissionStore.loadManageableRoles();
|
||||||
|
candidateRoleList.value = roleList;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const permissionId of role.permissionIdList) {
|
function getErrorMessage(error: unknown, fallback: string): string {
|
||||||
permissionIdSet.add(permissionId);
|
if (error instanceof Error && error.message) {
|
||||||
|
return error.message;
|
||||||
}
|
}
|
||||||
|
if (typeof error === "string" && error) {
|
||||||
for (const permissionId of collectChildRolePermissionIds(role.childRoleIdList, visitedRoleIdSet)) {
|
return error;
|
||||||
permissionIdSet.add(permissionId);
|
|
||||||
}
|
}
|
||||||
}
|
return fallback;
|
||||||
return permissionIdSet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await permissionStore.loadAll();
|
await permissionStore.loadAll();
|
||||||
|
await loadAuthorizationScope();
|
||||||
if (!activeRoleId.value) {
|
if (!activeRoleId.value) {
|
||||||
activeRoleId.value = permissionStore.roleList[0]?.id || "";
|
activeRoleId.value = candidateRoleList.value[0]?.id || "";
|
||||||
}
|
}
|
||||||
await loadActiveRolePermissions();
|
await loadActiveRolePermissions();
|
||||||
});
|
});
|
||||||
@@ -197,19 +242,24 @@ onMounted(async () => {
|
|||||||
z-index: 20;
|
z-index: 20;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
background: #fff;
|
||||||
box-shadow: var(--td-shadow-1);
|
box-shadow: var(--td-shadow-1);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-around;
|
justify-content: center;
|
||||||
|
|
||||||
.role-selector::after {
|
.role-selector::after {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.inherit {
|
.inherit {
|
||||||
gap: .75rem;
|
gap: .75rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
margin: 0 1rem;
|
||||||
padding: .75rem 1rem;
|
padding: .75rem 1rem;
|
||||||
|
background: #fff;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
border-radius: .75rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
@@ -222,7 +272,6 @@ onMounted(async () => {
|
|||||||
--td-switch-checked-color: #14b8a6;
|
--td-switch-checked-color: #14b8a6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.t-cell__title) {
|
:deep(.t-cell__title) {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -272,18 +321,13 @@ onMounted(async () => {
|
|||||||
|
|
||||||
:global(html.theme-dark) {
|
:global(html.theme-dark) {
|
||||||
.role-permission-assign {
|
.role-permission-assign {
|
||||||
.role-menu {
|
.header,
|
||||||
background: #0f172a;
|
|
||||||
|
|
||||||
.inherit {
|
.inherit {
|
||||||
.label {
|
background: #0f172a;
|
||||||
color: #cbd5e1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch {
|
.inherit .label {
|
||||||
--td-switch-checked-color: #0f766e;
|
color: #cbd5e1;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.inherited {
|
.inherited {
|
||||||
@@ -298,4 +342,3 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<t-cell-group v-if="managedUser.user" theme="card">
|
<t-cell-group v-if="managedUser.user" theme="card">
|
||||||
<t-cell
|
<t-cell
|
||||||
:title="managedUser.user.name"
|
:title="managedUser.user.name"
|
||||||
:description="getRoleDisplayName(managedUser.user.roleList?.[0], permissionStore.roleList)"
|
:description="getRoleDisplayName(getManagedUserRoleList(managedUser)[0], permissionStore.roleList)"
|
||||||
>
|
>
|
||||||
<template #leftIcon>
|
<template #leftIcon>
|
||||||
<t-image
|
<t-image
|
||||||
@@ -74,7 +74,7 @@ import type { ManagedUser } from "@/types/ManagedUser";
|
|||||||
import type { EventItem } from "@/types/EventItem";
|
import type { EventItem } from "@/types/EventItem";
|
||||||
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||||
import { buildFallbackAvatar } from "@/utils/avatar";
|
import { buildFallbackAvatar } from "@/utils/avatar";
|
||||||
import { buildEffectiveRoleCodeSet, getRoleDisplayName } from "@/utils/role";
|
import { buildEffectiveRoleCodeSet, getManagedUserRoleList, getRoleDisplayName } from "@/utils/role";
|
||||||
import { permissionCodes } from "@/utils/permission";
|
import { permissionCodes } from "@/utils/permission";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -99,7 +99,7 @@ const currentUser = computed(() => {
|
|||||||
return {
|
return {
|
||||||
id: item.id || "",
|
id: item.id || "",
|
||||||
name: user?.nick || user?.name || "未命名用户",
|
name: user?.nick || user?.name || "未命名用户",
|
||||||
role: getRoleDisplayName(item.roleList?.[0], permissionStore.roleList),
|
role: getRoleDisplayName(getManagedUserRoleList(item)[0], permissionStore.roleList),
|
||||||
avatar: buildUserAvatar(user),
|
avatar: buildUserAvatar(user),
|
||||||
mobile: user?.phoneNo || "",
|
mobile: user?.phoneNo || "",
|
||||||
lastLoginAt: user?.lastLoginAt
|
lastLoginAt: user?.lastLoginAt
|
||||||
@@ -146,7 +146,7 @@ function isUser(user?: Partial<User>): user is User {
|
|||||||
// ---------- 事件范围 ----------
|
// ---------- 事件范围 ----------
|
||||||
|
|
||||||
function canEventItem(action: EventItem, item: ManagedUser): boolean {
|
function canEventItem(action: EventItem, item: ManagedUser): boolean {
|
||||||
const userRoleCodeSet = buildEffectiveRoleCodeSet(item, permissionStore.roleList);
|
const userRoleCodeSet = buildEffectiveRoleCodeSet(item);
|
||||||
const roleCodeList = action.roleCodeList || [];
|
const roleCodeList = action.roleCodeList || [];
|
||||||
if (!roleCodeList.length) {
|
if (!roleCodeList.length) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import type { ManagedUser } from "@/types/ManagedUser";
|
|||||||
import type { EventItem } from "@/types/EventItem";
|
import type { EventItem } from "@/types/EventItem";
|
||||||
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
import { usePermissionSettingsStore } from "@/store/permissionSettingsStore";
|
||||||
import { buildFallbackAvatar } from "@/utils/avatar";
|
import { buildFallbackAvatar } from "@/utils/avatar";
|
||||||
import { buildEffectiveRoleCodeSet, getRoleDisplayName } from "@/utils/role";
|
import { buildEffectiveRoleCodeSet, getManagedUserRoleList, getRoleDisplayName } from "@/utils/role";
|
||||||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -118,7 +118,7 @@ function getUserName(item: ManagedUser): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getRoleName(item: ManagedUser): string {
|
function getRoleName(item: ManagedUser): string {
|
||||||
return getRoleDisplayName(item.roleList?.[0], permissionStore.roleList);
|
return getRoleDisplayName(getManagedUserRoleList(item)[0], permissionStore.roleList);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActionScope(item: ManagedUser): string {
|
function getActionScope(item: ManagedUser): string {
|
||||||
@@ -131,7 +131,7 @@ function getActionScope(item: ManagedUser): string {
|
|||||||
// ---------- 事件范围 ----------
|
// ---------- 事件范围 ----------
|
||||||
|
|
||||||
function canEventItem(action: EventItem, item: ManagedUser): boolean {
|
function canEventItem(action: EventItem, item: ManagedUser): boolean {
|
||||||
const userRoleCodeSet = buildEffectiveRoleCodeSet(item, permissionStore.roleList);
|
const userRoleCodeSet = buildEffectiveRoleCodeSet(item);
|
||||||
const roleCodeList = action.roleCodeList || [];
|
const roleCodeList = action.roleCodeList || [];
|
||||||
if (!roleCodeList.length) {
|
if (!roleCodeList.length) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -49,23 +49,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
|
import type { DropdownOption, DropdownValue } from "tdesign-mobile-vue";
|
||||||
import { Toast } from "tdesign-mobile-vue";
|
import { Toast } from "tdesign-mobile-vue";
|
||||||
import { RoleAPI, type ModuleCode, type Role, useUserStore } from "timi-web";
|
import { RoleAPI, type ModuleCode, type Role as UserRole, useUserStore } from "timi-web";
|
||||||
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
import ManagedUserAPI from "@/api/ManagedUserAPI";
|
||||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||||
|
import type { Role } from "@/store/permissionSettingsStore";
|
||||||
import type { ManagedUser } from "@/types/ManagedUser";
|
import type { ManagedUser } from "@/types/ManagedUser";
|
||||||
import { corePermissionCodes, gaoRoleCodes } from "@/utils/permission";
|
import { corePermissionCodes, gaoRoleCodes } from "@/utils/permission";
|
||||||
|
|
||||||
type ManagedRole = Role & {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "UserRoleAssign"
|
name: "UserRoleAssign"
|
||||||
});
|
});
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const moduleCode: ModuleCode = "GAO";
|
const moduleCode: ModuleCode = "GAO";
|
||||||
const pageSize = 1000;
|
|
||||||
const allStoreFilterValue = "__ALL_STORES__";
|
const allStoreFilterValue = "__ALL_STORES__";
|
||||||
|
|
||||||
// ---------- 用户选择 ----------
|
// ---------- 用户选择 ----------
|
||||||
@@ -73,7 +69,7 @@ const allStoreFilterValue = "__ALL_STORES__";
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const currentStoreStore = useCurrentStoreStore();
|
const currentStoreStore = useCurrentStoreStore();
|
||||||
const userList = ref<ManagedUser[]>([]);
|
const userList = ref<ManagedUser[]>([]);
|
||||||
const roleList = ref<ManagedRole[]>([]);
|
const roleList = ref<Role[]>([]);
|
||||||
const routeManagedUserId = computed(() => String(route.params.id || route.query.managedUserId || ""));
|
const routeManagedUserId = computed(() => String(route.params.id || route.query.managedUserId || ""));
|
||||||
const activeManagedUserId = ref(routeManagedUserId.value);
|
const activeManagedUserId = ref(routeManagedUserId.value);
|
||||||
const selectedStoreId = ref(allStoreFilterValue);
|
const selectedStoreId = ref(allStoreFilterValue);
|
||||||
@@ -197,7 +193,10 @@ async function loadActiveUserRoles(): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkedRoleIds.value = roles.map(getRoleId).filter((item): item is string => !!item);
|
const availableRoleIdSet = new Set(roleList.value.map(item => item.id));
|
||||||
|
checkedRoleIds.value = roles
|
||||||
|
.map(getRoleId)
|
||||||
|
.filter((item): item is string => !!item && availableRoleIdSet.has(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 保存 ----------
|
// ---------- 保存 ----------
|
||||||
@@ -216,7 +215,16 @@ async function saveRoleList(): Promise<void> {
|
|||||||
moduleCode: moduleCode,
|
moduleCode: moduleCode,
|
||||||
roleIdList: [...checkedRoleIds.value]
|
roleIdList: [...checkedRoleIds.value]
|
||||||
});
|
});
|
||||||
|
await loadActiveUserRoles();
|
||||||
Toast.success("用户角色已保存");
|
Toast.success("用户角色已保存");
|
||||||
|
} catch (error) {
|
||||||
|
checkedRoleIds.value = [];
|
||||||
|
try {
|
||||||
|
await reloadAuthorizationScope();
|
||||||
|
await loadActiveUserRoles();
|
||||||
|
} finally {
|
||||||
|
Toast.error(getErrorMessage(error, "用户角色授权失败"));
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isSaving.value = false;
|
isSaving.value = false;
|
||||||
}
|
}
|
||||||
@@ -230,18 +238,21 @@ function getUserName(item: ManagedUser): string {
|
|||||||
|
|
||||||
// ---------- 角色列表 ----------
|
// ---------- 角色列表 ----------
|
||||||
|
|
||||||
async function loadRoleList(): Promise<ManagedRole[]> {
|
async function loadRoleList(): Promise<Role[]> {
|
||||||
const result = await RoleAPI.list({
|
const result = await RoleAPI.grantableList(moduleCode);
|
||||||
index: 0,
|
return (result || [])
|
||||||
size: pageSize,
|
.filter((role): role is UserRole & { id: string } => Boolean(role.id))
|
||||||
equalsExample: {
|
.map(role => ({
|
||||||
moduleCode: moduleCode
|
...role,
|
||||||
},
|
id: role.id,
|
||||||
orderMap: {
|
permissionIdList: role.permissionIdList || role.permissionList?.map(item => item.id) || [],
|
||||||
code: "ASC"
|
childRoleIdList: role.childRoleIdList || role.childRoleList?.map(item => item.id).filter((id): id is string => Boolean(id)) || [],
|
||||||
|
delegationPermissionIdList: role.delegationPermissionIdList || role.delegationPermissionList?.map(item => item.id) || []
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
});
|
|
||||||
return (result.list || []).filter((role): role is ManagedRole => Boolean(role.id));
|
async function reloadAuthorizationScope(): Promise<void> {
|
||||||
|
roleList.value = await loadRoleList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 角色勾选 ----------
|
// ---------- 角色勾选 ----------
|
||||||
@@ -250,7 +261,7 @@ function isRoleChecked(roleId: string): boolean {
|
|||||||
return checkedRoleIds.value.includes(roleId);
|
return checkedRoleIds.value.includes(roleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRoleId(role: Role | string): string {
|
function getRoleId(role: UserRole | string): string {
|
||||||
if (typeof role !== "string") {
|
if (typeof role !== "string") {
|
||||||
return role.id || "";
|
return role.id || "";
|
||||||
}
|
}
|
||||||
@@ -259,6 +270,16 @@ function getRoleId(role: Role | string): string {
|
|||||||
return fullRole?.id || "";
|
return fullRole?.id || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getErrorMessage(error: unknown, fallback: string): string {
|
||||||
|
if (error instanceof Error && error.message) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
if (typeof error === "string" && error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
function setRoleChecked(roleId: string, value: boolean): void {
|
function setRoleChecked(roleId: string, value: boolean): void {
|
||||||
if (value && !checkedRoleIds.value.includes(roleId)) {
|
if (value && !checkedRoleIds.value.includes(roleId)) {
|
||||||
checkedRoleIds.value.push(roleId);
|
checkedRoleIds.value.push(roleId);
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ import { UserAPI, useUserStore } from "timi-web";
|
|||||||
import { Toast } from "tdesign-mobile-vue";
|
import { Toast } from "tdesign-mobile-vue";
|
||||||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||||||
import { buildFallbackAvatar } from "@/utils/avatar";
|
import { buildFallbackAvatar } from "@/utils/avatar";
|
||||||
import { corePermissionCodes, coreRoleCodes, gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
import { corePermissionCodes, gaoRoleCodes, gaoStoreScopePermissionCodes, permissionCodes } from "@/utils/permission";
|
||||||
|
|
||||||
type UserAvatarSource = Parameters<typeof UserAPI.getAvatarURL>[0];
|
type UserAvatarSource = Parameters<typeof UserAPI.getAvatarURL>[0];
|
||||||
type PickerValue = string | number;
|
type PickerValue = string | number;
|
||||||
@@ -107,12 +107,10 @@ const currentUser = computed(() => userStore.loginUser.user);
|
|||||||
|
|
||||||
const currentUserAvatar = computed(() => buildUserAvatar(currentUser.value));
|
const currentUserAvatar = computed(() => buildUserAvatar(currentUser.value));
|
||||||
const canSelectStore = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
const canSelectStore = computed(() => userStore.hasRoles(gaoRoleCodes.globalManager));
|
||||||
const canLoadLoginStore = computed(() => userStore.hasRoles([gaoRoleCodes.storeManager, gaoRoleCodes.employee], "or"));
|
const canLoadLoginStore = computed(() => userStore.hasPermissions(gaoStoreScopePermissionCodes, "or"));
|
||||||
const canViewStore = computed(() => canSelectStore.value || canLoadLoginStore.value);
|
const canViewStore = computed(() => canSelectStore.value || canLoadLoginStore.value);
|
||||||
const canManageStore = computed(() => userStore.hasRoles([gaoRoleCodes.globalManager, gaoRoleCodes.storeManager], "or"));
|
const canManageStore = computed(() => userStore.hasRoles([gaoRoleCodes.globalManager, gaoRoleCodes.storeManager], "or"));
|
||||||
const canManagePermission = computed(() => (
|
const canManagePermission = computed(() => userStore.hasPermissions([
|
||||||
userStore.hasRoles(coreRoleCodes.admin) || userStore.hasRoles(coreRoleCodes.userRoleAdmin)
|
|
||||||
) && userStore.hasPermissions([
|
|
||||||
corePermissionCodes.role.read,
|
corePermissionCodes.role.read,
|
||||||
corePermissionCodes.permission.read
|
corePermissionCodes.permission.read
|
||||||
], "and"));
|
], "and"));
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import RoleEdit from "@/pages/management/RoleEdit.vue";
|
|||||||
import PermissionList from "@/pages/management/PermissionList.vue";
|
import PermissionList from "@/pages/management/PermissionList.vue";
|
||||||
import PermissionEdit from "@/pages/management/PermissionEdit.vue";
|
import PermissionEdit from "@/pages/management/PermissionEdit.vue";
|
||||||
import RolePermissionAssign from "@/pages/management/RolePermissionAssign.vue";
|
import RolePermissionAssign from "@/pages/management/RolePermissionAssign.vue";
|
||||||
|
import RoleDelegationPermissionAssign from "@/pages/management/RoleDelegationPermissionAssign.vue";
|
||||||
import UserRoleAssign from "@/pages/management/UserRoleAssign.vue";
|
import UserRoleAssign from "@/pages/management/UserRoleAssign.vue";
|
||||||
|
|
||||||
const permissionRoutes: RouteRecordRaw[] = [
|
const permissionRoutes: RouteRecordRaw[] = [
|
||||||
@@ -64,6 +65,34 @@ const permissionRoutes: RouteRecordRaw[] = [
|
|||||||
darkBodyBackground: "#10161f"
|
darkBodyBackground: "#10161f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/management/permissions/roles/:id/assign/p",
|
||||||
|
name: "RolePermissionAssignFromRole",
|
||||||
|
component: RolePermissionAssign,
|
||||||
|
meta: {
|
||||||
|
depth: 6,
|
||||||
|
navBarVisible: true,
|
||||||
|
navBarCanBack: true,
|
||||||
|
navBarTitle: "角色直接权限 P",
|
||||||
|
tabBarVisible: false,
|
||||||
|
bodyBackground: "#F5F5F8",
|
||||||
|
darkBodyBackground: "#10161f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/management/permissions/roles/:id/assign/d",
|
||||||
|
name: "RoleDelegationPermissionAssignFromRole",
|
||||||
|
component: RoleDelegationPermissionAssign,
|
||||||
|
meta: {
|
||||||
|
depth: 6,
|
||||||
|
navBarVisible: true,
|
||||||
|
navBarCanBack: true,
|
||||||
|
navBarTitle: "角色转授权限 D",
|
||||||
|
tabBarVisible: false,
|
||||||
|
bodyBackground: "#F5F5F8",
|
||||||
|
darkBodyBackground: "#10161f"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/management/permissions/items",
|
path: "/management/permissions/items",
|
||||||
name: "PermissionList",
|
name: "PermissionList",
|
||||||
@@ -108,13 +137,31 @@ const permissionRoutes: RouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/management/permissions/assign",
|
path: "/management/permissions/assign",
|
||||||
|
redirect: "/management/permissions/assign/p"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/management/permissions/assign/p",
|
||||||
name: "RolePermissionAssign",
|
name: "RolePermissionAssign",
|
||||||
component: RolePermissionAssign,
|
component: RolePermissionAssign,
|
||||||
meta: {
|
meta: {
|
||||||
depth: 4,
|
depth: 4,
|
||||||
navBarVisible: true,
|
navBarVisible: true,
|
||||||
navBarCanBack: true,
|
navBarCanBack: true,
|
||||||
navBarTitle: "角色授权权限",
|
navBarTitle: "角色直接权限 P",
|
||||||
|
tabBarVisible: false,
|
||||||
|
bodyBackground: "#F5F5F8",
|
||||||
|
darkBodyBackground: "#10161f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/management/permissions/assign/d",
|
||||||
|
name: "RoleDelegationPermissionAssign",
|
||||||
|
component: RoleDelegationPermissionAssign,
|
||||||
|
meta: {
|
||||||
|
depth: 4,
|
||||||
|
navBarVisible: true,
|
||||||
|
navBarCanBack: true,
|
||||||
|
navBarTitle: "角色转授权限 D",
|
||||||
tabBarVisible: false,
|
tabBarVisible: false,
|
||||||
bodyBackground: "#F5F5F8",
|
bodyBackground: "#F5F5F8",
|
||||||
darkBodyBackground: "#10161f"
|
darkBodyBackground: "#10161f"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Toast } from "tdesign-mobile-vue";
|
|||||||
import { useUserStore } from "timi-web";
|
import { useUserStore } from "timi-web";
|
||||||
import StoreAPI from "@/api/StoreAPI";
|
import StoreAPI from "@/api/StoreAPI";
|
||||||
import type { Store } from "@/types/Store";
|
import type { Store } from "@/types/Store";
|
||||||
import { gaoRoleCodes } from "@/utils/permission";
|
import { gaoRoleCodes, gaoStoreScopePermissionCodes } from "@/utils/permission";
|
||||||
import { getCurrentStoreId, readStoredCurrentStoreId, setCurrentStoreId, setLoginStoreId } from "@/utils/store";
|
import { getCurrentStoreId, readStoredCurrentStoreId, setCurrentStoreId, setLoginStoreId } from "@/utils/store";
|
||||||
|
|
||||||
export const useCurrentStoreStore = defineStore("currentStore", () => {
|
export const useCurrentStoreStore = defineStore("currentStore", () => {
|
||||||
@@ -86,8 +86,8 @@ export const useCurrentStoreStore = defineStore("currentStore", () => {
|
|||||||
|
|
||||||
async function loadAfterLogin(): Promise<void> {
|
async function loadAfterLogin(): Promise<void> {
|
||||||
const isGlobalManager = userStore.hasRoles(gaoRoleCodes.globalManager);
|
const isGlobalManager = userStore.hasRoles(gaoRoleCodes.globalManager);
|
||||||
const hasStoreRole = userStore.hasRoles([gaoRoleCodes.storeManager, gaoRoleCodes.employee], "or");
|
const hasStoreScope = userStore.hasPermissions(gaoStoreScopePermissionCodes, "or");
|
||||||
if (!isGlobalManager && !hasStoreRole) {
|
if (!isGlobalManager && !hasStoreScope) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,40 +3,27 @@ import {
|
|||||||
PermissionAPI,
|
PermissionAPI,
|
||||||
RoleAPI,
|
RoleAPI,
|
||||||
type ModuleCode,
|
type ModuleCode,
|
||||||
type Permission as UserPermission,
|
|
||||||
type PermissionPayload,
|
type PermissionPayload,
|
||||||
type Role as UserRole,
|
type Role as UserRole
|
||||||
type RolePayload
|
|
||||||
} from "timi-web";
|
} from "timi-web";
|
||||||
|
import type {
|
||||||
|
Permission,
|
||||||
|
PermissionDraft,
|
||||||
|
Role,
|
||||||
|
RoleDraft,
|
||||||
|
RolePayload
|
||||||
|
} from "@/types/PermissionSettings";
|
||||||
|
|
||||||
export const moduleCode: ModuleCode = "GAO";
|
export const moduleCode: ModuleCode = "GAO";
|
||||||
const pageSize = 1000;
|
const pageSize = 1000;
|
||||||
|
|
||||||
export type Permission = UserPermission;
|
export type {
|
||||||
export type Role = Omit<UserRole, "id" | "permissionIdList" | "childRoleIdList"> & {
|
Permission,
|
||||||
id: string;
|
PermissionDraft,
|
||||||
permissionIdList: string[];
|
Role,
|
||||||
childRoleIdList: string[];
|
RoleDraft,
|
||||||
};
|
RolePayload
|
||||||
|
} from "@/types/PermissionSettings";
|
||||||
export type RoleDraft = {
|
|
||||||
id?: string;
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
nameLangId?: string;
|
|
||||||
moduleCode?: ModuleCode;
|
|
||||||
permissionIdList: string[];
|
|
||||||
childRoleIdList: string[];
|
|
||||||
};
|
|
||||||
export type PermissionDraft = {
|
|
||||||
id?: string;
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
nameLangId?: string;
|
|
||||||
moduleCode?: ModuleCode;
|
|
||||||
};
|
|
||||||
|
|
||||||
function normalizeRole(role: UserRole): Role {
|
function normalizeRole(role: UserRole): Role {
|
||||||
return {
|
return {
|
||||||
@@ -44,8 +31,10 @@ function normalizeRole(role: UserRole): Role {
|
|||||||
id: role.id || "",
|
id: role.id || "",
|
||||||
moduleCode: role.moduleCode || moduleCode,
|
moduleCode: role.moduleCode || moduleCode,
|
||||||
description: role.description || "",
|
description: role.description || "",
|
||||||
|
parentRoleId: role.parentRoleId || role.parentRole?.id,
|
||||||
permissionIdList: role.permissionIdList || role.permissionList?.map(item => item.id) || [],
|
permissionIdList: role.permissionIdList || role.permissionList?.map(item => item.id) || [],
|
||||||
childRoleIdList: role.childRoleIdList || role.childRoleList?.map(item => item.id).filter((id): id is string => Boolean(id)) || []
|
childRoleIdList: role.childRoleIdList || role.childRoleList?.map(item => item.id).filter((id): id is string => Boolean(id)) || [],
|
||||||
|
delegationPermissionIdList: role.delegationPermissionIdList || role.delegationPermissionList?.map((item: Permission) => item.id) || []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +46,8 @@ function createEmptyRole(): Role {
|
|||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
permissionIdList: [],
|
permissionIdList: [],
|
||||||
childRoleIdList: []
|
childRoleIdList: [],
|
||||||
|
delegationPermissionIdList: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,13 +64,15 @@ function createEmptyPermission(): Permission {
|
|||||||
export const usePermissionSettingsStore = defineStore("permission-settings", () => {
|
export const usePermissionSettingsStore = defineStore("permission-settings", () => {
|
||||||
const roleList = ref<Role[]>([]);
|
const roleList = ref<Role[]>([]);
|
||||||
const permissionList = ref<Permission[]>([]);
|
const permissionList = ref<Permission[]>([]);
|
||||||
|
const manageableRoleList = ref<Role[]>([]);
|
||||||
|
const grantablePermissionList = ref<Permission[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const loaded = ref(false);
|
const loaded = ref(false);
|
||||||
let loadTask: Promise<void> | null = null;
|
let loadTask: Promise<void> | null = null;
|
||||||
|
|
||||||
const roleTree = computed<Array<Role & { level: number }>>(() => {
|
const roleTree = computed<Array<Role & { level: number }>>(() => {
|
||||||
const childIdSet = new Set(roleList.value.flatMap(item => item.childRoleIdList));
|
const roleIdSet = new Set(roleList.value.map(item => item.id));
|
||||||
const roots = roleList.value.filter(item => !childIdSet.has(item.id));
|
const roots = roleList.value.filter(item => !item.parentRoleId || !roleIdSet.has(item.parentRoleId));
|
||||||
return roots.flatMap(item => buildRoleTree(item.id, 0, new Set<string>()));
|
return roots.flatMap(item => buildRoleTree(item.id, 0, new Set<string>()));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -143,6 +135,38 @@ export const usePermissionSettingsStore = defineStore("permission-settings", ()
|
|||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- 授权范围 ----------
|
||||||
|
|
||||||
|
async function loadManageableRoles(): Promise<Role[]> {
|
||||||
|
manageableRoleList.value = [];
|
||||||
|
const list = await RoleAPI.manageableList(moduleCode);
|
||||||
|
manageableRoleList.value = (list || []).map(normalizeRole);
|
||||||
|
return manageableRoleList.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadGrantablePermissions(targetModuleCode: ModuleCode = moduleCode): Promise<Permission[]> {
|
||||||
|
grantablePermissionList.value = [];
|
||||||
|
const list = await PermissionAPI.grantableList(targetModuleCode);
|
||||||
|
grantablePermissionList.value = list || [];
|
||||||
|
return grantablePermissionList.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadGrantableRolePermissions(roleId: string): Promise<Permission[]> {
|
||||||
|
return await RoleAPI.grantablePermissionList(roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadDelegationPermissions(roleId: string): Promise<Permission[]> {
|
||||||
|
return await RoleAPI.delegationList(roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadGrantableDelegationPermissions(roleId: string): Promise<Permission[]> {
|
||||||
|
return await RoleAPI.grantableDelegationList(roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadParentGrantableRoles(roleId: string): Promise<Role[]> {
|
||||||
|
return (await RoleAPI.parentGrantableList(roleId)).map(normalizeRole);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadPermissionDetail(id: string): Promise<Permission> {
|
async function loadPermissionDetail(id: string): Promise<Permission> {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return createEmptyPermission();
|
return createEmptyPermission();
|
||||||
@@ -185,10 +209,13 @@ export const usePermissionSettingsStore = defineStore("permission-settings", ()
|
|||||||
const nextVisitedRoleIds = new Set(visitedRoleIds);
|
const nextVisitedRoleIds = new Set(visitedRoleIds);
|
||||||
nextVisitedRoleIds.add(roleId);
|
nextVisitedRoleIds.add(roleId);
|
||||||
|
|
||||||
|
const childRoleIdList = roleList.value
|
||||||
|
.filter(item => item.parentRoleId === roleId)
|
||||||
|
.map(item => item.id);
|
||||||
return [{
|
return [{
|
||||||
...current,
|
...current,
|
||||||
level
|
level
|
||||||
}, ...current.childRoleIdList.flatMap(childRoleId => buildRoleTree(childRoleId, level + 1, nextVisitedRoleIds))];
|
}, ...childRoleIdList.flatMap(childRoleId => buildRoleTree(childRoleId, level + 1, nextVisitedRoleIds))];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRoleById(id: string): Role | undefined {
|
function getRoleById(id: string): Role | undefined {
|
||||||
@@ -209,17 +236,13 @@ export const usePermissionSettingsStore = defineStore("permission-settings", ()
|
|||||||
|
|
||||||
function getInheritedPermissionList(role: Role): Permission[] {
|
function getInheritedPermissionList(role: Role): Permission[] {
|
||||||
const permissionMap = new Map<string, Permission>();
|
const permissionMap = new Map<string, Permission>();
|
||||||
const allPermissionList = role.allPermissionList || [];
|
const permissionList = role.allPermissionList?.length
|
||||||
for (const permission of [...getRolePermissionList(role), ...allPermissionList]) {
|
? role.allPermissionList
|
||||||
|
: getRolePermissionList(role);
|
||||||
|
for (const permission of permissionList) {
|
||||||
permissionMap.set(permission.id, permission);
|
permissionMap.set(permission.id, permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const childRole of getChildRoles(role)) {
|
|
||||||
for (const permission of getRolePermissionList(childRole)) {
|
|
||||||
permissionMap.set(permission.id, permission);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(permissionMap.values());
|
return Array.from(permissionMap.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,28 +250,38 @@ export const usePermissionSettingsStore = defineStore("permission-settings", ()
|
|||||||
|
|
||||||
async function upsertRole(draft: RoleDraft, createMode: boolean): Promise<Role | undefined> {
|
async function upsertRole(draft: RoleDraft, createMode: boolean): Promise<Role | undefined> {
|
||||||
const current = getRoleById(draft.id || "");
|
const current = getRoleById(draft.id || "");
|
||||||
|
const code = draft.code.trim();
|
||||||
|
const name = draft.name.trim();
|
||||||
|
const description = draft.description.trim();
|
||||||
const payload: RolePayload = {
|
const payload: RolePayload = {
|
||||||
...draft,
|
...draft,
|
||||||
moduleCode: moduleCode,
|
moduleCode: moduleCode,
|
||||||
code: draft.code.trim(),
|
code,
|
||||||
nameLangId: current?.nameLangId,
|
nameLangId: current?.nameLangId,
|
||||||
name: draft.name.trim(),
|
name,
|
||||||
description: draft.description.trim(),
|
description
|
||||||
permissionIdList: [...draft.permissionIdList],
|
|
||||||
childRoleIdList: [...draft.childRoleIdList]
|
|
||||||
};
|
};
|
||||||
|
if (draft.permissionIdList !== undefined) {
|
||||||
|
payload.permissionIdList = [...draft.permissionIdList];
|
||||||
|
}
|
||||||
|
if (draft.childRoleIdList !== undefined) {
|
||||||
|
payload.childRoleIdList = [...draft.childRoleIdList];
|
||||||
|
}
|
||||||
|
|
||||||
if (createMode) {
|
if (createMode) {
|
||||||
await RoleAPI.create({
|
await RoleAPI.create({
|
||||||
moduleCode: payload.moduleCode,
|
moduleCode: moduleCode,
|
||||||
code: payload.code,
|
code,
|
||||||
name: payload.name,
|
name,
|
||||||
description: payload.description,
|
description,
|
||||||
|
parentRoleId: payload.parentRoleId,
|
||||||
|
ownerType: payload.ownerType,
|
||||||
|
ownerId: payload.ownerId,
|
||||||
permissionIdList: payload.permissionIdList,
|
permissionIdList: payload.permissionIdList,
|
||||||
childRoleIdList: payload.childRoleIdList
|
childRoleIdList: payload.childRoleIdList
|
||||||
});
|
});
|
||||||
await loadAll(true);
|
await loadAll(true);
|
||||||
return undefined;
|
return roleList.value.find(item => item.code === payload.code && item.moduleCode === moduleCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
await RoleAPI.update(payload);
|
await RoleAPI.update(payload);
|
||||||
@@ -263,6 +296,7 @@ export const usePermissionSettingsStore = defineStore("permission-settings", ()
|
|||||||
.filter(item => item.id !== id)
|
.filter(item => item.id !== id)
|
||||||
.map(item => ({
|
.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
|
parentRoleId: item.parentRoleId === id ? undefined : item.parentRoleId,
|
||||||
childRoleIdList: item.childRoleIdList.filter(childId => childId !== id)
|
childRoleIdList: item.childRoleIdList.filter(childId => childId !== id)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -305,25 +339,56 @@ export const usePermissionSettingsStore = defineStore("permission-settings", ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setRolePermissionList(roleId: string, permissionIds: string[]): Promise<void> {
|
async function setRolePermissionList(roleId: string, permissionIds: string[]): Promise<void> {
|
||||||
|
await RoleAPI.update({
|
||||||
|
id: roleId,
|
||||||
|
permissionIdList: [...permissionIds]
|
||||||
|
});
|
||||||
const role = await loadRoleDetail(roleId);
|
const role = await loadRoleDetail(roleId);
|
||||||
await upsertRole({
|
upsertLocalRole(role);
|
||||||
id: role.id,
|
}
|
||||||
moduleCode: moduleCode,
|
|
||||||
code: role.code,
|
async function setRoleChildRoleList(roleId: string, childRoleIds: string[]): Promise<void> {
|
||||||
name: role.name || "",
|
await RoleAPI.update({
|
||||||
description: role.description || "",
|
id: roleId,
|
||||||
permissionIdList: [...permissionIds],
|
childRoleIdList: [...childRoleIds]
|
||||||
childRoleIdList: [...role.childRoleIdList]
|
});
|
||||||
}, false);
|
const role = await loadRoleDetail(roleId);
|
||||||
|
upsertLocalRole(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setRoleDelegationPermissionList(roleId: string, permissionIds: string[]): Promise<void> {
|
||||||
|
await RoleAPI.updateDelegation({
|
||||||
|
id: roleId,
|
||||||
|
delegationPermissionIdList: permissionIds
|
||||||
|
});
|
||||||
|
const role = await loadRoleDetail(roleId);
|
||||||
|
upsertLocalRole(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setRoleParentRole(roleId: string, parentRoleId: string): Promise<void> {
|
||||||
|
await RoleAPI.updateParent({
|
||||||
|
id: roleId,
|
||||||
|
parentRoleId
|
||||||
|
});
|
||||||
|
const role = await loadRoleDetail(roleId);
|
||||||
|
upsertLocalRole(role);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
roleList,
|
roleList,
|
||||||
permissionList,
|
permissionList,
|
||||||
|
manageableRoleList,
|
||||||
|
grantablePermissionList,
|
||||||
roleTree,
|
roleTree,
|
||||||
loading,
|
loading,
|
||||||
loaded,
|
loaded,
|
||||||
loadAll,
|
loadAll,
|
||||||
|
loadManageableRoles,
|
||||||
|
loadGrantablePermissions,
|
||||||
|
loadGrantableRolePermissions,
|
||||||
|
loadDelegationPermissions,
|
||||||
|
loadGrantableDelegationPermissions,
|
||||||
|
loadParentGrantableRoles,
|
||||||
loadRoleDetail,
|
loadRoleDetail,
|
||||||
loadPermissionDetail,
|
loadPermissionDetail,
|
||||||
getRoleById,
|
getRoleById,
|
||||||
@@ -335,7 +400,10 @@ export const usePermissionSettingsStore = defineStore("permission-settings", ()
|
|||||||
removeRole,
|
removeRole,
|
||||||
upsertPermission,
|
upsertPermission,
|
||||||
removePermission,
|
removePermission,
|
||||||
setRolePermissionList
|
setRolePermissionList,
|
||||||
|
setRoleChildRoleList,
|
||||||
|
setRoleDelegationPermissionList,
|
||||||
|
setRoleParentRole
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import type { AuthOwnerType, ModuleCode, Permission as UserPermission, Role as UserRole } from "timi-web";
|
||||||
|
|
||||||
|
export type Permission = UserPermission & {
|
||||||
|
builtin?: boolean;
|
||||||
|
protectedPermission?: boolean;
|
||||||
|
ownerType?: AuthOwnerType;
|
||||||
|
ownerId?: string;
|
||||||
|
createdBy?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Role = Omit<
|
||||||
|
UserRole,
|
||||||
|
"id" | "permissionIdList" | "childRoleIdList" | "delegationPermissionIdList" | "delegationPermissionList"
|
||||||
|
> & {
|
||||||
|
id: string;
|
||||||
|
permissionIdList: string[];
|
||||||
|
childRoleIdList: string[];
|
||||||
|
delegationPermissionIdList: string[];
|
||||||
|
builtin?: boolean;
|
||||||
|
protectedRole?: boolean;
|
||||||
|
ownerType?: AuthOwnerType;
|
||||||
|
ownerId?: string;
|
||||||
|
createdBy?: string;
|
||||||
|
permissionList?: Permission[];
|
||||||
|
allPermissionList?: Permission[];
|
||||||
|
childRoleList?: UserRole[];
|
||||||
|
parentRole?: UserRole;
|
||||||
|
delegationPermissionList?: Permission[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RoleDraft = {
|
||||||
|
id?: string;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
nameLangId?: string;
|
||||||
|
moduleCode?: ModuleCode;
|
||||||
|
parentRoleId?: string;
|
||||||
|
ownerType?: AuthOwnerType;
|
||||||
|
ownerId?: string;
|
||||||
|
permissionIdList?: string[];
|
||||||
|
childRoleIdList?: string[];
|
||||||
|
delegationPermissionIdList?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RolePayload = Partial<Pick<Role, "moduleCode" | "code" | "nameLangId" | "name" | "description">> & {
|
||||||
|
id?: string;
|
||||||
|
parentRoleId?: string;
|
||||||
|
ownerType?: AuthOwnerType;
|
||||||
|
ownerId?: string;
|
||||||
|
permissionIdList?: string[];
|
||||||
|
childRoleIdList?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PermissionDraft = {
|
||||||
|
id?: string;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
nameLangId?: string;
|
||||||
|
moduleCode?: ModuleCode;
|
||||||
|
};
|
||||||
+29
-15
@@ -58,10 +58,10 @@ export const corePermissionCodes = {
|
|||||||
read: "CORE:ROLE_PERMISSION:READ",
|
read: "CORE:ROLE_PERMISSION:READ",
|
||||||
delete: "CORE:ROLE_PERMISSION:DELETE"
|
delete: "CORE:ROLE_PERMISSION:DELETE"
|
||||||
},
|
},
|
||||||
roleRelation: {
|
rolePermissionInherit: {
|
||||||
create: "CORE:ROLE_RELATION:CREATE",
|
create: "CORE:ROLE_PERMISSION_INHERIT:CREATE",
|
||||||
read: "CORE:ROLE_RELATION:READ",
|
read: "CORE:ROLE_PERMISSION_INHERIT:READ",
|
||||||
delete: "CORE:ROLE_RELATION:DELETE"
|
delete: "CORE:ROLE_PERMISSION_INHERIT:DELETE"
|
||||||
},
|
},
|
||||||
userRole: {
|
userRole: {
|
||||||
create: "CORE:USER_ROLE:CREATE",
|
create: "CORE:USER_ROLE:CREATE",
|
||||||
@@ -71,23 +71,37 @@ export const corePermissionCodes = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const coreRoleCodes = {
|
export const coreRoleCodes = {
|
||||||
admin: "CORE:ADMIN",
|
admin: "CORE:ADMIN"
|
||||||
userRoleAdmin: "CORE:USER_ROLE_ADMIN"
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const gaoRoleCodes = {
|
export const gaoRoleCodes = {
|
||||||
globalManager: "GAO:GLOBAL_MANAGER",
|
globalManager: "GAO:GLOBAL_MANAGER",
|
||||||
storeManager: "GAO:STORE_MANAGER",
|
storeManager: "GAO:STORE_MANAGER"
|
||||||
employee: "GAO:EMPLOYEE"
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const permissionManagementRoleAccess = {
|
/** 需要读取登录用户所属门店的业务权限。 */
|
||||||
value: [
|
export const gaoStoreScopePermissionCodes = [
|
||||||
coreRoleCodes.admin,
|
permissionCodes.customer.create,
|
||||||
coreRoleCodes.userRoleAdmin
|
permissionCodes.customer.read,
|
||||||
],
|
permissionCodes.customer.export,
|
||||||
mode: "or"
|
permissionCodes.customer.update,
|
||||||
} as const;
|
permissionCodes.customer.delete,
|
||||||
|
permissionCodes.event.create,
|
||||||
|
permissionCodes.event.read,
|
||||||
|
permissionCodes.event.update,
|
||||||
|
permissionCodes.event.delete,
|
||||||
|
permissionCodes.eventRecord.create,
|
||||||
|
permissionCodes.eventRecord.read,
|
||||||
|
permissionCodes.eventRecord.export,
|
||||||
|
permissionCodes.eventRecord.update,
|
||||||
|
permissionCodes.eventRecord.delete,
|
||||||
|
permissionCodes.stat.read,
|
||||||
|
permissionCodes.user.create,
|
||||||
|
permissionCodes.user.read,
|
||||||
|
permissionCodes.user.update,
|
||||||
|
permissionCodes.user.delete,
|
||||||
|
permissionCodes.qrCode.create
|
||||||
|
];
|
||||||
|
|
||||||
export type PermissionCodes = typeof permissionCodes;
|
export type PermissionCodes = typeof permissionCodes;
|
||||||
export type CorePermissionCodes = typeof corePermissionCodes;
|
export type CorePermissionCodes = typeof corePermissionCodes;
|
||||||
|
|||||||
+12
-35
@@ -7,25 +7,18 @@ function buildRoleCode(role: Role): string {
|
|||||||
return role.moduleCode && !role.code.includes(":") ? `${role.moduleCode}:${role.code}` : role.code;
|
return role.moduleCode && !role.code.includes(":") ? `${role.moduleCode}:${role.code}` : role.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBareRoleCode(code: string): string {
|
|
||||||
const codePartList = code.split(":");
|
|
||||||
return codePartList[codePartList.length - 1] || code;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findRole(role: ManagedUserRole, roleList: Role[]): Role | undefined {
|
function findRole(role: ManagedUserRole, roleList: Role[]): Role | undefined {
|
||||||
if (typeof role === "string") {
|
if (typeof role === "string") {
|
||||||
return roleList.find(roleItem => roleItem.code === role || buildRoleCode(roleItem) === role);
|
return roleList.find(roleItem => buildRoleCode(roleItem) === role);
|
||||||
}
|
}
|
||||||
return roleList.find(roleItem => roleItem.id === role.id || roleItem.code === role.code || buildRoleCode(roleItem) === role.code);
|
return roleList.find(roleItem => roleItem.id === role.id || buildRoleCode(roleItem) === buildRoleCode(role as Role));
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendCompatibleRoleCode(roleCodeSet: Set<string>, code: string): void {
|
export function getManagedUserRoleList(user: ManagedUser): ManagedUserRole[] {
|
||||||
if (!code) {
|
if (user.user?.roleEntityList?.length) {
|
||||||
return;
|
return user.user.roleEntityList;
|
||||||
}
|
}
|
||||||
|
return user.roleList || user.user?.roleList || [];
|
||||||
roleCodeSet.add(code);
|
|
||||||
roleCodeSet.add(getBareRoleCode(code));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRoleDisplayName(role: ManagedUserRole | undefined, roleList: Role[]): string {
|
export function getRoleDisplayName(role: ManagedUserRole | undefined, roleList: Role[]): string {
|
||||||
@@ -38,34 +31,18 @@ export function getRoleDisplayName(role: ManagedUserRole | undefined, roleList:
|
|||||||
return fullRole.name || fullRole.code || "未配置角色";
|
return fullRole.name || fullRole.code || "未配置角色";
|
||||||
}
|
}
|
||||||
|
|
||||||
return typeof role === "string" ? getBareRoleCode(role) : role.name || role.code || "未配置角色";
|
return typeof role === "string" ? role : role.name || role.code || "未配置角色";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildEffectiveRoleCodeSet(user: ManagedUser, roleList: Role[]): Set<string> {
|
export function buildEffectiveRoleCodeSet(user: ManagedUser): Set<string> {
|
||||||
const roleCodeSet = new Set<string>();
|
const roleCodeSet = new Set<string>();
|
||||||
for (const role of user.roleList || []) {
|
for (const role of getManagedUserRoleList(user)) {
|
||||||
const fullRole = findRole(role, roleList);
|
if (typeof role !== "string") {
|
||||||
if (fullRole) {
|
roleCodeSet.add(buildRoleCode(role as Role));
|
||||||
appendRoleCode(roleCodeSet, fullRole, roleList, new Set<string>());
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
appendCompatibleRoleCode(roleCodeSet, typeof role === "string" ? role : role.code);
|
roleCodeSet.add(role);
|
||||||
}
|
}
|
||||||
return roleCodeSet;
|
return roleCodeSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendRoleCode(roleCodeSet: Set<string>, role: Role, roleList: Role[], visitedRoleIds: Set<string>): void {
|
|
||||||
if (!role.id || visitedRoleIds.has(role.id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
visitedRoleIds.add(role.id);
|
|
||||||
appendCompatibleRoleCode(roleCodeSet, buildRoleCode(role));
|
|
||||||
for (const childRoleId of role.childRoleIdList) {
|
|
||||||
const childRole = roleList.find(item => item.id === childRoleId);
|
|
||||||
if (childRole) {
|
|
||||||
appendRoleCode(roleCodeSet, childRole, roleList, visitedRoleIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user