@ebiz/designer-components 0.1.124 → 0.1.126

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/README.md +29 -29
  2. package/dist/designer-components.css +1 -1
  3. package/dist/index.mjs +35548 -33511
  4. package/dist/{pdf-C9oIcL2N.js → pdf-KomaE0u5.js} +891 -891
  5. package/package.json +1 -1
  6. package/src/App.vue +26 -26
  7. package/src/apiService/SIMPLE_DATA_SERVICE.md +284 -284
  8. package/src/apiService/mockDataService.js +115 -115
  9. package/src/apiService/simpleDataService.js +312 -312
  10. package/src/assets/base.css +86 -86
  11. package/src/assets/logo.svg +1 -1
  12. package/src/components/Button.vue +149 -149
  13. package/src/components/DataContainer.vue +40 -40
  14. package/src/components/EbizApproval.vue +366 -366
  15. package/src/components/EbizApprovalDetail.vue +169 -169
  16. package/src/components/EbizApprovalForm.vue +534 -534
  17. package/src/components/EbizApprovalV2.vue +713 -713
  18. package/src/components/EbizAutoForm.vue +596 -596
  19. package/src/components/EbizAvatar.vue +115 -115
  20. package/src/components/EbizCheckbox.vue +93 -93
  21. package/src/components/EbizCheckboxGroup.vue +69 -69
  22. package/src/components/EbizDepartmentSelector.vue +148 -148
  23. package/src/components/EbizDescriptions.vue +340 -340
  24. package/src/components/EbizDescriptionsItem.vue +47 -47
  25. package/src/components/EbizDetailBlock.vue +81 -81
  26. package/src/components/EbizDetailItem.vue +559 -559
  27. package/src/components/EbizDetailView.md +438 -438
  28. package/src/components/EbizDetailView.vue +355 -355
  29. package/src/components/EbizDialog.vue +260 -260
  30. package/src/components/EbizDictionarySelect.vue +229 -229
  31. package/src/components/EbizDiv.vue +40 -40
  32. package/src/components/EbizDivider.vue +96 -96
  33. package/src/components/EbizDormDashboard.vue +314 -314
  34. package/src/components/EbizDropdown.vue +135 -135
  35. package/src/components/EbizDropdownItem.vue +85 -85
  36. package/src/components/EbizEmployeeInfo.vue +144 -144
  37. package/src/components/EbizEmployeeSelector.vue +1160 -1160
  38. package/src/components/EbizFileList.vue +502 -502
  39. package/src/components/EbizMap.vue +541 -541
  40. package/src/components/EbizMeetingRoomSelector.vue +1149 -1149
  41. package/src/components/EbizMobileMeetingRoomSelector.vue +727 -727
  42. package/src/components/EbizOkrTree.vue +99 -99
  43. package/src/components/EbizPageHeader.vue +98 -98
  44. package/src/components/EbizPagination.vue +162 -162
  45. package/src/components/EbizParkingDashboard.vue +152 -152
  46. package/src/components/EbizPdfViewer.vue +540 -540
  47. package/src/components/EbizPopconfirm.vue +47 -47
  48. package/src/components/EbizQrCode.vue +167 -167
  49. package/src/components/EbizRadio.vue +86 -86
  50. package/src/components/EbizRadioGroup.vue +83 -83
  51. package/src/components/EbizRemoteSelect.vue +232 -232
  52. package/src/components/EbizRichTextEditor.vue +338 -338
  53. package/src/components/EbizRouteBreadcrumb.vue +46 -46
  54. package/src/components/EbizSApprovalProcess.vue +1456 -1426
  55. package/src/components/EbizSelect.vue +85 -85
  56. package/src/components/EbizSpace.vue +100 -100
  57. package/src/components/EbizStatistic.vue +149 -149
  58. package/src/components/EbizStatsCard.vue +113 -113
  59. package/src/components/EbizSwiper.vue +113 -113
  60. package/src/components/EbizSwiperItem.vue +13 -13
  61. package/src/components/EbizSwitch.vue +85 -85
  62. package/src/components/EbizTabHeader.vue +132 -132
  63. package/src/components/EbizTabPanel.vue +22 -22
  64. package/src/components/EbizTable.vue +469 -469
  65. package/src/components/EbizTableColumn.vue +116 -116
  66. package/src/components/EbizTableSort.vue +179 -179
  67. package/src/components/EbizTabs.vue +142 -142
  68. package/src/components/EbizTdesignButtonDialog.vue +332 -332
  69. package/src/components/EbizTdesignLoading.vue +107 -107
  70. package/src/components/EbizTimePicker.vue +143 -143
  71. package/src/components/EbizTitle.vue +91 -91
  72. package/src/components/EbizTree.vue +141 -141
  73. package/src/components/EbizTreeMergeTable.vue +1494 -1494
  74. package/src/components/EbizTreeSelector.vue +469 -469
  75. package/src/components/EbizVideo.vue +553 -553
  76. package/src/components/EbizVxeTable.vue +290 -290
  77. package/src/components/Form.vue +28 -28
  78. package/src/components/Home.vue +7 -7
  79. package/src/components/LaunchInterview.vue +526 -526
  80. package/src/components/MyComponent.vue +39 -39
  81. package/src/components/Table.vue +45 -45
  82. package/src/components/TdesignAlert.vue +115 -115
  83. package/src/components/TdesignButton.vue +135 -135
  84. package/src/components/TdesignCalendar/index.vue +145 -145
  85. package/src/components/TdesignCard.vue +195 -195
  86. package/src/components/TdesignCol.vue +101 -101
  87. package/src/components/TdesignCollapse.vue +142 -142
  88. package/src/components/TdesignCollapsePanel.vue +79 -79
  89. package/src/components/TdesignDatePicker.vue +124 -124
  90. package/src/components/TdesignDescriptions.vue +74 -74
  91. package/src/components/TdesignDescriptionsItem.vue +50 -50
  92. package/src/components/TdesignDialog.vue +225 -225
  93. package/src/components/TdesignForm.vue +138 -138
  94. package/src/components/TdesignFormItem.vue +105 -105
  95. package/src/components/TdesignGrid.vue +55 -55
  96. package/src/components/TdesignIcon.vue +67 -67
  97. package/src/components/TdesignImage.vue +162 -162
  98. package/src/components/TdesignImageViewer.vue +200 -200
  99. package/src/components/TdesignInput.vue +242 -242
  100. package/src/components/TdesignSelect.vue +446 -446
  101. package/src/components/TdesignTag.vue +117 -117
  102. package/src/components/TdesignTextarea.vue +142 -142
  103. package/src/components/TdesignTimeline.vue +58 -58
  104. package/src/components/TdesignTimelineItem.vue +71 -71
  105. package/src/components/TdesignUpload.vue +414 -414
  106. package/src/components/TdesignWatermark.vue +107 -107
  107. package/src/components/ebiz-form/components/cascader.vue +61 -61
  108. package/src/components/ebiz-form/components/checkbox.vue +37 -37
  109. package/src/components/ebiz-form/components/city.vue +137 -137
  110. package/src/components/ebiz-form/components/date-panel.vue +52 -52
  111. package/src/components/ebiz-form/components/date-range-panel.vue +52 -52
  112. package/src/components/ebiz-form/components/date-range.vue +56 -56
  113. package/src/components/ebiz-form/components/date.vue +52 -52
  114. package/src/components/ebiz-form/components/editor-multi-language.vue +47 -47
  115. package/src/components/ebiz-form/components/editor.vue +78 -78
  116. package/src/components/ebiz-form/components/file-multi-language.vue +52 -52
  117. package/src/components/ebiz-form/components/file.vue +149 -149
  118. package/src/components/ebiz-form/components/images-multi-language.vue +52 -52
  119. package/src/components/ebiz-form/components/images.vue +129 -129
  120. package/src/components/ebiz-form/components/img-multi-language.vue +51 -51
  121. package/src/components/ebiz-form/components/img.vue +129 -129
  122. package/src/components/ebiz-form/components/number.vue +50 -50
  123. package/src/components/ebiz-form/components/radio.vue +28 -28
  124. package/src/components/ebiz-form/components/select.vue +119 -119
  125. package/src/components/ebiz-form/components/switch.vue +23 -23
  126. package/src/components/ebiz-form/components/text-multi-language.vue +47 -47
  127. package/src/components/ebiz-form/components/text.vue +52 -52
  128. package/src/components/ebiz-form/components/textarea-multi-language.vue +48 -48
  129. package/src/components/ebiz-form/components/textarea.vue +29 -29
  130. package/src/components/ebiz-form/components/video-multi-language.vue +51 -51
  131. package/src/components/ebiz-form/components/video.vue +97 -97
  132. package/src/components/ebiz-form/index.vue +157 -157
  133. package/src/components/examples/PopconfirmExample.vue +149 -149
  134. package/src/components/icons/IconCommunity.vue +7 -7
  135. package/src/components/icons/IconDocumentation.vue +7 -7
  136. package/src/components/icons/IconEcosystem.vue +7 -7
  137. package/src/components/icons/IconSupport.vue +7 -7
  138. package/src/components/icons/IconTooling.vue +19 -19
  139. package/src/components/mItems/UserInfo.vue +349 -349
  140. package/src/components/senior/EbizApprovalList/ApprovalList.vue +127 -127
  141. package/src/components/senior/EbizSData/index.vue +280 -280
  142. package/src/components/senior/EbizSDialog/index.vue +776 -776
  143. package/src/components/senior/EbizSForm/README.md +157 -157
  144. package/src/components/senior/EbizSForm/index.vue +667 -667
  145. package/src/components/senior/EbizSForm/item.vue +1011 -1011
  146. package/src/components/senior/EbizSForm/mItems/DateTimePicker.vue +51 -51
  147. package/src/components/senior/EbizSForm/mItems/Picker.vue +63 -63
  148. package/src/index.js +327 -327
  149. package/src/main.js +55 -55
  150. package/src/router/index.js +436 -436
  151. package/src/utils/formatCode.js +24 -24
  152. package/src/utils/generateImportStatement.js +52 -52
  153. package/src/utils/hasJsx.js +25 -25
  154. package/src/utils/index.js +166 -166
  155. package/src/utils/mergeOptions.js +29 -29
  156. package/src/utils/parseRequiredBlocks.js +18 -18
  157. package/src/utils/vue-sfc-validator.js +155 -155
  158. package/src/views/Button.vue +23 -23
  159. package/src/views/CheckboxDemo.vue +104 -104
  160. package/src/views/DataContainer.vue +19 -19
  161. package/src/views/DialogDemo.vue +125 -125
  162. package/src/views/EbizApprovalDemo.vue +87 -87
  163. package/src/views/EbizApprovalFormDemo.vue +207 -207
  164. package/src/views/EbizAutoFormDemo.vue +129 -129
  165. package/src/views/EbizAvatar.vue +223 -223
  166. package/src/views/EbizDepartmentSelectorDemo.vue +169 -169
  167. package/src/views/EbizDetailBlockDemo.vue +30 -30
  168. package/src/views/EbizDetailViewDemo.vue +412 -412
  169. package/src/views/EbizDormDashboardDemo.vue +87 -87
  170. package/src/views/EbizEmployeeInfo.vue +249 -249
  171. package/src/views/EbizEmployeeSelector.vue +85 -85
  172. package/src/views/EbizFileListDemo.vue +339 -339
  173. package/src/views/EbizMap.vue +201 -201
  174. package/src/views/EbizMeetingRoomSelectorDemo.vue +293 -293
  175. package/src/views/EbizMobileMeetingRoomSelectorDemo.vue +566 -566
  176. package/src/views/EbizParkingDashboardDemo.vue +28 -28
  177. package/src/views/EbizRadioDemo.vue +151 -151
  178. package/src/views/EbizSDataDemo.vue +136 -136
  179. package/src/views/EbizSDialogDemo.vue +303 -303
  180. package/src/views/EbizSForm/index.vue +352 -352
  181. package/src/views/EbizSFormDemo.vue +420 -420
  182. package/src/views/EbizSpace.vue +185 -185
  183. package/src/views/EbizSwiper.vue +157 -157
  184. package/src/views/EbizTdesignButtonDialogExample.vue +437 -437
  185. package/src/views/Form.vue +19 -19
  186. package/src/views/GridDemo.vue +238 -238
  187. package/src/views/Home.vue +156 -156
  188. package/src/views/LaunchInterviewDemo.vue +111 -111
  189. package/src/views/Mindmap.vue +17 -17
  190. package/src/views/MyComponent.vue +19 -19
  191. package/src/views/OkrTree.vue +19 -19
  192. package/src/views/PageHeaderDemo.vue +104 -104
  193. package/src/views/PaginationDemo.vue +96 -96
  194. package/src/views/PdfViewerDemo.vue +433 -433
  195. package/src/views/PermissionBoxDemo.vue +85 -85
  196. package/src/views/PopconfirmDemo.vue +80 -80
  197. package/src/views/RemoteSelect.vue +350 -350
  198. package/src/views/StatisticDemo.vue +190 -190
  199. package/src/views/SwitchDemo.vue +79 -79
  200. package/src/views/Table.vue +19 -19
  201. package/src/views/TableDemo.vue +334 -334
  202. package/src/views/TableSortDemo.vue +143 -143
  203. package/src/views/TableView.vue +68 -68
  204. package/src/views/TabsDemo.vue +282 -282
  205. package/src/views/TagDemo.vue +101 -101
  206. package/src/views/TdesignAlert.vue +98 -98
  207. package/src/views/TdesignButton.vue +190 -190
  208. package/src/views/TdesignCalendar.vue +94 -94
  209. package/src/views/TdesignCard.vue +296 -296
  210. package/src/views/TdesignCollapse.vue +293 -293
  211. package/src/views/TdesignDatePicker.vue +187 -187
  212. package/src/views/TdesignDescriptions.vue +101 -101
  213. package/src/views/TdesignForm.vue +248 -248
  214. package/src/views/TdesignIcon.vue +203 -203
  215. package/src/views/TdesignImage.vue +215 -215
  216. package/src/views/TdesignImageViewer.vue +198 -198
  217. package/src/views/TdesignInput.vue +252 -252
  218. package/src/views/TdesignSelect.vue +473 -473
  219. package/src/views/TdesignSwiper.vue +157 -157
  220. package/src/views/TextareaDemo.vue +93 -93
  221. package/src/views/TimePickerDemo.vue +146 -146
  222. package/src/views/TimelineDemo.vue +160 -160
  223. package/src/views/Title.vue +19 -19
  224. package/src/views/TreeDemo.vue +254 -254
  225. package/src/views/TreeMergeTableDemo.vue +239 -239
  226. package/src/views/TreeSelectorDemo.vue +245 -245
  227. package/src/views/UploadDemo.vue +128 -128
  228. package/src/views/VideoDemo.vue +245 -245
  229. package/src/views/VxeTableDemo.vue +279 -279
  230. package/src/views/WatermarkDemo.vue +85 -85
@@ -1,1494 +1,1494 @@
1
- <template>
2
- <div>
3
- <!-- 选中数据结构展示 -->
4
- <!-- <div style="margin-bottom: 16px">
5
- <t-card title="选中数据结构(JSON)">
6
- <pre style="background: #f6f8fa; padding: 12px 16px; border-radius: 6px; overflow: auto; max-height: 300px"
7
- >{{ JSON.stringify(computedSelectedData, null, 2) }}
8
- </pre>
9
- </t-card>
10
- </div> -->
11
- <!-- 树表上方的数据权限配置模块 -->
12
- <div class="permission-config-panel">
13
- <div class="panel-header">
14
- <h3 class="panel-title">数据权限配置</h3>
15
- </div>
16
- <div class="panel-content">
17
- <div class="permission-type-selector">
18
- <t-radio-group v-model="globalPermission" class="permission-radio-group">
19
- <t-radio-button :value="1">全部数据</t-radio-button>
20
- <t-radio-button :value="2">事业部数据</t-radio-button>
21
- <t-radio-button :value="3">本部门数据</t-radio-button>
22
- <t-radio-button :value="4">子部门数据</t-radio-button>
23
- <t-radio-button :value="5">个人数据</t-radio-button>
24
- <t-radio-button :value="6">自定义数据</t-radio-button>
25
- </t-radio-group>
26
- </div>
27
-
28
- <div v-if="globalPermission === 6" class="org-select-row">
29
- <div class="selected-count">
30
- {{ globalSelectedOrgs.length > 0 ? '已选择' + globalSelectedOrgs.length + '个组织' : '未选择组织' }}
31
- </div>
32
- <t-button theme="primary" @click="openGlobalOrgSelector" class="org-select-btn">
33
- {{ globalSelectedOrgs.length > 0 ? '编辑' : '选择' }}
34
- <template #suffix>
35
- <t-icon name="chevron-right" />
36
- </template>
37
- </t-button>
38
- </div>
39
-
40
- <div v-if="globalPermission === 6" class="selected-orgs-panel">
41
- <div class="selected-orgs-header">
42
- 已选组织:
43
- <t-button theme="primary" variant="text" size="small" @click="clearGlobalOrgs">清空</t-button>
44
- </div>
45
- <div class="selected-orgs-content">
46
- <div v-if="globalSelectedOrgs.length === 0" class="no-org-selected">请从左侧选择组织</div>
47
- <div v-else class="selected-orgs-tags">
48
- <t-tag v-for="org in globalSelectedOrgs" :key="org.id" theme="primary" variant="light" class="org-tag"
49
- closable @close="removeGlobalOrg(org.id)">
50
- {{ org.name }}
51
- </t-tag>
52
- </div>
53
- </div>
54
- </div>
55
- </div>
56
- </div>
57
-
58
- <t-table :bordered="true" :data="data" :columns="columns" row-key="id" :rowspan-and-colspan="rowspanAndColspan"
59
- resizable table-layout="fixed" lazy-load @cell-click="handleCellClick">
60
- <!-- 模块列的自定义单元格 -->
61
- <template #module="{ row }">
62
- <div class="cell-with-checkbox">
63
- <t-checkbox v-model="row.moduleChecked" @change="(checked) => handleModuleCheck(row, checked)"></t-checkbox>
64
- <div class="cell-content">
65
- <div class="cell-text">{{ row.module }}</div>
66
- <div class="cell-id" v-if="row.moduleKey">{{ row.moduleKey }}</div>
67
- </div>
68
- </div>
69
- </template>
70
-
71
- <!-- 子模块1列的自定义单元格 -->
72
- <template #subModule1="{ row }">
73
- <div class="cell-with-checkbox">
74
- <t-checkbox v-model="row.subModule1Checked"
75
- @change="(checked) => handleSubModule1Check(row, checked)"></t-checkbox>
76
- <div class="cell-content">
77
- <div class="cell-text">{{ row.subModule1 || row.module }}</div>
78
- <div class="cell-id" v-if="row.subModule1Key">{{ row.subModule1Key }}</div>
79
- </div>
80
- </div>
81
- </template>
82
-
83
- <!-- 子模块2列的自定义单元格 -->
84
- <template #subModule2="{ row }">
85
- <div class="cell-with-checkbox" v-if="row.subModule2">
86
- <t-checkbox v-model="row.subModule2Checked"
87
- @change="(checked) => handleSubModule2Check(row, checked)"></t-checkbox>
88
- <div class="cell-content">
89
- <div class="cell-text">{{ row.subModule2 }}</div>
90
- <div class="cell-id" v-if="row.subModule2Key">{{ row.subModule2Key }}</div>
91
- </div>
92
- </div>
93
- <div v-else class="cell-with-checkbox">
94
- <span class="cell-text empty-cell">-</span>
95
- </div>
96
- </template>
97
-
98
- <!-- 功能列的自定义单元格 -->
99
- <template #functions="{ row }">
100
- <div class="functions-container">
101
- <div v-for="(func, idx) in row.functions" :key="func.id || idx" class="function-item">
102
- <t-checkbox v-model="func.checked"
103
- @change="(checked) => handleFunctionCheck(row, func, checked)"></t-checkbox>
104
- <div class="cell-content">
105
- <div class="cell-text">{{ func.name }}</div>
106
- <div class="cell-id" v-if="func.funcKey">{{ func.funcKey }}</div>
107
- </div>
108
- </div>
109
- </div>
110
- </template>
111
-
112
- <!-- 操作列的自定义单元格 -->
113
- <template #operation="{ row }">
114
- <div class="operation-buttons">
115
- <t-button theme="primary" size="medium" @click="openPermissionDialog(row)"
116
- :disabled="!row.functions.some((f) => f.checked)">数据权限配置</t-button>
117
- </div>
118
- </template>
119
-
120
- <slot></slot>
121
- </t-table>
122
-
123
- <!-- 新增保存按钮 -->
124
- <div style="margin-top: 24px; display: flex; justify-content: flex-end">
125
- <t-button theme="primary" @click="handleSaveRolePermission">保存角色权限</t-button>
126
- </div>
127
-
128
- <!-- 全局组织选择弹窗 -->
129
- <t-dialog v-model:visible="globalOrgDialogVisible" header="选择组织" width="800px" attach="body"
130
- :confirm-btn="{ content: '确定', theme: 'primary' }" :cancel-btn="{ content: '取消', theme: 'default' }"
131
- @confirm="handleGlobalOrgConfirm" @cancel="handleGlobalOrgCancel">
132
- <div class="org-selector-dialog">
133
- <div class="org-dialog-container">
134
- <div class="org-left-panel">
135
- <div class="org-panel-header">组织架构</div>
136
- <div class="org-search-container">
137
- <t-input v-model="keyWord" placeholder="请输入关键词搜索" clearable style="width: 100%">
138
- <template #suffix>
139
- <t-button theme="default" shape="square" @click="onOrgSearch" size="small">
140
- <t-icon name="search" />
141
- </t-button>
142
- </template>
143
- </t-input>
144
- <div v-if="keyWord" class="search-result-info">
145
- 找到 {{ getMatchedCount(filteredOrgTreeData) }} 个结果
146
- </div>
147
- </div>
148
- <div class="org-tree-container">
149
- <t-tree :data="filteredOrgTreeData" :keys="orgTreeKeys" activable hover expandAll line
150
- expand-on-click-node :expanded="expandedKeys" :active="activeKey" @active="handleOrgNodeActive"
151
- @expand="handleOrgNodeExpand">
152
- <template #operations="{ node }">
153
- <t-checkbox :checked="isGlobalOrgNodeSelected(node.data.id)"
154
- @change="(checked) => handleGlobalOrgNodeSelect(node.data, checked)"></t-checkbox>
155
- </template>
156
- <template #label="{ node }">
157
- <div class="tree-node-label" :class="{ selected: isGlobalOrgNodeSelected(node.data.id) }"
158
- @click.stop="toggleGlobalOrgNodeSelect(node.data)">
159
- {{ node.data.name }}
160
- </div>
161
- </template>
162
- </t-tree>
163
- <div v-if="keyWord && filteredOrgTreeData.length === 0" class="no-search-results">
164
- 未找到符合"{{ keyWord }}"的组织
165
- </div>
166
- </div>
167
- </div>
168
- <div class="org-right-panel">
169
- <div class="org-panel-header">
170
- <span>已选择组织</span>
171
- <t-button theme="primary" variant="text" @click="clearGlobalSelectedOrgs">清空</t-button>
172
- </div>
173
- <div class="org-selected-container">
174
- <div v-if="tempGlobalSelectedOrgs.length === 0" class="no-selected-tip">请从左侧选择组织</div>
175
- <div v-else class="selected-orgs-tags">
176
- <t-tag v-for="org in tempGlobalSelectedOrgs" :key="org.id" closable @close="removeGlobalSelectedOrg(org.id)"
177
- theme="primary" variant="light" class="org-tag">
178
- {{ org.name }}
179
- </t-tag>
180
- </div>
181
- </div>
182
- </div>
183
- </div>
184
- </div>
185
- </t-dialog>
186
-
187
- <!-- 数据权限配置弹窗(针对表格行) -->
188
- <t-dialog v-model:visible="permissionDialogVisible" :header="permissionDialogTitle" width="800px" attach="body"
189
- :confirm-btn="{ content: '确定', theme: 'primary' }" :cancel-btn="{ content: '取消', theme: 'default' }"
190
- @confirm="handlePermissionConfirm" @cancel="handlePermissionCancel">
191
- <div class="permission-content">
192
- <template v-if="currentRow">
193
- <div class="permission-item">
194
- <div class="permission-options">
195
- <t-radio-group v-model="selectedPermission">
196
- <t-radio-button :value="1">全部数据</t-radio-button>
197
- <t-radio-button :value="2">事业部数据</t-radio-button>
198
- <t-radio-button :value="3">本部门数据</t-radio-button>
199
- <t-radio-button :value="4">子部门数据</t-radio-button>
200
- <t-radio-button :value="5">个人数据</t-radio-button>
201
- <t-radio-button :value="6">自定义数据</t-radio-button>
202
- </t-radio-group>
203
- </div>
204
-
205
- <div v-if="selectedPermission === 6" class="custom-permission">
206
- <div class="org-dialog-container">
207
- <div class="org-left-panel">
208
- <div class="org-panel-header">组织架构</div>
209
- <div class="org-search-container">
210
- <t-input v-model="keyWord" placeholder="请输入关键词搜索" clearable style="width: 100%">
211
- <template #suffix>
212
- <t-button theme="default" shape="square" @click="onOrgSearch" size="small">
213
- <t-icon name="search" />
214
- </t-button>
215
- </template>
216
- </t-input>
217
- <div v-if="keyWord" class="search-result-info">
218
- 找到 {{ getMatchedCount(filteredOrgTreeData) }} 个结果
219
- </div>
220
- </div>
221
- <div class="org-tree-container">
222
- <t-tree :data="filteredOrgTreeData" :keys="orgTreeKeys" activable hover expandAll line
223
- expand-on-click-node :expanded="expandedKeys" :active="activeKey" @active="handleOrgNodeActive"
224
- @expand="handleOrgNodeExpand">
225
- <template #operations="{ node }">
226
- <t-checkbox :checked="isOrgNodeSelected(node.data.id)"
227
- @change="(checked) => handleOrgNodeSelect(node.data, checked)"></t-checkbox>
228
- </template>
229
- <template #label="{ node }">
230
- <div class="tree-node-label" :class="{ selected: isOrgNodeSelected(node.data.id) }"
231
- @click.stop="toggleOrgNodeSelect(node.data)">
232
- {{ node.data.name }}
233
- </div>
234
- </template>
235
- </t-tree>
236
- <div v-if="keyWord && filteredOrgTreeData.length === 0" class="no-search-results">
237
- 未找到符合"{{ keyWord }}"的组织
238
- </div>
239
- </div>
240
- </div>
241
- <div class="org-right-panel">
242
- <div class="org-panel-header">
243
- <span>已选择组织</span>
244
- <t-button theme="primary" variant="text" @click="clearSelectedOrgs">清空</t-button>
245
- </div>
246
- <div class="org-selected-container">
247
- <div v-if="selectedOrgs.length === 0" class="no-selected-tip">请从左侧选择组织</div>
248
- <div v-else class="selected-orgs-tags">
249
- <t-tag v-for="org in selectedOrgs" :key="org.id" closable @close="removeSelectedOrg(org.id)"
250
- theme="primary" variant="light" class="org-tag">
251
- {{ org.name }}
252
- </t-tag>
253
- </div>
254
- </div>
255
- </div>
256
- </div>
257
- </div>
258
- </div>
259
- </template>
260
- </div>
261
- </t-dialog>
262
-
263
- <!-- 保留原有的详情弹窗 -->
264
- <t-dialog v-model:visible="detailDialogVisible" :header="detailDialogTitle" width="600px" :footer="false">
265
- <div class="detail-content">
266
- <template v-if="currentRow">
267
- <div class="detail-item">
268
- <span class="item-label">模块:</span>
269
- <span class="item-value">{{ currentRow.module }}</span>
270
- </div>
271
-
272
- <div class="detail-item" v-if="currentRow.subModule1">
273
- <span class="item-label">子模块1:</span>
274
- <span class="item-value">{{ currentRow.subModule1 }}</span>
275
- </div>
276
-
277
- <div class="detail-item" v-if="currentRow.subModule2">
278
- <span class="item-label">子模块2:</span>
279
- <span class="item-value">{{ currentRow.subModule2 }}</span>
280
- </div>
281
-
282
- <div class="detail-item">
283
- <span class="item-label">功能:</span>
284
- <span class="item-value">{{ currentRow.function }}</span>
285
- </div>
286
-
287
- <div class="detail-item" v-if="currentRow.path">
288
- <span class="item-label">路径:</span>
289
- <span class="item-value">{{ currentRow.path }}</span>
290
- </div>
291
-
292
- <div class="detail-item" v-if="currentRow.childFunctions && currentRow.childFunctions.length > 0">
293
- <span class="item-label">子功能:</span>
294
- <div class="sub-functions">
295
- <div v-for="(func, index) in currentRow.childFunctions" :key="index" class="sub-function-item">
296
- <span>{{ func.name }}</span>
297
- </div>
298
- </div>
299
- </div>
300
- </template>
301
- </div>
302
- </t-dialog>
303
- </div>
304
- </template>
305
-
306
- <script>
307
- export default {
308
- name: 'EbizTreeMergeTable'
309
- }
310
- </script>
311
-
312
- <script setup>
313
- import dataService from '../apiService/simpleDataService'
314
- import { ref, computed, onMounted, watch } from 'vue'
315
- import {
316
- Table as TTable,
317
- Checkbox as TCheckbox,
318
- Button as TButton,
319
- Dialog as TDialog,
320
- RadioGroup as TRadioGroup,
321
- RadioButton as TRadioButton,
322
- Input as TInput,
323
- Tree as TTree,
324
- Tag as TTag,
325
- Icon as TIcon,
326
- Message
327
- } from 'tdesign-vue-next'
328
-
329
- const apis = {
330
- // tree: '/menu/tree',
331
- tree: '/function/tree',
332
- orgTree: '/appdata/execute/plugin?key=organizational_structure',
333
- getConfig: '/role/functions/search/{roleId}',
334
- getRole: '/role/position/{positionId}',
335
- gerRoleInfo: '/role/detail/{id}',
336
- saveRole: '/role/save'
337
- }
338
-
339
- const props = defineProps({
340
- type: {
341
- type: String,
342
- default: 'role' // role,post 角色,岗位
343
- },
344
- id: {
345
- type: String,
346
- default: ''
347
- }
348
- })
349
- const emits = defineEmits(['success'])
350
- const roleId = ref()
351
-
352
- const data = ref([])
353
- // 表格列配置,添加自定义单元格插槽
354
- const columns = [
355
- { colKey: 'module', title: '模块', width: 150, cell: 'module' },
356
- { colKey: 'subModule1', title: '子模块1', width: 150, cell: 'subModule1' },
357
- { colKey: 'subModule2', title: '子模块2', width: 150, cell: 'subModule2' },
358
- { colKey: 'functions', title: '功能', width: 300, cell: 'functions' },
359
- { colKey: 'operation', title: '操作', width: 100, cell: 'operation', fixed: 'right' }
360
- ]
361
-
362
-
363
- // 获取数据并处理
364
- dataService.fetch({}, {}, apis.tree).then((res) => {
365
- const processedData = processTreeData(res || [])
366
- // 为每行数据添加选中状态属性
367
- processedData.forEach((row) => {
368
- // 添加选中状态属性
369
- row.moduleChecked = false
370
- row.subModule1Checked = false
371
- row.subModule2Checked = false
372
- row.functionChecked = false
373
- })
374
- data.value = processedData;
375
- })
376
- function getRoleConfig() {
377
- dataService.fetch({}, {}, apis.getConfig.replace('{roleId}', roleId.value || props.id)).then((res) => {
378
- res.forEach(item => {
379
- // 获取functionId
380
- const functionId = item.functionId;
381
- // 在data.value中查找对应功能所在的行
382
- data.value.forEach(row => {
383
- // 检查functions数组中是否有匹配的功能
384
- const matchedFunction = row.functions.find(func => func.id === functionId);
385
- if (matchedFunction) {
386
- // 设置功能勾选状态为true
387
- matchedFunction.checked = true;
388
-
389
- // 保存数据权限配置(如果有)
390
- if (item.dataScope) {
391
- row.dataPermission = item.dataScope;
392
- // 标记为自定义配置
393
- row.hasCustomConfig = true;
394
- // 如果是自定义数据权限,则设置已选组织
395
- if (item.dataScope === 6 && item.deptIds && item.deptIds.length > 0) {
396
- row.selectedOrgs = item.deptIds.map(id => {
397
- const org = findOrgInTree(orgTreeData.value, id);
398
- return {
399
- id: id,
400
- name: org ? org.name : `组织${id}`
401
- };
402
- });
403
- }
404
- }
405
-
406
- // 更新父级勾选状态(模块、子模块)
407
- updateParentCheckStatus(row);
408
- }
409
- });
410
- });
411
- })
412
- }
413
- /**
414
- * 处理树形数据,生成四级结构(模块、子模块1、子模块2、功能列表)
415
- * @param {Array} treeData 原始树形数据
416
- * @returns {Array} 扁平化处理后的数据
417
- */
418
- const processTreeData = (treeData) => {
419
- const result = []
420
- // 处理第一级:模块
421
- treeData.forEach((moduleItem) => {
422
- if (!moduleItem.children || moduleItem.children.length === 0) {
423
- // 没有子模块,功能为全部
424
- result.push({
425
- id: `${moduleItem.id}_all`,
426
- module: moduleItem.name,
427
- moduleId: moduleItem.id,
428
- moduleKey: moduleItem.funcKey,
429
- subModule1: null,
430
- subModule1Id: null,
431
- subModule1Key: null,
432
- subModule2: null,
433
- subModule2Id: null,
434
- subModule2Key: null,
435
- functions: [{ id: null, name: '全部', checked: false }]
436
- })
437
- return
438
- }
439
- moduleItem.children.forEach((subModule1Item) => {
440
- if (!subModule1Item.children || subModule1Item.children.length === 0) {
441
- // 没有子模块2,功能为全部
442
- result.push({
443
- id: `${moduleItem.id}_${subModule1Item.id}_all`,
444
- module: moduleItem.name,
445
- moduleId: moduleItem.id,
446
- moduleKey: moduleItem.funcKey,
447
- subModule1: subModule1Item.name,
448
- subModule1Id: subModule1Item.id,
449
- subModule1Key: subModule1Item.funcKey,
450
- subModule2: null,
451
- subModule2Id: null,
452
- subModule2Key: null,
453
- functions: [{ id: null, name: '全部', checked: false }]
454
- })
455
- } else {
456
- subModule1Item.children.forEach((subModule2Item) => {
457
- // 功能聚合
458
- let functions = []
459
- if (subModule2Item.children && subModule2Item.children.length > 0) {
460
- subModule2Item.children.forEach((functionItem) => {
461
- // 主功能
462
- const func = {
463
- id: functionItem.id,
464
- name: functionItem.name,
465
- funcKey: functionItem.funcKey,
466
- checked: false
467
- }
468
- functions.push(func)
469
- // 子功能
470
- if (functionItem.children && functionItem.children.length > 0) {
471
- functionItem.children.forEach((childFunc) => {
472
- functions.push({
473
- id: childFunc.id,
474
- name: childFunc.name,
475
- funcKey: childFunc.funcKey,
476
- checked: false
477
- })
478
- })
479
- }
480
- })
481
- } else {
482
- functions.push({ id: null, name: '全部', checked: false })
483
- }
484
- result.push({
485
- id: `${moduleItem.id}_${subModule1Item.id}_${subModule2Item.id}`,
486
- module: moduleItem.name,
487
- moduleId: moduleItem.id,
488
- moduleKey: moduleItem.funcKey,
489
- subModule1: subModule1Item.name,
490
- subModule1Id: subModule1Item.id,
491
- subModule1Key: subModule1Item.funcKey,
492
- subModule2: subModule2Item.name,
493
- subModule2Id: subModule2Item.id,
494
- subModule2Key: subModule2Item.funcKey,
495
- functions
496
- })
497
- })
498
- }
499
- })
500
- })
501
- return result
502
- }
503
-
504
- /**
505
- * 单元格合并计算函数
506
- */
507
- const rowspanAndColspan = ({ row, col, rowIndex, colIndex }) => {
508
- // 功能列合并逻辑
509
- if (col.colKey === 'functions') {
510
- const isFirstRow =
511
- rowIndex === 0 ||
512
- data.value[rowIndex - 1].moduleId !== row.moduleId ||
513
- data.value[rowIndex - 1].subModule1Id !== row.subModule1Id ||
514
- data.value[rowIndex - 1].subModule2Id !== row.subModule2Id
515
- if (isFirstRow) {
516
- let rowspan = 1
517
- for (let i = rowIndex + 1; i < data.value.length; i++) {
518
- if (
519
- data.value[i].moduleId === row.moduleId &&
520
- data.value[i].subModule1Id === row.subModule1Id &&
521
- data.value[i].subModule2Id === row.subModule2Id
522
- ) {
523
- rowspan++
524
- } else {
525
- break
526
- }
527
- }
528
- return { rowspan, colspan: 1 }
529
- }
530
- return { rowspan: 0, colspan: 0 }
531
- }
532
- // 模块列合并逻辑
533
- if (col.colKey === 'module') {
534
- const currentModuleId = row.moduleId
535
- const isFirstRow = rowIndex === 0 || data.value[rowIndex - 1].moduleId !== currentModuleId
536
- if (isFirstRow) {
537
- let rowspan = 1
538
- for (let i = rowIndex + 1; i < data.value.length; i++) {
539
- if (data.value[i].moduleId === currentModuleId) {
540
- rowspan++
541
- } else {
542
- break
543
- }
544
- }
545
- return { rowspan, colspan: 1 }
546
- }
547
- return { rowspan: 0, colspan: 0 }
548
- }
549
- // 子模块1列合并逻辑
550
- if (col.colKey === 'subModule1') {
551
- if (!row.subModule1) return {}
552
- const currentModule = row.moduleId
553
- const currentSubModule1 = row.subModule1Id
554
- const isFirstRow =
555
- rowIndex === 0 ||
556
- data.value[rowIndex - 1].subModule1Id !== currentSubModule1 ||
557
- data.value[rowIndex - 1].moduleId !== currentModule
558
- if (isFirstRow) {
559
- let rowspan = 1
560
- for (let i = rowIndex + 1; i < data.value.length; i++) {
561
- if (data.value[i].moduleId === currentModule && data.value[i].subModule1Id === currentSubModule1) {
562
- rowspan++
563
- } else {
564
- break
565
- }
566
- }
567
- return { rowspan, colspan: 1 }
568
- }
569
- return { rowspan: 0, colspan: 0 }
570
- }
571
- // 子模块2列合并逻辑
572
- if (col.colKey === 'subModule2') {
573
- if (!row.subModule2) return {}
574
- const currentModule = row.moduleId
575
- const currentSubModule1 = row.subModule1Id
576
- const currentSubModule2 = row.subModule2Id
577
- const isFirstRow =
578
- rowIndex === 0 ||
579
- data.value[rowIndex - 1].subModule2Id !== currentSubModule2 ||
580
- data.value[rowIndex - 1].subModule1Id !== currentSubModule1 ||
581
- data.value[rowIndex - 1].moduleId !== currentModule
582
- if (isFirstRow) {
583
- let rowspan = 1
584
- for (let i = rowIndex + 1; i < data.value.length; i++) {
585
- if (
586
- data.value[i].moduleId === currentModule &&
587
- data.value[i].subModule1Id === currentSubModule1 &&
588
- data.value[i].subModule2Id === currentSubModule2
589
- ) {
590
- rowspan++
591
- } else {
592
- break
593
- }
594
- }
595
- return { rowspan, colspan: 1 }
596
- }
597
- return { rowspan: 0, colspan: 0 }
598
- }
599
- return {} // 其他列不合并
600
- }
601
-
602
- // 勾选模块时联动所有下级
603
- const handleModuleCheck = (row, checked) => {
604
- row.moduleChecked = checked
605
- data.value.forEach((item) => {
606
- if (item.moduleId === row.moduleId) {
607
- item.subModule1Checked = checked
608
- item.subModule2Checked = checked
609
- item.functions.forEach((func) => (func.checked = checked))
610
- }
611
- })
612
- }
613
-
614
- // 勾选子模块1时联动所有下级
615
- const handleSubModule1Check = (row, checked) => {
616
- row.subModule1Checked = checked
617
- data.value.forEach((item) => {
618
- if (item.moduleId === row.moduleId && item.subModule1Id === row.subModule1Id) {
619
- item.subModule2Checked = checked
620
- item.functions.forEach((func) => (func.checked = checked))
621
- }
622
- })
623
- // 向上联动模块
624
- updateModuleChecked(row.moduleId)
625
- }
626
-
627
- // 勾选子模块2时联动所有下级
628
- const handleSubModule2Check = (row, checked) => {
629
- row.subModule2Checked = checked
630
- data.value.forEach((item) => {
631
- if (
632
- item.moduleId === row.moduleId &&
633
- item.subModule1Id === row.subModule1Id &&
634
- item.subModule2Id === row.subModule2Id
635
- ) {
636
- item.functions.forEach((func) => (func.checked = checked))
637
- }
638
- })
639
- // 向上联动子模块1
640
- updateSubModule1Checked(row.moduleId, row.subModule1Id)
641
- }
642
-
643
- // 勾选功能时,自动判断父级
644
- const handleFunctionCheck = (row, func, checked) => {
645
- func.checked = checked
646
- // 判断本组是否有功能被选中
647
- const hasChecked = row.functions.some((f) => f.checked)
648
- row.subModule2Checked = hasChecked
649
- // 向上联动
650
- updateSubModule1Checked(row.moduleId, row.subModule1Id)
651
- }
652
-
653
- // 更新子模块1的勾选状态
654
- const updateSubModule1Checked = (moduleId, subModule1Id) => {
655
- // 找到所有同模块+同子模块1的行
656
- const group = data.value.filter((item) => item.moduleId === moduleId && item.subModule1Id === subModule1Id)
657
- const hasChecked = group.some((item) => item.subModule2Checked)
658
- group.forEach((item) => {
659
- item.subModule1Checked = hasChecked
660
- })
661
- // 向上联动模块
662
- updateModuleChecked(moduleId)
663
- }
664
-
665
- // 更新模块的勾选状态
666
- const updateModuleChecked = (moduleId) => {
667
- const group = data.value.filter((item) => item.moduleId === moduleId)
668
- const hasChecked = group.some((item) => item.subModule1Checked)
669
- group.forEach((item) => {
670
- item.moduleChecked = hasChecked
671
- })
672
- }
673
-
674
- // 单元格点击事件
675
- const handleCellClick = (context) => {
676
- console.log('单元格点击:', context)
677
- }
678
-
679
- // 详情弹窗相关
680
- const detailDialogVisible = ref(false)
681
- const detailDialogTitle = ref('详情')
682
- const currentRow = ref(null)
683
-
684
-
685
- // 数据权限弹窗相关
686
- const permissionDialogVisible = ref(false)
687
- const permissionDialogTitle = ref('数据权限配置')
688
- const selectedPermission = ref(1) // 默认选择全部数据
689
-
690
- // 组织架构树相关
691
- const orgTreeData = ref([])
692
- const keyWord = ref('')
693
- const filteredOrgTreeData = computed(() => {
694
- if (!keyWord.value) return orgTreeData.value
695
- return filterTreeNodes(orgTreeData.value, keyWord.value)
696
- })
697
- const orgTreeKeys = {
698
- label: 'name',
699
- value: 'id',
700
- children: 'childs'
701
- }
702
- const expandedKeys = ref([])
703
- const activeKey = ref(null)
704
- const selectedOrgs = ref([])
705
-
706
- // 过滤树节点函数
707
- const filterTreeNodes = (nodes, keyword) => {
708
- if (!nodes || !Array.isArray(nodes)) return []
709
-
710
- const filtered = []
711
-
712
- for (const node of nodes) {
713
- // 创建节点副本,避免修改原始数据
714
- const newNode = { ...node }
715
-
716
- // 检查当前节点名称是否包含关键词
717
- const matchesKeyword = node.name && node.name.toLowerCase().includes(keyword.toLowerCase())
718
-
719
- // 递归过滤子节点
720
- let filteredChildren = []
721
- if (node.childs && node.childs.length > 0) {
722
- filteredChildren = filterTreeNodes(node.childs, keyword)
723
- }
724
-
725
- // 如果当前节点匹配或者有匹配的子节点,则保留
726
- if (matchesKeyword || filteredChildren.length > 0) {
727
- if (filteredChildren.length > 0) {
728
- newNode.childs = filteredChildren
729
- }
730
- filtered.push(newNode)
731
- }
732
- }
733
-
734
- return filtered
735
- }
736
-
737
- // 处理组织节点选中
738
- const handleOrgNodeSelect = (node, checked) => {
739
- if (checked) {
740
- // 检查是否已经存在
741
- if (!selectedOrgs.value.find((org) => org.id === node.id)) {
742
- selectedOrgs.value.push({
743
- id: node.id,
744
- name: node.name
745
- })
746
- }
747
- } else {
748
- // 从已选中中移除
749
- removeSelectedOrg(node.id)
750
- }
751
- }
752
-
753
- // 移除选中的组织
754
- const removeSelectedOrg = (id) => {
755
- const index = selectedOrgs.value.findIndex((org) => org.id === id)
756
- if (index !== -1) {
757
- selectedOrgs.value.splice(index, 1)
758
- }
759
- }
760
-
761
- // 清空已选择的组织
762
- const clearSelectedOrgs = () => {
763
- selectedOrgs.value = []
764
- }
765
-
766
- // 检查节点是否已被选中
767
- const isOrgNodeSelected = (id) => {
768
- return selectedOrgs.value.some((org) => org.id === id)
769
- }
770
-
771
- // 切换组织节点选中状态
772
- const toggleOrgNodeSelect = (node) => {
773
- const isSelected = isOrgNodeSelected(node.id)
774
- handleOrgNodeSelect(node, !isSelected)
775
- }
776
-
777
- // 处理组织节点激活
778
- const handleOrgNodeActive = (value) => {
779
- activeKey.value = value
780
- }
781
-
782
- // 处理组织节点展开
783
- const handleOrgNodeExpand = (value) => {
784
- expandedKeys.value = value
785
- }
786
-
787
- // 打开数据权限配置弹窗
788
- const openPermissionDialog = (row) => {
789
- currentRow.value = row
790
- // 检查是否已有自定义配置
791
- if (row.hasCustomConfig) {
792
- // 已有自定义配置,使用自身配置
793
- selectedPermission.value = row.dataPermission || 1
794
- if (row.dataPermission === 6 && row.selectedOrgs) {
795
- selectedOrgs.value = [...row.selectedOrgs]
796
- } else {
797
- selectedOrgs.value = []
798
- }
799
- } else {
800
- // 没有自定义配置,继承全局配置
801
- selectedPermission.value = globalPermission.value
802
- if (globalPermission.value === 6) {
803
- selectedOrgs.value = [...globalSelectedOrgs.value]
804
- } else {
805
- selectedOrgs.value = []
806
- }
807
- }
808
- expandedKeys.value = ['1']
809
- let title = '数据权限配置: '
810
- if (row.module) title += row.module
811
- if (row.subModule1 && row.subModule1 !== row.module) title += ' - ' + row.subModule1
812
- if (row.subModule2 && row.subModule2 !== row.subModule1) title += ' - ' + row.subModule2
813
- if (row.functions && row.functions.length === 1 && row.functions[0].name !== '全部')
814
- title += ' - ' + row.functions[0].name
815
- permissionDialogTitle.value = title
816
- permissionDialogVisible.value = true
817
- }
818
-
819
- // 全局数据权限相关
820
- const globalPermission = ref(1) // 默认全部数据
821
- const globalSelectedOrgs = ref([])
822
- const tempGlobalSelectedOrgs = ref([])
823
- const globalOrgDialogVisible = ref(false)
824
-
825
- // 打开全局组织选择器
826
- const openGlobalOrgSelector = () => {
827
- // 初始化临时选择的组织列表
828
- tempGlobalSelectedOrgs.value = [...globalSelectedOrgs.value]
829
-
830
- // 加载组织架构树数据
831
- expandedKeys.value = ['1'] // 默认展开第一级
832
-
833
- globalOrgDialogVisible.value = true
834
- }
835
-
836
- // 全局组织节点选择相关函数
837
- const handleGlobalOrgNodeSelect = (node, checked) => {
838
- if (checked) {
839
- // 检查是否已经存在
840
- if (!tempGlobalSelectedOrgs.value.find((org) => org.id === node.id)) {
841
- tempGlobalSelectedOrgs.value.push({
842
- id: node.id,
843
- name: node.name
844
- })
845
- }
846
- } else {
847
- // 从已选中中移除
848
- removeGlobalSelectedOrg(node.id)
849
- }
850
- }
851
-
852
- // 移除选中的全局组织(对话框中使用)
853
- const removeGlobalSelectedOrg = (id) => {
854
- console.log('移除临时全局组织', id);
855
- const index = tempGlobalSelectedOrgs.value.findIndex((org) => org.id === id);
856
- if (index !== -1) {
857
- tempGlobalSelectedOrgs.value.splice(index, 1);
858
- console.log('移除成功,剩余组织数量:', tempGlobalSelectedOrgs.value.length);
859
- } else {
860
- console.log('未找到对应组织');
861
- }
862
- }
863
-
864
- // 直接从全局组织中移除(主界面使用)
865
- const removeGlobalOrg = (id) => {
866
- const index = globalSelectedOrgs.value.findIndex((org) => org.id === id)
867
- if (index !== -1) {
868
- globalSelectedOrgs.value.splice(index, 1)
869
- }
870
- }
871
-
872
- // 清空已选择的全局组织(用于弹窗中)
873
- const clearGlobalSelectedOrgs = () => {
874
- tempGlobalSelectedOrgs.value = []
875
- }
876
-
877
- // 清空主界面已选择的全局组织
878
- const clearGlobalOrgs = () => {
879
- globalSelectedOrgs.value = []
880
- }
881
-
882
- // 检查全局节点是否已被选中
883
- const isGlobalOrgNodeSelected = (id) => {
884
- return tempGlobalSelectedOrgs.value.some((org) => org.id === id)
885
- }
886
-
887
- // 切换全局组织节点选中状态
888
- const toggleGlobalOrgNodeSelect = (node) => {
889
- const isSelected = isGlobalOrgNodeSelected(node.id)
890
- handleGlobalOrgNodeSelect(node, !isSelected)
891
- }
892
-
893
- // 确认全局组织选择
894
- const handleGlobalOrgConfirm = () => {
895
- // 保存选择结果
896
- globalSelectedOrgs.value = [...tempGlobalSelectedOrgs.value]
897
-
898
- // 应用到未配置自定义数据权限的行
899
- applyGlobalOrgsToCustomRows()
900
-
901
- globalOrgDialogVisible.value = false
902
- }
903
-
904
- // 取消全局组织选择
905
- const handleGlobalOrgCancel = () => {
906
- globalOrgDialogVisible.value = false
907
- }
908
-
909
- // 将全局组织应用到未配置自定义数据权限的行
910
- const applyGlobalOrgsToCustomRows = () => {
911
- // 遍历每一行数据
912
- data.value.forEach((row) => {
913
- // 只对没有自定义配置的行应用全局设置
914
- if (!row.hasCustomConfig) {
915
- // 应用全局权限设置
916
- row.dataPermission = globalPermission.value
917
-
918
- // 如果全局设置是自定义数据权限,则应用全局选择的组织
919
- if (globalPermission.value === 6) {
920
- row.selectedOrgs = [...globalSelectedOrgs.value]
921
- } else {
922
- row.selectedOrgs = []
923
- }
924
- }
925
- })
926
- }
927
-
928
- // 确认权限配置(修改处理全局配置的情况)
929
- const handlePermissionConfirm = () => {
930
- if (currentRow.value) {
931
- // 标记该行已有自定义配置
932
- currentRow.value.hasCustomConfig = true;
933
- currentRow.value.dataPermission = selectedPermission.value
934
- if (selectedPermission.value === 6) {
935
- currentRow.value.selectedOrgs = [...selectedOrgs.value]
936
- } else {
937
- currentRow.value.selectedOrgs = []
938
- }
939
- }
940
- permissionDialogVisible.value = false
941
- }
942
-
943
- // 取消权限配置(修改处理全局配置的情况)
944
- const handlePermissionCancel = () => {
945
- // 只关闭弹窗,不做其他操作
946
- permissionDialogVisible.value = false
947
- }
948
-
949
- // 获取匹配的节点总数
950
- const getMatchedCount = (nodes) => {
951
- if (!nodes || !Array.isArray(nodes)) return 0
952
-
953
- let count = nodes.length
954
-
955
- for (const node of nodes) {
956
- if (node.childs && node.childs.length > 0) {
957
- count += getMatchedCount(node.childs)
958
- }
959
- }
960
-
961
- return count
962
- }
963
-
964
- // 新增的搜索函数
965
- const onOrgSearch = () => {
966
- dataService
967
- .fetch(
968
- {
969
- postEnable: false,
970
- keyWord: keyWord.value
971
- },
972
- {},
973
- apis.orgTree
974
- )
975
- .then(async (res) => {
976
- orgTreeData.value = res;
977
- if (props.type === 'post') {
978
- roleId.value = (await dataService.fetch({}, {}, apis.getRole.replace('{positionId}', props.id))).id
979
- } else {
980
- roleId.value = props.id;
981
- }
982
- if (!roleId.value) return;
983
- let timer = setInterval(() => {
984
- if (data.value.length > 0) {
985
- clearInterval(timer)
986
- getRoleConfig()
987
- }
988
- }, 500);
989
- dataService.fetch({}, {}, apis.gerRoleInfo.replace('{id}', roleId.value)).then(res => {
990
- globalPermission.value = res.dataScope;
991
- // 使用递归查找函数获取完整组织信息
992
- globalSelectedOrgs.value = [...res.deptIds].map(id => {
993
- const org = findOrgInTree(orgTreeData.value, id);
994
- return {
995
- id: id,
996
- name: org ? org.name : `组织${id}` // 如果找不到组织,提供一个默认名称
997
- };
998
- });
999
- // 查询角色信息
1000
- })
1001
- })
1002
- }
1003
- onOrgSearch()
1004
-
1005
- // 获取所有父级权限ID的辅助函数
1006
- const getParentPermissionIds = (functionIds, row) => {
1007
- const parentIds = new Set()
1008
-
1009
- // 添加模块ID(如果存在)
1010
- if (row.moduleId) {
1011
- parentIds.add(row.moduleId)
1012
- }
1013
-
1014
- // 添加子模块1 ID(如果存在)
1015
- if (row.subModule1Id) {
1016
- parentIds.add(row.subModule1Id)
1017
- }
1018
-
1019
- // 添加子模块2 ID(如果存在)
1020
- if (row.subModule2Id) {
1021
- parentIds.add(row.subModule2Id)
1022
- }
1023
-
1024
- // 将Set转换为数组并去重
1025
- return Array.from(parentIds)
1026
- }
1027
-
1028
- // 计算选中的数据权限结构
1029
- const computedSelectedData = computed(() => {
1030
- // 全局数据权限
1031
- const dataScope = globalPermission.value
1032
- const deptIds = globalSelectedOrgs.value.map((org) => org.id)
1033
- // 功能明细
1034
- const functionList = []
1035
- data.value.forEach((row) => {
1036
- // 只处理有选中功能的行
1037
- const checkedFunctionIds = row.functions
1038
- .filter((f) => f.checked)
1039
- .map((f) => f.id)
1040
- .filter(Boolean)
1041
- if (checkedFunctionIds.length > 0) {
1042
- // 获取父级权限ID
1043
- const parentIds = getParentPermissionIds(checkedFunctionIds, row)
1044
-
1045
- // 合并功能ID和父级权限ID,去重
1046
- const allFunctionIds = [...new Set([...checkedFunctionIds, ...parentIds])]
1047
-
1048
- functionList.push({
1049
- functionIds: allFunctionIds,
1050
- dataScope: row.dataPermission || undefined,
1051
- deptIds:
1052
- row.dataPermission === 6 && row.selectedOrgs && row.selectedOrgs.length > 0
1053
- ? row.selectedOrgs.map((org) => org.id)
1054
- : []
1055
- })
1056
- }
1057
- })
1058
- let role = {
1059
- dataScope,
1060
- deptIds,
1061
- }
1062
- if (roleId.value) {
1063
- role.id = roleId.value
1064
- } else {
1065
- role.positionId = props.id
1066
- }
1067
- return {
1068
- role,
1069
- functionList
1070
- }
1071
- })
1072
-
1073
- watch(globalPermission, (val) => {
1074
- if (val !== 6) {
1075
- globalSelectedOrgs.value = []
1076
- }
1077
- })
1078
-
1079
- function handleSaveRolePermission() {
1080
- // 这里可以调用API或打印computedSelectedData
1081
- console.log('保存角色权限:', computedSelectedData.value)
1082
- dataService.fetch(
1083
- computedSelectedData.value,
1084
- {},
1085
- apis.saveRole
1086
- ).then(res => {
1087
- if (!res) {
1088
- Message.error('保存失败')
1089
- return
1090
- }
1091
- emits('success')
1092
- })
1093
- }
1094
-
1095
- // 暴露表格引用和方法
1096
- defineExpose({
1097
- selData: computedSelectedData
1098
- })
1099
-
1100
- // 先添加一个从树形结构中递归查找组织的辅助函数
1101
- const findOrgInTree = (tree, id) => {
1102
- if (!tree || !Array.isArray(tree)) return null;
1103
-
1104
- // 先在当前层级查找
1105
- const found = tree.find(item => item.id === id);
1106
- if (found) return found;
1107
-
1108
- // 如果当前层级没找到,递归查找子节点
1109
- for (const node of tree) {
1110
- if (node.childs && node.childs.length > 0) {
1111
- const foundInChild = findOrgInTree(node.childs, id);
1112
- if (foundInChild) return foundInChild;
1113
- }
1114
- }
1115
-
1116
- return null; // 未找到
1117
- };
1118
-
1119
- // 添加用于更新父级勾选状态的辅助函数
1120
- const updateParentCheckStatus = (row) => {
1121
- // 判断当前行的功能是否有被勾选
1122
- const hasFunctionsChecked = row.functions.some(func => func.checked);
1123
-
1124
- // 更新子模块2的勾选状态
1125
- if (row.subModule2) {
1126
- // 寻找同组的所有行
1127
- const sameGroup = data.value.filter(item =>
1128
- item.moduleId === row.moduleId &&
1129
- item.subModule1Id === row.subModule1Id &&
1130
- item.subModule2Id === row.subModule2Id
1131
- );
1132
-
1133
- // 判断同组的所有行是否有功能被勾选
1134
- const hasRowsChecked = sameGroup.some(item =>
1135
- item.functions.some(func => func.checked)
1136
- );
1137
-
1138
- // 更新子模块2的勾选状态
1139
- sameGroup.forEach(item => {
1140
- item.subModule2Checked = hasRowsChecked;
1141
- });
1142
- }
1143
-
1144
- // 更新子模块1的勾选状态
1145
- updateSubModule1Checked(row.moduleId, row.subModule1Id);
1146
- }
1147
- </script>
1148
-
1149
- <style lang="less" scoped>
1150
- .cell-with-checkbox {
1151
- display: flex;
1152
- align-items: flex-start;
1153
-
1154
- .cell-content {
1155
- margin-left: 8px;
1156
- display: flex;
1157
- flex-direction: column;
1158
- }
1159
-
1160
- .cell-text {
1161
- line-height: 1.4;
1162
- }
1163
-
1164
- .cell-id {
1165
- margin-top: 2px;
1166
- color: #999;
1167
- font-size: 12px;
1168
- font-family: monospace;
1169
- line-height: 1.2;
1170
- }
1171
-
1172
- .empty-cell {
1173
- color: #ccc;
1174
- font-style: italic;
1175
- }
1176
- }
1177
-
1178
- .functions-container {
1179
- display: flex;
1180
- flex-wrap: wrap;
1181
- gap: 12px;
1182
-
1183
- .function-item {
1184
- display: flex;
1185
- align-items: flex-start;
1186
- margin-right: 12px;
1187
-
1188
- .cell-content {
1189
- margin-left: 8px;
1190
- display: flex;
1191
- flex-direction: column;
1192
- }
1193
-
1194
- .cell-text {
1195
- line-height: 1.4;
1196
- }
1197
-
1198
- .cell-id {
1199
- margin-top: 2px;
1200
- color: #999;
1201
- font-size: 12px;
1202
- font-family: monospace;
1203
- line-height: 1.2;
1204
- }
1205
- }
1206
- }
1207
-
1208
- .operation-buttons {
1209
- display: flex;
1210
- justify-content: center;
1211
- }
1212
-
1213
- .permission-content {
1214
- padding: 20px 0;
1215
-
1216
- .permission-item {
1217
- .permission-options {
1218
- margin-bottom: 20px;
1219
-
1220
- :deep(.t-radio-group) {
1221
- display: flex;
1222
- flex-wrap: wrap;
1223
-
1224
- .t-radio-button {
1225
- margin-bottom: 10px;
1226
- margin-right: 0;
1227
- }
1228
- }
1229
- }
1230
- }
1231
- }
1232
-
1233
- .custom-permission {
1234
- margin-top: 16px;
1235
- }
1236
-
1237
- .org-selector-dialog {
1238
- padding: 0;
1239
- }
1240
-
1241
- // 组织选择器弹窗样式
1242
- .org-dialog-container {
1243
- display: flex;
1244
- height: 400px;
1245
- border: 1px solid #eaeaea;
1246
- border-radius: 4px;
1247
- overflow: hidden;
1248
- }
1249
-
1250
- .org-left-panel {
1251
- flex: 1;
1252
- display: flex;
1253
- flex-direction: column;
1254
- border-right: 1px solid #eaeaea;
1255
- }
1256
-
1257
- .org-panel-header {
1258
- padding: 16px;
1259
- font-weight: 500;
1260
- color: #181818;
1261
- font-size: 14px;
1262
- border-bottom: 1px solid #eaeaea;
1263
- background-color: #f5f5f5;
1264
- display: flex;
1265
- justify-content: space-between;
1266
- align-items: center;
1267
- }
1268
-
1269
- .org-search-container {
1270
- padding: 12px 16px;
1271
- border-bottom: 1px solid #eaeaea;
1272
-
1273
- :deep(.t-input) {
1274
- border-radius: 4px;
1275
-
1276
- .t-input__inner {
1277
- height: 32px;
1278
- }
1279
-
1280
- .t-input__suffix-icon {
1281
- cursor: pointer;
1282
- color: #999;
1283
-
1284
- &:hover {
1285
- color: var(--td-brand-color);
1286
- }
1287
- }
1288
- }
1289
-
1290
- .search-result-info {
1291
- font-size: 12px;
1292
- color: #999;
1293
- margin-top: 8px;
1294
- }
1295
- }
1296
-
1297
- .org-tree-container {
1298
- flex: 1;
1299
- overflow: auto;
1300
- padding: 16px;
1301
- }
1302
-
1303
- .org-right-panel {
1304
- width: 300px;
1305
- display: flex;
1306
- flex-direction: column;
1307
- }
1308
-
1309
- .org-selected-container {
1310
- flex: 1;
1311
- overflow: auto;
1312
- padding: 16px;
1313
- background-color: #fcfcfc;
1314
- }
1315
-
1316
- .selected-orgs-tags {
1317
- display: flex;
1318
- flex-wrap: wrap;
1319
- gap: 8px;
1320
-
1321
- .org-tag {
1322
- margin: 0;
1323
- white-space: nowrap;
1324
- transition: all 0.2s ease;
1325
- border-radius: 4px;
1326
- cursor: pointer;
1327
-
1328
- &:hover {
1329
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1330
- transform: translateY(-1px);
1331
- }
1332
- }
1333
- }
1334
-
1335
- .no-selected-tip {
1336
- color: #999;
1337
- text-align: center;
1338
- padding: 60px 0;
1339
- font-size: 14px;
1340
- }
1341
-
1342
- .detail-content {
1343
- padding: 16px;
1344
-
1345
- .detail-item {
1346
- margin-bottom: 16px;
1347
-
1348
- .item-label {
1349
- font-weight: bold;
1350
- margin-right: 8px;
1351
- color: #666;
1352
- }
1353
-
1354
- .item-value {
1355
- color: #333;
1356
- }
1357
-
1358
- .sub-functions {
1359
- margin-top: 8px;
1360
-
1361
- .sub-function-item {
1362
- padding: 4px 0;
1363
- border-bottom: 1px dashed #eee;
1364
-
1365
- &:last-child {
1366
- border-bottom: none;
1367
- }
1368
- }
1369
- }
1370
- }
1371
- }
1372
-
1373
- .tree-node-label {
1374
- cursor: pointer;
1375
- padding: 4px 0;
1376
- user-select: none;
1377
-
1378
- &:hover {
1379
- color: var(--td-brand-color);
1380
- }
1381
-
1382
- &.selected {
1383
- color: var(--td-brand-color);
1384
- font-weight: 500;
1385
- }
1386
- }
1387
-
1388
- // 数据权限配置面板样式
1389
- .permission-config-panel {
1390
- margin-bottom: 16px;
1391
- border: 1px solid #e7e7e7;
1392
- border-radius: 6px;
1393
- overflow: hidden;
1394
- background-color: #fff;
1395
-
1396
- .panel-header {
1397
- background-color: #f5f5f5;
1398
- padding: 12px 16px;
1399
- border-bottom: 1px solid #e7e7e7;
1400
-
1401
- .panel-title {
1402
- margin: 0;
1403
- font-size: 16px;
1404
- font-weight: bold;
1405
- color: #333;
1406
- }
1407
- }
1408
-
1409
- .panel-content {
1410
- padding: 16px;
1411
-
1412
- .permission-type-selector {
1413
- display: flex;
1414
- align-items: center;
1415
- margin-bottom: 16px;
1416
-
1417
- .permission-radio-group {
1418
- display: flex;
1419
- flex-wrap: wrap;
1420
-
1421
- :deep(.t-radio-button) {
1422
- margin-bottom: 10px;
1423
- margin-right: 0;
1424
- }
1425
- }
1426
- }
1427
-
1428
- .org-select-row {
1429
- display: flex;
1430
- align-items: center;
1431
- margin-bottom: 16px;
1432
-
1433
- .selected-count {
1434
- color: #666;
1435
- margin-right: 12px;
1436
- }
1437
-
1438
- .org-select-btn {
1439
- height: 32px;
1440
- }
1441
- }
1442
-
1443
- .selected-orgs-panel {
1444
- display: flex;
1445
- flex-direction: column;
1446
- border: 1px solid #eaeaea;
1447
- border-radius: 4px;
1448
- margin-top: 16px;
1449
-
1450
- .selected-orgs-header {
1451
- display: flex;
1452
- justify-content: space-between;
1453
- align-items: center;
1454
- padding: 10px 16px;
1455
- font-size: 14px;
1456
- color: #181818;
1457
- border-bottom: 1px solid #eaeaea;
1458
- background-color: #f5f5f5;
1459
- }
1460
-
1461
- .selected-orgs-content {
1462
- padding: 16px;
1463
- min-height: 80px;
1464
-
1465
- .no-org-selected {
1466
- color: #999;
1467
- text-align: center;
1468
- padding: 20px 0;
1469
- }
1470
-
1471
- .selected-orgs-tags {
1472
- display: flex;
1473
- flex-wrap: wrap;
1474
- gap: 8px;
1475
-
1476
- .org-tag {
1477
- margin: 0;
1478
- overflow: hidden;
1479
- text-overflow: ellipsis;
1480
- white-space: nowrap;
1481
- }
1482
- }
1483
- }
1484
- }
1485
- }
1486
- }
1487
-
1488
- .no-search-results {
1489
- text-align: center;
1490
- padding: 30px 0;
1491
- color: #999;
1492
- font-size: 14px;
1493
- }
1494
- </style>
1
+ <template>
2
+ <div>
3
+ <!-- 选中数据结构展示 -->
4
+ <!-- <div style="margin-bottom: 16px">
5
+ <t-card title="选中数据结构(JSON)">
6
+ <pre style="background: #f6f8fa; padding: 12px 16px; border-radius: 6px; overflow: auto; max-height: 300px"
7
+ >{{ JSON.stringify(computedSelectedData, null, 2) }}
8
+ </pre>
9
+ </t-card>
10
+ </div> -->
11
+ <!-- 树表上方的数据权限配置模块 -->
12
+ <div class="permission-config-panel">
13
+ <div class="panel-header">
14
+ <h3 class="panel-title">数据权限配置</h3>
15
+ </div>
16
+ <div class="panel-content">
17
+ <div class="permission-type-selector">
18
+ <t-radio-group v-model="globalPermission" class="permission-radio-group">
19
+ <t-radio-button :value="1">全部数据</t-radio-button>
20
+ <t-radio-button :value="2">事业部数据</t-radio-button>
21
+ <t-radio-button :value="3">本部门数据</t-radio-button>
22
+ <t-radio-button :value="4">子部门数据</t-radio-button>
23
+ <t-radio-button :value="5">个人数据</t-radio-button>
24
+ <t-radio-button :value="6">自定义数据</t-radio-button>
25
+ </t-radio-group>
26
+ </div>
27
+
28
+ <div v-if="globalPermission === 6" class="org-select-row">
29
+ <div class="selected-count">
30
+ {{ globalSelectedOrgs.length > 0 ? '已选择' + globalSelectedOrgs.length + '个组织' : '未选择组织' }}
31
+ </div>
32
+ <t-button theme="primary" @click="openGlobalOrgSelector" class="org-select-btn">
33
+ {{ globalSelectedOrgs.length > 0 ? '编辑' : '选择' }}
34
+ <template #suffix>
35
+ <t-icon name="chevron-right" />
36
+ </template>
37
+ </t-button>
38
+ </div>
39
+
40
+ <div v-if="globalPermission === 6" class="selected-orgs-panel">
41
+ <div class="selected-orgs-header">
42
+ 已选组织:
43
+ <t-button theme="primary" variant="text" size="small" @click="clearGlobalOrgs">清空</t-button>
44
+ </div>
45
+ <div class="selected-orgs-content">
46
+ <div v-if="globalSelectedOrgs.length === 0" class="no-org-selected">请从左侧选择组织</div>
47
+ <div v-else class="selected-orgs-tags">
48
+ <t-tag v-for="org in globalSelectedOrgs" :key="org.id" theme="primary" variant="light" class="org-tag"
49
+ closable @close="removeGlobalOrg(org.id)">
50
+ {{ org.name }}
51
+ </t-tag>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ <t-table :bordered="true" :data="data" :columns="columns" row-key="id" :rowspan-and-colspan="rowspanAndColspan"
59
+ resizable table-layout="fixed" lazy-load @cell-click="handleCellClick">
60
+ <!-- 模块列的自定义单元格 -->
61
+ <template #module="{ row }">
62
+ <div class="cell-with-checkbox">
63
+ <t-checkbox v-model="row.moduleChecked" @change="(checked) => handleModuleCheck(row, checked)"></t-checkbox>
64
+ <div class="cell-content">
65
+ <div class="cell-text">{{ row.module }}</div>
66
+ <div class="cell-id" v-if="row.moduleKey">{{ row.moduleKey }}</div>
67
+ </div>
68
+ </div>
69
+ </template>
70
+
71
+ <!-- 子模块1列的自定义单元格 -->
72
+ <template #subModule1="{ row }">
73
+ <div class="cell-with-checkbox">
74
+ <t-checkbox v-model="row.subModule1Checked"
75
+ @change="(checked) => handleSubModule1Check(row, checked)"></t-checkbox>
76
+ <div class="cell-content">
77
+ <div class="cell-text">{{ row.subModule1 || row.module }}</div>
78
+ <div class="cell-id" v-if="row.subModule1Key">{{ row.subModule1Key }}</div>
79
+ </div>
80
+ </div>
81
+ </template>
82
+
83
+ <!-- 子模块2列的自定义单元格 -->
84
+ <template #subModule2="{ row }">
85
+ <div class="cell-with-checkbox" v-if="row.subModule2">
86
+ <t-checkbox v-model="row.subModule2Checked"
87
+ @change="(checked) => handleSubModule2Check(row, checked)"></t-checkbox>
88
+ <div class="cell-content">
89
+ <div class="cell-text">{{ row.subModule2 }}</div>
90
+ <div class="cell-id" v-if="row.subModule2Key">{{ row.subModule2Key }}</div>
91
+ </div>
92
+ </div>
93
+ <div v-else class="cell-with-checkbox">
94
+ <span class="cell-text empty-cell">-</span>
95
+ </div>
96
+ </template>
97
+
98
+ <!-- 功能列的自定义单元格 -->
99
+ <template #functions="{ row }">
100
+ <div class="functions-container">
101
+ <div v-for="(func, idx) in row.functions" :key="func.id || idx" class="function-item">
102
+ <t-checkbox v-model="func.checked"
103
+ @change="(checked) => handleFunctionCheck(row, func, checked)"></t-checkbox>
104
+ <div class="cell-content">
105
+ <div class="cell-text">{{ func.name }}</div>
106
+ <div class="cell-id" v-if="func.funcKey">{{ func.funcKey }}</div>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ </template>
111
+
112
+ <!-- 操作列的自定义单元格 -->
113
+ <template #operation="{ row }">
114
+ <div class="operation-buttons">
115
+ <t-button theme="primary" size="medium" @click="openPermissionDialog(row)"
116
+ :disabled="!row.functions.some((f) => f.checked)">数据权限配置</t-button>
117
+ </div>
118
+ </template>
119
+
120
+ <slot></slot>
121
+ </t-table>
122
+
123
+ <!-- 新增保存按钮 -->
124
+ <div style="margin-top: 24px; display: flex; justify-content: flex-end">
125
+ <t-button theme="primary" @click="handleSaveRolePermission">保存角色权限</t-button>
126
+ </div>
127
+
128
+ <!-- 全局组织选择弹窗 -->
129
+ <t-dialog v-model:visible="globalOrgDialogVisible" header="选择组织" width="800px" attach="body"
130
+ :confirm-btn="{ content: '确定', theme: 'primary' }" :cancel-btn="{ content: '取消', theme: 'default' }"
131
+ @confirm="handleGlobalOrgConfirm" @cancel="handleGlobalOrgCancel">
132
+ <div class="org-selector-dialog">
133
+ <div class="org-dialog-container">
134
+ <div class="org-left-panel">
135
+ <div class="org-panel-header">组织架构</div>
136
+ <div class="org-search-container">
137
+ <t-input v-model="keyWord" placeholder="请输入关键词搜索" clearable style="width: 100%">
138
+ <template #suffix>
139
+ <t-button theme="default" shape="square" @click="onOrgSearch" size="small">
140
+ <t-icon name="search" />
141
+ </t-button>
142
+ </template>
143
+ </t-input>
144
+ <div v-if="keyWord" class="search-result-info">
145
+ 找到 {{ getMatchedCount(filteredOrgTreeData) }} 个结果
146
+ </div>
147
+ </div>
148
+ <div class="org-tree-container">
149
+ <t-tree :data="filteredOrgTreeData" :keys="orgTreeKeys" activable hover expandAll line
150
+ expand-on-click-node :expanded="expandedKeys" :active="activeKey" @active="handleOrgNodeActive"
151
+ @expand="handleOrgNodeExpand">
152
+ <template #operations="{ node }">
153
+ <t-checkbox :checked="isGlobalOrgNodeSelected(node.data.id)"
154
+ @change="(checked) => handleGlobalOrgNodeSelect(node.data, checked)"></t-checkbox>
155
+ </template>
156
+ <template #label="{ node }">
157
+ <div class="tree-node-label" :class="{ selected: isGlobalOrgNodeSelected(node.data.id) }"
158
+ @click.stop="toggleGlobalOrgNodeSelect(node.data)">
159
+ {{ node.data.name }}
160
+ </div>
161
+ </template>
162
+ </t-tree>
163
+ <div v-if="keyWord && filteredOrgTreeData.length === 0" class="no-search-results">
164
+ 未找到符合"{{ keyWord }}"的组织
165
+ </div>
166
+ </div>
167
+ </div>
168
+ <div class="org-right-panel">
169
+ <div class="org-panel-header">
170
+ <span>已选择组织</span>
171
+ <t-button theme="primary" variant="text" @click="clearGlobalSelectedOrgs">清空</t-button>
172
+ </div>
173
+ <div class="org-selected-container">
174
+ <div v-if="tempGlobalSelectedOrgs.length === 0" class="no-selected-tip">请从左侧选择组织</div>
175
+ <div v-else class="selected-orgs-tags">
176
+ <t-tag v-for="org in tempGlobalSelectedOrgs" :key="org.id" closable @close="removeGlobalSelectedOrg(org.id)"
177
+ theme="primary" variant="light" class="org-tag">
178
+ {{ org.name }}
179
+ </t-tag>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ </t-dialog>
186
+
187
+ <!-- 数据权限配置弹窗(针对表格行) -->
188
+ <t-dialog v-model:visible="permissionDialogVisible" :header="permissionDialogTitle" width="800px" attach="body"
189
+ :confirm-btn="{ content: '确定', theme: 'primary' }" :cancel-btn="{ content: '取消', theme: 'default' }"
190
+ @confirm="handlePermissionConfirm" @cancel="handlePermissionCancel">
191
+ <div class="permission-content">
192
+ <template v-if="currentRow">
193
+ <div class="permission-item">
194
+ <div class="permission-options">
195
+ <t-radio-group v-model="selectedPermission">
196
+ <t-radio-button :value="1">全部数据</t-radio-button>
197
+ <t-radio-button :value="2">事业部数据</t-radio-button>
198
+ <t-radio-button :value="3">本部门数据</t-radio-button>
199
+ <t-radio-button :value="4">子部门数据</t-radio-button>
200
+ <t-radio-button :value="5">个人数据</t-radio-button>
201
+ <t-radio-button :value="6">自定义数据</t-radio-button>
202
+ </t-radio-group>
203
+ </div>
204
+
205
+ <div v-if="selectedPermission === 6" class="custom-permission">
206
+ <div class="org-dialog-container">
207
+ <div class="org-left-panel">
208
+ <div class="org-panel-header">组织架构</div>
209
+ <div class="org-search-container">
210
+ <t-input v-model="keyWord" placeholder="请输入关键词搜索" clearable style="width: 100%">
211
+ <template #suffix>
212
+ <t-button theme="default" shape="square" @click="onOrgSearch" size="small">
213
+ <t-icon name="search" />
214
+ </t-button>
215
+ </template>
216
+ </t-input>
217
+ <div v-if="keyWord" class="search-result-info">
218
+ 找到 {{ getMatchedCount(filteredOrgTreeData) }} 个结果
219
+ </div>
220
+ </div>
221
+ <div class="org-tree-container">
222
+ <t-tree :data="filteredOrgTreeData" :keys="orgTreeKeys" activable hover expandAll line
223
+ expand-on-click-node :expanded="expandedKeys" :active="activeKey" @active="handleOrgNodeActive"
224
+ @expand="handleOrgNodeExpand">
225
+ <template #operations="{ node }">
226
+ <t-checkbox :checked="isOrgNodeSelected(node.data.id)"
227
+ @change="(checked) => handleOrgNodeSelect(node.data, checked)"></t-checkbox>
228
+ </template>
229
+ <template #label="{ node }">
230
+ <div class="tree-node-label" :class="{ selected: isOrgNodeSelected(node.data.id) }"
231
+ @click.stop="toggleOrgNodeSelect(node.data)">
232
+ {{ node.data.name }}
233
+ </div>
234
+ </template>
235
+ </t-tree>
236
+ <div v-if="keyWord && filteredOrgTreeData.length === 0" class="no-search-results">
237
+ 未找到符合"{{ keyWord }}"的组织
238
+ </div>
239
+ </div>
240
+ </div>
241
+ <div class="org-right-panel">
242
+ <div class="org-panel-header">
243
+ <span>已选择组织</span>
244
+ <t-button theme="primary" variant="text" @click="clearSelectedOrgs">清空</t-button>
245
+ </div>
246
+ <div class="org-selected-container">
247
+ <div v-if="selectedOrgs.length === 0" class="no-selected-tip">请从左侧选择组织</div>
248
+ <div v-else class="selected-orgs-tags">
249
+ <t-tag v-for="org in selectedOrgs" :key="org.id" closable @close="removeSelectedOrg(org.id)"
250
+ theme="primary" variant="light" class="org-tag">
251
+ {{ org.name }}
252
+ </t-tag>
253
+ </div>
254
+ </div>
255
+ </div>
256
+ </div>
257
+ </div>
258
+ </div>
259
+ </template>
260
+ </div>
261
+ </t-dialog>
262
+
263
+ <!-- 保留原有的详情弹窗 -->
264
+ <t-dialog v-model:visible="detailDialogVisible" :header="detailDialogTitle" width="600px" :footer="false">
265
+ <div class="detail-content">
266
+ <template v-if="currentRow">
267
+ <div class="detail-item">
268
+ <span class="item-label">模块:</span>
269
+ <span class="item-value">{{ currentRow.module }}</span>
270
+ </div>
271
+
272
+ <div class="detail-item" v-if="currentRow.subModule1">
273
+ <span class="item-label">子模块1:</span>
274
+ <span class="item-value">{{ currentRow.subModule1 }}</span>
275
+ </div>
276
+
277
+ <div class="detail-item" v-if="currentRow.subModule2">
278
+ <span class="item-label">子模块2:</span>
279
+ <span class="item-value">{{ currentRow.subModule2 }}</span>
280
+ </div>
281
+
282
+ <div class="detail-item">
283
+ <span class="item-label">功能:</span>
284
+ <span class="item-value">{{ currentRow.function }}</span>
285
+ </div>
286
+
287
+ <div class="detail-item" v-if="currentRow.path">
288
+ <span class="item-label">路径:</span>
289
+ <span class="item-value">{{ currentRow.path }}</span>
290
+ </div>
291
+
292
+ <div class="detail-item" v-if="currentRow.childFunctions && currentRow.childFunctions.length > 0">
293
+ <span class="item-label">子功能:</span>
294
+ <div class="sub-functions">
295
+ <div v-for="(func, index) in currentRow.childFunctions" :key="index" class="sub-function-item">
296
+ <span>{{ func.name }}</span>
297
+ </div>
298
+ </div>
299
+ </div>
300
+ </template>
301
+ </div>
302
+ </t-dialog>
303
+ </div>
304
+ </template>
305
+
306
+ <script>
307
+ export default {
308
+ name: 'EbizTreeMergeTable'
309
+ }
310
+ </script>
311
+
312
+ <script setup>
313
+ import dataService from '../apiService/simpleDataService'
314
+ import { ref, computed, onMounted, watch } from 'vue'
315
+ import {
316
+ Table as TTable,
317
+ Checkbox as TCheckbox,
318
+ Button as TButton,
319
+ Dialog as TDialog,
320
+ RadioGroup as TRadioGroup,
321
+ RadioButton as TRadioButton,
322
+ Input as TInput,
323
+ Tree as TTree,
324
+ Tag as TTag,
325
+ Icon as TIcon,
326
+ Message
327
+ } from 'tdesign-vue-next'
328
+
329
+ const apis = {
330
+ // tree: '/menu/tree',
331
+ tree: '/function/tree',
332
+ orgTree: '/appdata/execute/plugin?key=organizational_structure',
333
+ getConfig: '/role/functions/search/{roleId}',
334
+ getRole: '/role/position/{positionId}',
335
+ gerRoleInfo: '/role/detail/{id}',
336
+ saveRole: '/role/save'
337
+ }
338
+
339
+ const props = defineProps({
340
+ type: {
341
+ type: String,
342
+ default: 'role' // role,post 角色,岗位
343
+ },
344
+ id: {
345
+ type: String,
346
+ default: ''
347
+ }
348
+ })
349
+ const emits = defineEmits(['success'])
350
+ const roleId = ref()
351
+
352
+ const data = ref([])
353
+ // 表格列配置,添加自定义单元格插槽
354
+ const columns = [
355
+ { colKey: 'module', title: '模块', width: 150, cell: 'module' },
356
+ { colKey: 'subModule1', title: '子模块1', width: 150, cell: 'subModule1' },
357
+ { colKey: 'subModule2', title: '子模块2', width: 150, cell: 'subModule2' },
358
+ { colKey: 'functions', title: '功能', width: 300, cell: 'functions' },
359
+ { colKey: 'operation', title: '操作', width: 100, cell: 'operation', fixed: 'right' }
360
+ ]
361
+
362
+
363
+ // 获取数据并处理
364
+ dataService.fetch({}, {}, apis.tree).then((res) => {
365
+ const processedData = processTreeData(res || [])
366
+ // 为每行数据添加选中状态属性
367
+ processedData.forEach((row) => {
368
+ // 添加选中状态属性
369
+ row.moduleChecked = false
370
+ row.subModule1Checked = false
371
+ row.subModule2Checked = false
372
+ row.functionChecked = false
373
+ })
374
+ data.value = processedData;
375
+ })
376
+ function getRoleConfig() {
377
+ dataService.fetch({}, {}, apis.getConfig.replace('{roleId}', roleId.value || props.id)).then((res) => {
378
+ res.forEach(item => {
379
+ // 获取functionId
380
+ const functionId = item.functionId;
381
+ // 在data.value中查找对应功能所在的行
382
+ data.value.forEach(row => {
383
+ // 检查functions数组中是否有匹配的功能
384
+ const matchedFunction = row.functions.find(func => func.id === functionId);
385
+ if (matchedFunction) {
386
+ // 设置功能勾选状态为true
387
+ matchedFunction.checked = true;
388
+
389
+ // 保存数据权限配置(如果有)
390
+ if (item.dataScope) {
391
+ row.dataPermission = item.dataScope;
392
+ // 标记为自定义配置
393
+ row.hasCustomConfig = true;
394
+ // 如果是自定义数据权限,则设置已选组织
395
+ if (item.dataScope === 6 && item.deptIds && item.deptIds.length > 0) {
396
+ row.selectedOrgs = item.deptIds.map(id => {
397
+ const org = findOrgInTree(orgTreeData.value, id);
398
+ return {
399
+ id: id,
400
+ name: org ? org.name : `组织${id}`
401
+ };
402
+ });
403
+ }
404
+ }
405
+
406
+ // 更新父级勾选状态(模块、子模块)
407
+ updateParentCheckStatus(row);
408
+ }
409
+ });
410
+ });
411
+ })
412
+ }
413
+ /**
414
+ * 处理树形数据,生成四级结构(模块、子模块1、子模块2、功能列表)
415
+ * @param {Array} treeData 原始树形数据
416
+ * @returns {Array} 扁平化处理后的数据
417
+ */
418
+ const processTreeData = (treeData) => {
419
+ const result = []
420
+ // 处理第一级:模块
421
+ treeData.forEach((moduleItem) => {
422
+ if (!moduleItem.children || moduleItem.children.length === 0) {
423
+ // 没有子模块,功能为全部
424
+ result.push({
425
+ id: `${moduleItem.id}_all`,
426
+ module: moduleItem.name,
427
+ moduleId: moduleItem.id,
428
+ moduleKey: moduleItem.funcKey,
429
+ subModule1: null,
430
+ subModule1Id: null,
431
+ subModule1Key: null,
432
+ subModule2: null,
433
+ subModule2Id: null,
434
+ subModule2Key: null,
435
+ functions: [{ id: null, name: '全部', checked: false }]
436
+ })
437
+ return
438
+ }
439
+ moduleItem.children.forEach((subModule1Item) => {
440
+ if (!subModule1Item.children || subModule1Item.children.length === 0) {
441
+ // 没有子模块2,功能为全部
442
+ result.push({
443
+ id: `${moduleItem.id}_${subModule1Item.id}_all`,
444
+ module: moduleItem.name,
445
+ moduleId: moduleItem.id,
446
+ moduleKey: moduleItem.funcKey,
447
+ subModule1: subModule1Item.name,
448
+ subModule1Id: subModule1Item.id,
449
+ subModule1Key: subModule1Item.funcKey,
450
+ subModule2: null,
451
+ subModule2Id: null,
452
+ subModule2Key: null,
453
+ functions: [{ id: null, name: '全部', checked: false }]
454
+ })
455
+ } else {
456
+ subModule1Item.children.forEach((subModule2Item) => {
457
+ // 功能聚合
458
+ let functions = []
459
+ if (subModule2Item.children && subModule2Item.children.length > 0) {
460
+ subModule2Item.children.forEach((functionItem) => {
461
+ // 主功能
462
+ const func = {
463
+ id: functionItem.id,
464
+ name: functionItem.name,
465
+ funcKey: functionItem.funcKey,
466
+ checked: false
467
+ }
468
+ functions.push(func)
469
+ // 子功能
470
+ if (functionItem.children && functionItem.children.length > 0) {
471
+ functionItem.children.forEach((childFunc) => {
472
+ functions.push({
473
+ id: childFunc.id,
474
+ name: childFunc.name,
475
+ funcKey: childFunc.funcKey,
476
+ checked: false
477
+ })
478
+ })
479
+ }
480
+ })
481
+ } else {
482
+ functions.push({ id: null, name: '全部', checked: false })
483
+ }
484
+ result.push({
485
+ id: `${moduleItem.id}_${subModule1Item.id}_${subModule2Item.id}`,
486
+ module: moduleItem.name,
487
+ moduleId: moduleItem.id,
488
+ moduleKey: moduleItem.funcKey,
489
+ subModule1: subModule1Item.name,
490
+ subModule1Id: subModule1Item.id,
491
+ subModule1Key: subModule1Item.funcKey,
492
+ subModule2: subModule2Item.name,
493
+ subModule2Id: subModule2Item.id,
494
+ subModule2Key: subModule2Item.funcKey,
495
+ functions
496
+ })
497
+ })
498
+ }
499
+ })
500
+ })
501
+ return result
502
+ }
503
+
504
+ /**
505
+ * 单元格合并计算函数
506
+ */
507
+ const rowspanAndColspan = ({ row, col, rowIndex, colIndex }) => {
508
+ // 功能列合并逻辑
509
+ if (col.colKey === 'functions') {
510
+ const isFirstRow =
511
+ rowIndex === 0 ||
512
+ data.value[rowIndex - 1].moduleId !== row.moduleId ||
513
+ data.value[rowIndex - 1].subModule1Id !== row.subModule1Id ||
514
+ data.value[rowIndex - 1].subModule2Id !== row.subModule2Id
515
+ if (isFirstRow) {
516
+ let rowspan = 1
517
+ for (let i = rowIndex + 1; i < data.value.length; i++) {
518
+ if (
519
+ data.value[i].moduleId === row.moduleId &&
520
+ data.value[i].subModule1Id === row.subModule1Id &&
521
+ data.value[i].subModule2Id === row.subModule2Id
522
+ ) {
523
+ rowspan++
524
+ } else {
525
+ break
526
+ }
527
+ }
528
+ return { rowspan, colspan: 1 }
529
+ }
530
+ return { rowspan: 0, colspan: 0 }
531
+ }
532
+ // 模块列合并逻辑
533
+ if (col.colKey === 'module') {
534
+ const currentModuleId = row.moduleId
535
+ const isFirstRow = rowIndex === 0 || data.value[rowIndex - 1].moduleId !== currentModuleId
536
+ if (isFirstRow) {
537
+ let rowspan = 1
538
+ for (let i = rowIndex + 1; i < data.value.length; i++) {
539
+ if (data.value[i].moduleId === currentModuleId) {
540
+ rowspan++
541
+ } else {
542
+ break
543
+ }
544
+ }
545
+ return { rowspan, colspan: 1 }
546
+ }
547
+ return { rowspan: 0, colspan: 0 }
548
+ }
549
+ // 子模块1列合并逻辑
550
+ if (col.colKey === 'subModule1') {
551
+ if (!row.subModule1) return {}
552
+ const currentModule = row.moduleId
553
+ const currentSubModule1 = row.subModule1Id
554
+ const isFirstRow =
555
+ rowIndex === 0 ||
556
+ data.value[rowIndex - 1].subModule1Id !== currentSubModule1 ||
557
+ data.value[rowIndex - 1].moduleId !== currentModule
558
+ if (isFirstRow) {
559
+ let rowspan = 1
560
+ for (let i = rowIndex + 1; i < data.value.length; i++) {
561
+ if (data.value[i].moduleId === currentModule && data.value[i].subModule1Id === currentSubModule1) {
562
+ rowspan++
563
+ } else {
564
+ break
565
+ }
566
+ }
567
+ return { rowspan, colspan: 1 }
568
+ }
569
+ return { rowspan: 0, colspan: 0 }
570
+ }
571
+ // 子模块2列合并逻辑
572
+ if (col.colKey === 'subModule2') {
573
+ if (!row.subModule2) return {}
574
+ const currentModule = row.moduleId
575
+ const currentSubModule1 = row.subModule1Id
576
+ const currentSubModule2 = row.subModule2Id
577
+ const isFirstRow =
578
+ rowIndex === 0 ||
579
+ data.value[rowIndex - 1].subModule2Id !== currentSubModule2 ||
580
+ data.value[rowIndex - 1].subModule1Id !== currentSubModule1 ||
581
+ data.value[rowIndex - 1].moduleId !== currentModule
582
+ if (isFirstRow) {
583
+ let rowspan = 1
584
+ for (let i = rowIndex + 1; i < data.value.length; i++) {
585
+ if (
586
+ data.value[i].moduleId === currentModule &&
587
+ data.value[i].subModule1Id === currentSubModule1 &&
588
+ data.value[i].subModule2Id === currentSubModule2
589
+ ) {
590
+ rowspan++
591
+ } else {
592
+ break
593
+ }
594
+ }
595
+ return { rowspan, colspan: 1 }
596
+ }
597
+ return { rowspan: 0, colspan: 0 }
598
+ }
599
+ return {} // 其他列不合并
600
+ }
601
+
602
+ // 勾选模块时联动所有下级
603
+ const handleModuleCheck = (row, checked) => {
604
+ row.moduleChecked = checked
605
+ data.value.forEach((item) => {
606
+ if (item.moduleId === row.moduleId) {
607
+ item.subModule1Checked = checked
608
+ item.subModule2Checked = checked
609
+ item.functions.forEach((func) => (func.checked = checked))
610
+ }
611
+ })
612
+ }
613
+
614
+ // 勾选子模块1时联动所有下级
615
+ const handleSubModule1Check = (row, checked) => {
616
+ row.subModule1Checked = checked
617
+ data.value.forEach((item) => {
618
+ if (item.moduleId === row.moduleId && item.subModule1Id === row.subModule1Id) {
619
+ item.subModule2Checked = checked
620
+ item.functions.forEach((func) => (func.checked = checked))
621
+ }
622
+ })
623
+ // 向上联动模块
624
+ updateModuleChecked(row.moduleId)
625
+ }
626
+
627
+ // 勾选子模块2时联动所有下级
628
+ const handleSubModule2Check = (row, checked) => {
629
+ row.subModule2Checked = checked
630
+ data.value.forEach((item) => {
631
+ if (
632
+ item.moduleId === row.moduleId &&
633
+ item.subModule1Id === row.subModule1Id &&
634
+ item.subModule2Id === row.subModule2Id
635
+ ) {
636
+ item.functions.forEach((func) => (func.checked = checked))
637
+ }
638
+ })
639
+ // 向上联动子模块1
640
+ updateSubModule1Checked(row.moduleId, row.subModule1Id)
641
+ }
642
+
643
+ // 勾选功能时,自动判断父级
644
+ const handleFunctionCheck = (row, func, checked) => {
645
+ func.checked = checked
646
+ // 判断本组是否有功能被选中
647
+ const hasChecked = row.functions.some((f) => f.checked)
648
+ row.subModule2Checked = hasChecked
649
+ // 向上联动
650
+ updateSubModule1Checked(row.moduleId, row.subModule1Id)
651
+ }
652
+
653
+ // 更新子模块1的勾选状态
654
+ const updateSubModule1Checked = (moduleId, subModule1Id) => {
655
+ // 找到所有同模块+同子模块1的行
656
+ const group = data.value.filter((item) => item.moduleId === moduleId && item.subModule1Id === subModule1Id)
657
+ const hasChecked = group.some((item) => item.subModule2Checked)
658
+ group.forEach((item) => {
659
+ item.subModule1Checked = hasChecked
660
+ })
661
+ // 向上联动模块
662
+ updateModuleChecked(moduleId)
663
+ }
664
+
665
+ // 更新模块的勾选状态
666
+ const updateModuleChecked = (moduleId) => {
667
+ const group = data.value.filter((item) => item.moduleId === moduleId)
668
+ const hasChecked = group.some((item) => item.subModule1Checked)
669
+ group.forEach((item) => {
670
+ item.moduleChecked = hasChecked
671
+ })
672
+ }
673
+
674
+ // 单元格点击事件
675
+ const handleCellClick = (context) => {
676
+ console.log('单元格点击:', context)
677
+ }
678
+
679
+ // 详情弹窗相关
680
+ const detailDialogVisible = ref(false)
681
+ const detailDialogTitle = ref('详情')
682
+ const currentRow = ref(null)
683
+
684
+
685
+ // 数据权限弹窗相关
686
+ const permissionDialogVisible = ref(false)
687
+ const permissionDialogTitle = ref('数据权限配置')
688
+ const selectedPermission = ref(1) // 默认选择全部数据
689
+
690
+ // 组织架构树相关
691
+ const orgTreeData = ref([])
692
+ const keyWord = ref('')
693
+ const filteredOrgTreeData = computed(() => {
694
+ if (!keyWord.value) return orgTreeData.value
695
+ return filterTreeNodes(orgTreeData.value, keyWord.value)
696
+ })
697
+ const orgTreeKeys = {
698
+ label: 'name',
699
+ value: 'id',
700
+ children: 'childs'
701
+ }
702
+ const expandedKeys = ref([])
703
+ const activeKey = ref(null)
704
+ const selectedOrgs = ref([])
705
+
706
+ // 过滤树节点函数
707
+ const filterTreeNodes = (nodes, keyword) => {
708
+ if (!nodes || !Array.isArray(nodes)) return []
709
+
710
+ const filtered = []
711
+
712
+ for (const node of nodes) {
713
+ // 创建节点副本,避免修改原始数据
714
+ const newNode = { ...node }
715
+
716
+ // 检查当前节点名称是否包含关键词
717
+ const matchesKeyword = node.name && node.name.toLowerCase().includes(keyword.toLowerCase())
718
+
719
+ // 递归过滤子节点
720
+ let filteredChildren = []
721
+ if (node.childs && node.childs.length > 0) {
722
+ filteredChildren = filterTreeNodes(node.childs, keyword)
723
+ }
724
+
725
+ // 如果当前节点匹配或者有匹配的子节点,则保留
726
+ if (matchesKeyword || filteredChildren.length > 0) {
727
+ if (filteredChildren.length > 0) {
728
+ newNode.childs = filteredChildren
729
+ }
730
+ filtered.push(newNode)
731
+ }
732
+ }
733
+
734
+ return filtered
735
+ }
736
+
737
+ // 处理组织节点选中
738
+ const handleOrgNodeSelect = (node, checked) => {
739
+ if (checked) {
740
+ // 检查是否已经存在
741
+ if (!selectedOrgs.value.find((org) => org.id === node.id)) {
742
+ selectedOrgs.value.push({
743
+ id: node.id,
744
+ name: node.name
745
+ })
746
+ }
747
+ } else {
748
+ // 从已选中中移除
749
+ removeSelectedOrg(node.id)
750
+ }
751
+ }
752
+
753
+ // 移除选中的组织
754
+ const removeSelectedOrg = (id) => {
755
+ const index = selectedOrgs.value.findIndex((org) => org.id === id)
756
+ if (index !== -1) {
757
+ selectedOrgs.value.splice(index, 1)
758
+ }
759
+ }
760
+
761
+ // 清空已选择的组织
762
+ const clearSelectedOrgs = () => {
763
+ selectedOrgs.value = []
764
+ }
765
+
766
+ // 检查节点是否已被选中
767
+ const isOrgNodeSelected = (id) => {
768
+ return selectedOrgs.value.some((org) => org.id === id)
769
+ }
770
+
771
+ // 切换组织节点选中状态
772
+ const toggleOrgNodeSelect = (node) => {
773
+ const isSelected = isOrgNodeSelected(node.id)
774
+ handleOrgNodeSelect(node, !isSelected)
775
+ }
776
+
777
+ // 处理组织节点激活
778
+ const handleOrgNodeActive = (value) => {
779
+ activeKey.value = value
780
+ }
781
+
782
+ // 处理组织节点展开
783
+ const handleOrgNodeExpand = (value) => {
784
+ expandedKeys.value = value
785
+ }
786
+
787
+ // 打开数据权限配置弹窗
788
+ const openPermissionDialog = (row) => {
789
+ currentRow.value = row
790
+ // 检查是否已有自定义配置
791
+ if (row.hasCustomConfig) {
792
+ // 已有自定义配置,使用自身配置
793
+ selectedPermission.value = row.dataPermission || 1
794
+ if (row.dataPermission === 6 && row.selectedOrgs) {
795
+ selectedOrgs.value = [...row.selectedOrgs]
796
+ } else {
797
+ selectedOrgs.value = []
798
+ }
799
+ } else {
800
+ // 没有自定义配置,继承全局配置
801
+ selectedPermission.value = globalPermission.value
802
+ if (globalPermission.value === 6) {
803
+ selectedOrgs.value = [...globalSelectedOrgs.value]
804
+ } else {
805
+ selectedOrgs.value = []
806
+ }
807
+ }
808
+ expandedKeys.value = ['1']
809
+ let title = '数据权限配置: '
810
+ if (row.module) title += row.module
811
+ if (row.subModule1 && row.subModule1 !== row.module) title += ' - ' + row.subModule1
812
+ if (row.subModule2 && row.subModule2 !== row.subModule1) title += ' - ' + row.subModule2
813
+ if (row.functions && row.functions.length === 1 && row.functions[0].name !== '全部')
814
+ title += ' - ' + row.functions[0].name
815
+ permissionDialogTitle.value = title
816
+ permissionDialogVisible.value = true
817
+ }
818
+
819
+ // 全局数据权限相关
820
+ const globalPermission = ref(1) // 默认全部数据
821
+ const globalSelectedOrgs = ref([])
822
+ const tempGlobalSelectedOrgs = ref([])
823
+ const globalOrgDialogVisible = ref(false)
824
+
825
+ // 打开全局组织选择器
826
+ const openGlobalOrgSelector = () => {
827
+ // 初始化临时选择的组织列表
828
+ tempGlobalSelectedOrgs.value = [...globalSelectedOrgs.value]
829
+
830
+ // 加载组织架构树数据
831
+ expandedKeys.value = ['1'] // 默认展开第一级
832
+
833
+ globalOrgDialogVisible.value = true
834
+ }
835
+
836
+ // 全局组织节点选择相关函数
837
+ const handleGlobalOrgNodeSelect = (node, checked) => {
838
+ if (checked) {
839
+ // 检查是否已经存在
840
+ if (!tempGlobalSelectedOrgs.value.find((org) => org.id === node.id)) {
841
+ tempGlobalSelectedOrgs.value.push({
842
+ id: node.id,
843
+ name: node.name
844
+ })
845
+ }
846
+ } else {
847
+ // 从已选中中移除
848
+ removeGlobalSelectedOrg(node.id)
849
+ }
850
+ }
851
+
852
+ // 移除选中的全局组织(对话框中使用)
853
+ const removeGlobalSelectedOrg = (id) => {
854
+ console.log('移除临时全局组织', id);
855
+ const index = tempGlobalSelectedOrgs.value.findIndex((org) => org.id === id);
856
+ if (index !== -1) {
857
+ tempGlobalSelectedOrgs.value.splice(index, 1);
858
+ console.log('移除成功,剩余组织数量:', tempGlobalSelectedOrgs.value.length);
859
+ } else {
860
+ console.log('未找到对应组织');
861
+ }
862
+ }
863
+
864
+ // 直接从全局组织中移除(主界面使用)
865
+ const removeGlobalOrg = (id) => {
866
+ const index = globalSelectedOrgs.value.findIndex((org) => org.id === id)
867
+ if (index !== -1) {
868
+ globalSelectedOrgs.value.splice(index, 1)
869
+ }
870
+ }
871
+
872
+ // 清空已选择的全局组织(用于弹窗中)
873
+ const clearGlobalSelectedOrgs = () => {
874
+ tempGlobalSelectedOrgs.value = []
875
+ }
876
+
877
+ // 清空主界面已选择的全局组织
878
+ const clearGlobalOrgs = () => {
879
+ globalSelectedOrgs.value = []
880
+ }
881
+
882
+ // 检查全局节点是否已被选中
883
+ const isGlobalOrgNodeSelected = (id) => {
884
+ return tempGlobalSelectedOrgs.value.some((org) => org.id === id)
885
+ }
886
+
887
+ // 切换全局组织节点选中状态
888
+ const toggleGlobalOrgNodeSelect = (node) => {
889
+ const isSelected = isGlobalOrgNodeSelected(node.id)
890
+ handleGlobalOrgNodeSelect(node, !isSelected)
891
+ }
892
+
893
+ // 确认全局组织选择
894
+ const handleGlobalOrgConfirm = () => {
895
+ // 保存选择结果
896
+ globalSelectedOrgs.value = [...tempGlobalSelectedOrgs.value]
897
+
898
+ // 应用到未配置自定义数据权限的行
899
+ applyGlobalOrgsToCustomRows()
900
+
901
+ globalOrgDialogVisible.value = false
902
+ }
903
+
904
+ // 取消全局组织选择
905
+ const handleGlobalOrgCancel = () => {
906
+ globalOrgDialogVisible.value = false
907
+ }
908
+
909
+ // 将全局组织应用到未配置自定义数据权限的行
910
+ const applyGlobalOrgsToCustomRows = () => {
911
+ // 遍历每一行数据
912
+ data.value.forEach((row) => {
913
+ // 只对没有自定义配置的行应用全局设置
914
+ if (!row.hasCustomConfig) {
915
+ // 应用全局权限设置
916
+ row.dataPermission = globalPermission.value
917
+
918
+ // 如果全局设置是自定义数据权限,则应用全局选择的组织
919
+ if (globalPermission.value === 6) {
920
+ row.selectedOrgs = [...globalSelectedOrgs.value]
921
+ } else {
922
+ row.selectedOrgs = []
923
+ }
924
+ }
925
+ })
926
+ }
927
+
928
+ // 确认权限配置(修改处理全局配置的情况)
929
+ const handlePermissionConfirm = () => {
930
+ if (currentRow.value) {
931
+ // 标记该行已有自定义配置
932
+ currentRow.value.hasCustomConfig = true;
933
+ currentRow.value.dataPermission = selectedPermission.value
934
+ if (selectedPermission.value === 6) {
935
+ currentRow.value.selectedOrgs = [...selectedOrgs.value]
936
+ } else {
937
+ currentRow.value.selectedOrgs = []
938
+ }
939
+ }
940
+ permissionDialogVisible.value = false
941
+ }
942
+
943
+ // 取消权限配置(修改处理全局配置的情况)
944
+ const handlePermissionCancel = () => {
945
+ // 只关闭弹窗,不做其他操作
946
+ permissionDialogVisible.value = false
947
+ }
948
+
949
+ // 获取匹配的节点总数
950
+ const getMatchedCount = (nodes) => {
951
+ if (!nodes || !Array.isArray(nodes)) return 0
952
+
953
+ let count = nodes.length
954
+
955
+ for (const node of nodes) {
956
+ if (node.childs && node.childs.length > 0) {
957
+ count += getMatchedCount(node.childs)
958
+ }
959
+ }
960
+
961
+ return count
962
+ }
963
+
964
+ // 新增的搜索函数
965
+ const onOrgSearch = () => {
966
+ dataService
967
+ .fetch(
968
+ {
969
+ postEnable: false,
970
+ keyWord: keyWord.value
971
+ },
972
+ {},
973
+ apis.orgTree
974
+ )
975
+ .then(async (res) => {
976
+ orgTreeData.value = res;
977
+ if (props.type === 'post') {
978
+ roleId.value = (await dataService.fetch({}, {}, apis.getRole.replace('{positionId}', props.id))).id
979
+ } else {
980
+ roleId.value = props.id;
981
+ }
982
+ if (!roleId.value) return;
983
+ let timer = setInterval(() => {
984
+ if (data.value.length > 0) {
985
+ clearInterval(timer)
986
+ getRoleConfig()
987
+ }
988
+ }, 500);
989
+ dataService.fetch({}, {}, apis.gerRoleInfo.replace('{id}', roleId.value)).then(res => {
990
+ globalPermission.value = res.dataScope;
991
+ // 使用递归查找函数获取完整组织信息
992
+ globalSelectedOrgs.value = [...res.deptIds].map(id => {
993
+ const org = findOrgInTree(orgTreeData.value, id);
994
+ return {
995
+ id: id,
996
+ name: org ? org.name : `组织${id}` // 如果找不到组织,提供一个默认名称
997
+ };
998
+ });
999
+ // 查询角色信息
1000
+ })
1001
+ })
1002
+ }
1003
+ onOrgSearch()
1004
+
1005
+ // 获取所有父级权限ID的辅助函数
1006
+ const getParentPermissionIds = (functionIds, row) => {
1007
+ const parentIds = new Set()
1008
+
1009
+ // 添加模块ID(如果存在)
1010
+ if (row.moduleId) {
1011
+ parentIds.add(row.moduleId)
1012
+ }
1013
+
1014
+ // 添加子模块1 ID(如果存在)
1015
+ if (row.subModule1Id) {
1016
+ parentIds.add(row.subModule1Id)
1017
+ }
1018
+
1019
+ // 添加子模块2 ID(如果存在)
1020
+ if (row.subModule2Id) {
1021
+ parentIds.add(row.subModule2Id)
1022
+ }
1023
+
1024
+ // 将Set转换为数组并去重
1025
+ return Array.from(parentIds)
1026
+ }
1027
+
1028
+ // 计算选中的数据权限结构
1029
+ const computedSelectedData = computed(() => {
1030
+ // 全局数据权限
1031
+ const dataScope = globalPermission.value
1032
+ const deptIds = globalSelectedOrgs.value.map((org) => org.id)
1033
+ // 功能明细
1034
+ const functionList = []
1035
+ data.value.forEach((row) => {
1036
+ // 只处理有选中功能的行
1037
+ const checkedFunctionIds = row.functions
1038
+ .filter((f) => f.checked)
1039
+ .map((f) => f.id)
1040
+ .filter(Boolean)
1041
+ if (checkedFunctionIds.length > 0) {
1042
+ // 获取父级权限ID
1043
+ const parentIds = getParentPermissionIds(checkedFunctionIds, row)
1044
+
1045
+ // 合并功能ID和父级权限ID,去重
1046
+ const allFunctionIds = [...new Set([...checkedFunctionIds, ...parentIds])]
1047
+
1048
+ functionList.push({
1049
+ functionIds: allFunctionIds,
1050
+ dataScope: row.dataPermission || undefined,
1051
+ deptIds:
1052
+ row.dataPermission === 6 && row.selectedOrgs && row.selectedOrgs.length > 0
1053
+ ? row.selectedOrgs.map((org) => org.id)
1054
+ : []
1055
+ })
1056
+ }
1057
+ })
1058
+ let role = {
1059
+ dataScope,
1060
+ deptIds,
1061
+ }
1062
+ if (roleId.value) {
1063
+ role.id = roleId.value
1064
+ } else {
1065
+ role.positionId = props.id
1066
+ }
1067
+ return {
1068
+ role,
1069
+ functionList
1070
+ }
1071
+ })
1072
+
1073
+ watch(globalPermission, (val) => {
1074
+ if (val !== 6) {
1075
+ globalSelectedOrgs.value = []
1076
+ }
1077
+ })
1078
+
1079
+ function handleSaveRolePermission() {
1080
+ // 这里可以调用API或打印computedSelectedData
1081
+ console.log('保存角色权限:', computedSelectedData.value)
1082
+ dataService.fetch(
1083
+ computedSelectedData.value,
1084
+ {},
1085
+ apis.saveRole
1086
+ ).then(res => {
1087
+ if (!res) {
1088
+ Message.error('保存失败')
1089
+ return
1090
+ }
1091
+ emits('success')
1092
+ })
1093
+ }
1094
+
1095
+ // 暴露表格引用和方法
1096
+ defineExpose({
1097
+ selData: computedSelectedData
1098
+ })
1099
+
1100
+ // 先添加一个从树形结构中递归查找组织的辅助函数
1101
+ const findOrgInTree = (tree, id) => {
1102
+ if (!tree || !Array.isArray(tree)) return null;
1103
+
1104
+ // 先在当前层级查找
1105
+ const found = tree.find(item => item.id === id);
1106
+ if (found) return found;
1107
+
1108
+ // 如果当前层级没找到,递归查找子节点
1109
+ for (const node of tree) {
1110
+ if (node.childs && node.childs.length > 0) {
1111
+ const foundInChild = findOrgInTree(node.childs, id);
1112
+ if (foundInChild) return foundInChild;
1113
+ }
1114
+ }
1115
+
1116
+ return null; // 未找到
1117
+ };
1118
+
1119
+ // 添加用于更新父级勾选状态的辅助函数
1120
+ const updateParentCheckStatus = (row) => {
1121
+ // 判断当前行的功能是否有被勾选
1122
+ const hasFunctionsChecked = row.functions.some(func => func.checked);
1123
+
1124
+ // 更新子模块2的勾选状态
1125
+ if (row.subModule2) {
1126
+ // 寻找同组的所有行
1127
+ const sameGroup = data.value.filter(item =>
1128
+ item.moduleId === row.moduleId &&
1129
+ item.subModule1Id === row.subModule1Id &&
1130
+ item.subModule2Id === row.subModule2Id
1131
+ );
1132
+
1133
+ // 判断同组的所有行是否有功能被勾选
1134
+ const hasRowsChecked = sameGroup.some(item =>
1135
+ item.functions.some(func => func.checked)
1136
+ );
1137
+
1138
+ // 更新子模块2的勾选状态
1139
+ sameGroup.forEach(item => {
1140
+ item.subModule2Checked = hasRowsChecked;
1141
+ });
1142
+ }
1143
+
1144
+ // 更新子模块1的勾选状态
1145
+ updateSubModule1Checked(row.moduleId, row.subModule1Id);
1146
+ }
1147
+ </script>
1148
+
1149
+ <style lang="less" scoped>
1150
+ .cell-with-checkbox {
1151
+ display: flex;
1152
+ align-items: flex-start;
1153
+
1154
+ .cell-content {
1155
+ margin-left: 8px;
1156
+ display: flex;
1157
+ flex-direction: column;
1158
+ }
1159
+
1160
+ .cell-text {
1161
+ line-height: 1.4;
1162
+ }
1163
+
1164
+ .cell-id {
1165
+ margin-top: 2px;
1166
+ color: #999;
1167
+ font-size: 12px;
1168
+ font-family: monospace;
1169
+ line-height: 1.2;
1170
+ }
1171
+
1172
+ .empty-cell {
1173
+ color: #ccc;
1174
+ font-style: italic;
1175
+ }
1176
+ }
1177
+
1178
+ .functions-container {
1179
+ display: flex;
1180
+ flex-wrap: wrap;
1181
+ gap: 12px;
1182
+
1183
+ .function-item {
1184
+ display: flex;
1185
+ align-items: flex-start;
1186
+ margin-right: 12px;
1187
+
1188
+ .cell-content {
1189
+ margin-left: 8px;
1190
+ display: flex;
1191
+ flex-direction: column;
1192
+ }
1193
+
1194
+ .cell-text {
1195
+ line-height: 1.4;
1196
+ }
1197
+
1198
+ .cell-id {
1199
+ margin-top: 2px;
1200
+ color: #999;
1201
+ font-size: 12px;
1202
+ font-family: monospace;
1203
+ line-height: 1.2;
1204
+ }
1205
+ }
1206
+ }
1207
+
1208
+ .operation-buttons {
1209
+ display: flex;
1210
+ justify-content: center;
1211
+ }
1212
+
1213
+ .permission-content {
1214
+ padding: 20px 0;
1215
+
1216
+ .permission-item {
1217
+ .permission-options {
1218
+ margin-bottom: 20px;
1219
+
1220
+ :deep(.t-radio-group) {
1221
+ display: flex;
1222
+ flex-wrap: wrap;
1223
+
1224
+ .t-radio-button {
1225
+ margin-bottom: 10px;
1226
+ margin-right: 0;
1227
+ }
1228
+ }
1229
+ }
1230
+ }
1231
+ }
1232
+
1233
+ .custom-permission {
1234
+ margin-top: 16px;
1235
+ }
1236
+
1237
+ .org-selector-dialog {
1238
+ padding: 0;
1239
+ }
1240
+
1241
+ // 组织选择器弹窗样式
1242
+ .org-dialog-container {
1243
+ display: flex;
1244
+ height: 400px;
1245
+ border: 1px solid #eaeaea;
1246
+ border-radius: 4px;
1247
+ overflow: hidden;
1248
+ }
1249
+
1250
+ .org-left-panel {
1251
+ flex: 1;
1252
+ display: flex;
1253
+ flex-direction: column;
1254
+ border-right: 1px solid #eaeaea;
1255
+ }
1256
+
1257
+ .org-panel-header {
1258
+ padding: 16px;
1259
+ font-weight: 500;
1260
+ color: #181818;
1261
+ font-size: 14px;
1262
+ border-bottom: 1px solid #eaeaea;
1263
+ background-color: #f5f5f5;
1264
+ display: flex;
1265
+ justify-content: space-between;
1266
+ align-items: center;
1267
+ }
1268
+
1269
+ .org-search-container {
1270
+ padding: 12px 16px;
1271
+ border-bottom: 1px solid #eaeaea;
1272
+
1273
+ :deep(.t-input) {
1274
+ border-radius: 4px;
1275
+
1276
+ .t-input__inner {
1277
+ height: 32px;
1278
+ }
1279
+
1280
+ .t-input__suffix-icon {
1281
+ cursor: pointer;
1282
+ color: #999;
1283
+
1284
+ &:hover {
1285
+ color: var(--td-brand-color);
1286
+ }
1287
+ }
1288
+ }
1289
+
1290
+ .search-result-info {
1291
+ font-size: 12px;
1292
+ color: #999;
1293
+ margin-top: 8px;
1294
+ }
1295
+ }
1296
+
1297
+ .org-tree-container {
1298
+ flex: 1;
1299
+ overflow: auto;
1300
+ padding: 16px;
1301
+ }
1302
+
1303
+ .org-right-panel {
1304
+ width: 300px;
1305
+ display: flex;
1306
+ flex-direction: column;
1307
+ }
1308
+
1309
+ .org-selected-container {
1310
+ flex: 1;
1311
+ overflow: auto;
1312
+ padding: 16px;
1313
+ background-color: #fcfcfc;
1314
+ }
1315
+
1316
+ .selected-orgs-tags {
1317
+ display: flex;
1318
+ flex-wrap: wrap;
1319
+ gap: 8px;
1320
+
1321
+ .org-tag {
1322
+ margin: 0;
1323
+ white-space: nowrap;
1324
+ transition: all 0.2s ease;
1325
+ border-radius: 4px;
1326
+ cursor: pointer;
1327
+
1328
+ &:hover {
1329
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1330
+ transform: translateY(-1px);
1331
+ }
1332
+ }
1333
+ }
1334
+
1335
+ .no-selected-tip {
1336
+ color: #999;
1337
+ text-align: center;
1338
+ padding: 60px 0;
1339
+ font-size: 14px;
1340
+ }
1341
+
1342
+ .detail-content {
1343
+ padding: 16px;
1344
+
1345
+ .detail-item {
1346
+ margin-bottom: 16px;
1347
+
1348
+ .item-label {
1349
+ font-weight: bold;
1350
+ margin-right: 8px;
1351
+ color: #666;
1352
+ }
1353
+
1354
+ .item-value {
1355
+ color: #333;
1356
+ }
1357
+
1358
+ .sub-functions {
1359
+ margin-top: 8px;
1360
+
1361
+ .sub-function-item {
1362
+ padding: 4px 0;
1363
+ border-bottom: 1px dashed #eee;
1364
+
1365
+ &:last-child {
1366
+ border-bottom: none;
1367
+ }
1368
+ }
1369
+ }
1370
+ }
1371
+ }
1372
+
1373
+ .tree-node-label {
1374
+ cursor: pointer;
1375
+ padding: 4px 0;
1376
+ user-select: none;
1377
+
1378
+ &:hover {
1379
+ color: var(--td-brand-color);
1380
+ }
1381
+
1382
+ &.selected {
1383
+ color: var(--td-brand-color);
1384
+ font-weight: 500;
1385
+ }
1386
+ }
1387
+
1388
+ // 数据权限配置面板样式
1389
+ .permission-config-panel {
1390
+ margin-bottom: 16px;
1391
+ border: 1px solid #e7e7e7;
1392
+ border-radius: 6px;
1393
+ overflow: hidden;
1394
+ background-color: #fff;
1395
+
1396
+ .panel-header {
1397
+ background-color: #f5f5f5;
1398
+ padding: 12px 16px;
1399
+ border-bottom: 1px solid #e7e7e7;
1400
+
1401
+ .panel-title {
1402
+ margin: 0;
1403
+ font-size: 16px;
1404
+ font-weight: bold;
1405
+ color: #333;
1406
+ }
1407
+ }
1408
+
1409
+ .panel-content {
1410
+ padding: 16px;
1411
+
1412
+ .permission-type-selector {
1413
+ display: flex;
1414
+ align-items: center;
1415
+ margin-bottom: 16px;
1416
+
1417
+ .permission-radio-group {
1418
+ display: flex;
1419
+ flex-wrap: wrap;
1420
+
1421
+ :deep(.t-radio-button) {
1422
+ margin-bottom: 10px;
1423
+ margin-right: 0;
1424
+ }
1425
+ }
1426
+ }
1427
+
1428
+ .org-select-row {
1429
+ display: flex;
1430
+ align-items: center;
1431
+ margin-bottom: 16px;
1432
+
1433
+ .selected-count {
1434
+ color: #666;
1435
+ margin-right: 12px;
1436
+ }
1437
+
1438
+ .org-select-btn {
1439
+ height: 32px;
1440
+ }
1441
+ }
1442
+
1443
+ .selected-orgs-panel {
1444
+ display: flex;
1445
+ flex-direction: column;
1446
+ border: 1px solid #eaeaea;
1447
+ border-radius: 4px;
1448
+ margin-top: 16px;
1449
+
1450
+ .selected-orgs-header {
1451
+ display: flex;
1452
+ justify-content: space-between;
1453
+ align-items: center;
1454
+ padding: 10px 16px;
1455
+ font-size: 14px;
1456
+ color: #181818;
1457
+ border-bottom: 1px solid #eaeaea;
1458
+ background-color: #f5f5f5;
1459
+ }
1460
+
1461
+ .selected-orgs-content {
1462
+ padding: 16px;
1463
+ min-height: 80px;
1464
+
1465
+ .no-org-selected {
1466
+ color: #999;
1467
+ text-align: center;
1468
+ padding: 20px 0;
1469
+ }
1470
+
1471
+ .selected-orgs-tags {
1472
+ display: flex;
1473
+ flex-wrap: wrap;
1474
+ gap: 8px;
1475
+
1476
+ .org-tag {
1477
+ margin: 0;
1478
+ overflow: hidden;
1479
+ text-overflow: ellipsis;
1480
+ white-space: nowrap;
1481
+ }
1482
+ }
1483
+ }
1484
+ }
1485
+ }
1486
+ }
1487
+
1488
+ .no-search-results {
1489
+ text-align: center;
1490
+ padding: 30px 0;
1491
+ color: #999;
1492
+ font-size: 14px;
1493
+ }
1494
+ </style>