@jzt-packages/components 1.0.0

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 (145) hide show
  1. package/package.json +68 -0
  2. package/src/JztBackTop/index.vue +255 -0
  3. package/src/JztButtonList/index.vue +88 -0
  4. package/src/JztChart/index.vue +95 -0
  5. package/src/JztCharts/index.vue +317 -0
  6. package/src/JztClassTabs/index.vue +156 -0
  7. package/src/JztDateSelect/dateSelect.vue +186 -0
  8. package/src/JztDateSelect/dateType.vue +54 -0
  9. package/src/JztDateSelect/index.ts +135 -0
  10. package/src/JztDateSelect/interface/index.ts +13 -0
  11. package/src/JztDialog/index.vue +249 -0
  12. package/src/JztEllipsisTooltip/index.vue +61 -0
  13. package/src/JztEmpty/index.vue +45 -0
  14. package/src/JztErrorPage/403.vue +30 -0
  15. package/src/JztErrorPage/404.vue +19 -0
  16. package/src/JztErrorPage/500.vue +18 -0
  17. package/src/JztErrorPage/assets/401.png +0 -0
  18. package/src/JztErrorPage/assets/403.png +0 -0
  19. package/src/JztErrorPage/assets/404.png +0 -0
  20. package/src/JztErrorPage/assets/500.png +0 -0
  21. package/src/JztErrorPage/index.scss +35 -0
  22. package/src/JztErrorPage/index.vue +35 -0
  23. package/src/JztFilePreview/components/pdfViewer.vue +221 -0
  24. package/src/JztFilePreview/hooks/useImageMethod.ts +256 -0
  25. package/src/JztFilePreview/index.scss +171 -0
  26. package/src/JztFilePreview/index.vue +68 -0
  27. package/src/JztFilePreview/interface/index.ts +18 -0
  28. package/src/JztFilePreview/previewFile.vue +371 -0
  29. package/src/JztFormGrid/README.md +520 -0
  30. package/src/JztFormGrid/components/formItem.vue +209 -0
  31. package/src/JztFormGrid/components/formItemValue.vue +384 -0
  32. package/src/JztFormGrid/components/showDetailForm.vue +172 -0
  33. package/src/JztFormGrid/index.scss +60 -0
  34. package/src/JztFormGrid/index.vue +513 -0
  35. package/src/JztFormGrid/interface/index.ts +106 -0
  36. package/src/JztGrid/components/GridItem.vue +68 -0
  37. package/src/JztGrid/index.vue +179 -0
  38. package/src/JztGrid/interface/index.ts +6 -0
  39. package/src/JztImportExcel/assets/delete.png +0 -0
  40. package/src/JztImportExcel/index.scss +46 -0
  41. package/src/JztImportExcel/index.vue +430 -0
  42. package/src/JztImportExcel/interface/index.ts +25 -0
  43. package/src/JztLabelTitle/index.vue +65 -0
  44. package/src/JztLeftRightMode/components/CollapseButton.vue +80 -0
  45. package/src/JztLeftRightMode/components/LeftCard.vue +203 -0
  46. package/src/JztLeftRightMode/components/LeftLayout.vue +173 -0
  47. package/src/JztLeftRightMode/components/RightHeader.vue +186 -0
  48. package/src/JztLeftRightMode/components/RightLayout.vue +235 -0
  49. package/src/JztLeftRightMode/components/RightTableHeader.vue +43 -0
  50. package/src/JztLeftRightMode/hooks/useCollapse.ts +17 -0
  51. package/src/JztLeftRightMode/hooks/useDefaultProps.ts +19 -0
  52. package/src/JztLeftRightMode/hooks/useLeftLayout.ts +201 -0
  53. package/src/JztLeftRightMode/hooks/useMode.ts +20 -0
  54. package/src/JztLeftRightMode/hooks/usePrevNext.ts +60 -0
  55. package/src/JztLeftRightMode/hooks/useRightLayout.ts +215 -0
  56. package/src/JztLeftRightMode/hooks/useSlots.ts +15 -0
  57. package/src/JztLeftRightMode/index.ts +3 -0
  58. package/src/JztLeftRightMode/index.vue +494 -0
  59. package/src/JztLeftRightMode/types/index.ts +457 -0
  60. package/src/JztLoading/fullScreen.ts +45 -0
  61. package/src/JztLoading/index.scss +67 -0
  62. package/src/JztLoading/index.vue +18 -0
  63. package/src/JztLogin/components/LoginFooter.vue +17 -0
  64. package/src/JztLogin/components/LoginForm.vue +99 -0
  65. package/src/JztLogin/hooks/useLogin.ts +186 -0
  66. package/src/JztLogin/index.scss +142 -0
  67. package/src/JztLogin/index.vue +31 -0
  68. package/src/JztLogin/interface/index.ts +47 -0
  69. package/src/JztNumericalRange/index.vue +81 -0
  70. package/src/JztPageCard/comm/datePicker.vue +151 -0
  71. package/src/JztPageCard/comm/details.vue +60 -0
  72. package/src/JztPageCard/comm/export.vue +24 -0
  73. package/src/JztPageCard/comm/tabs.vue +94 -0
  74. package/src/JztPageCard/comm/tooltip.vue +31 -0
  75. package/src/JztPageCard/index.vue +287 -0
  76. package/src/JztPagination/index.vue +70 -0
  77. package/src/JztProductInfo/components/imagePreview.vue +275 -0
  78. package/src/JztProductInfo/components/qxUnique.vue +101 -0
  79. package/src/JztProductInfo/components/records.vue +265 -0
  80. package/src/JztProductInfo/hooks/useParams.ts +143 -0
  81. package/src/JztProductInfo/hooks/useQxUnique.tsx +466 -0
  82. package/src/JztProductInfo/images/defaultProduct.png +0 -0
  83. package/src/JztProductInfo/index.ts +116 -0
  84. package/src/JztProductInfo/index.vue +108 -0
  85. package/src/JztProductInfo/interface/index.ts +15 -0
  86. package/src/JztQueryDetailTable/index.scss +100 -0
  87. package/src/JztQueryDetailTable/index.vue +400 -0
  88. package/src/JztQueryDetailTable/interface/index.ts +10 -0
  89. package/src/JztQueryTable/QueryTable /345/212/237/350/203/275.md" +1580 -0
  90. package/src/JztQueryTable/README.md +567 -0
  91. package/src/JztQueryTable/components/ColSetting.vue +67 -0
  92. package/src/JztQueryTable/components/ColumnsSetting.vue +404 -0
  93. package/src/JztQueryTable/components/ColumnsSetting1.vue +220 -0
  94. package/src/JztQueryTable/components/DeployToAccountLevelSetting.vue +351 -0
  95. package/src/JztQueryTable/components/Pagination.vue +54 -0
  96. package/src/JztQueryTable/components/TableColumn.vue +109 -0
  97. package/src/JztQueryTable/const.ts +1 -0
  98. package/src/JztQueryTable/hooks/useQueryTable.ts +194 -0
  99. package/src/JztQueryTable/hooks/useSelection.ts +47 -0
  100. package/src/JztQueryTable/hooks/useTableSetting.ts +197 -0
  101. package/src/JztQueryTable/hooks/useTemplate.ts +127 -0
  102. package/src/JztQueryTable/index.scss +91 -0
  103. package/src/JztQueryTable/index.vue +1445 -0
  104. package/src/JztQueryTable/interface/index.ts +185 -0
  105. package/src/JztRegionSelect/index.vue +134 -0
  106. package/src/JztSearchForm/components/SearchFormItem.vue +473 -0
  107. package/src/JztSearchForm/index.vue +530 -0
  108. package/src/JztSearchForm/interface/index.ts +100 -0
  109. package/src/JztSelectFilter/index.scss +63 -0
  110. package/src/JztSelectFilter/index.vue +110 -0
  111. package/src/JztSelectTable/index.vue +257 -0
  112. package/src/JztTable/index.scss +72 -0
  113. package/src/JztTable/index.vue +353 -0
  114. package/src/JztTable/interface/index.ts +1 -0
  115. package/src/JztTime/comm/agencySelect.vue +112 -0
  116. package/src/JztTime/comm/collapseRow.vue +132 -0
  117. package/src/JztTime/comm/dateSelect.vue +292 -0
  118. package/src/JztTime/comm/deptSelect.vue +193 -0
  119. package/src/JztTime/comm/typeSelect.vue +97 -0
  120. package/src/JztTime/index.ts +216 -0
  121. package/src/JztTime/index.vue +303 -0
  122. package/src/JztTime/interface/index.ts +23 -0
  123. package/src/JztTreeFilter/index.scss +44 -0
  124. package/src/JztTreeFilter/index.vue +177 -0
  125. package/src/JztUploadFile/interface/index.ts +21 -0
  126. package/src/JztUploadFile/multiple.scss +215 -0
  127. package/src/JztUploadFile/multiple.vue +318 -0
  128. package/src/JztUploadFile/single.scss +226 -0
  129. package/src/JztUploadFile/single.vue +274 -0
  130. package/src/JztUploadImg/Img.vue +294 -0
  131. package/src/JztUploadImg/Imgs.vue +411 -0
  132. package/src/JztUploadImg/index.scss +138 -0
  133. package/src/JztUploadImg/interface/index.ts +22 -0
  134. package/src/SelectIcon/index.scss +39 -0
  135. package/src/SelectIcon/index.vue +106 -0
  136. package/src/SvgIcon/index.vue +22 -0
  137. package/src/hooks/useAuthButtons.ts +58 -0
  138. package/src/hooks/useFormByUserType.ts +90 -0
  139. package/src/hooks/useTableEvents.ts +30 -0
  140. package/src/hooks/useUploadFileHook.ts +262 -0
  141. package/src/index.ts +91 -0
  142. package/src/typings/global.d.ts +101 -0
  143. package/src/utils/index.ts +107 -0
  144. package/src/utils/tree.ts +57 -0
  145. package/tsconfig.json +45 -0
@@ -0,0 +1,1445 @@
1
+ <!-- 📚📚📚 Pro-Table 文档: https://juejin.cn/post/7166068828202336263 -->
2
+
3
+ <template>
4
+ <div class="jzt-query-page">
5
+ <!-- 查询表单 -->
6
+ <JztSearchForm
7
+ ref="searchFormRef"
8
+ v-show="isShowSearch"
9
+ :search="_search"
10
+ @setFirstValue="setFirstValue"
11
+ :reset="_reset"
12
+ :customForm="customForm"
13
+ :allow-collapsed="allowCollapsed"
14
+ :collapsed="collapsed"
15
+ :query-items="searchColumns"
16
+ :search-param="searchParam"
17
+ :show-param="initParam"
18
+ :search-init-param="searchInitParam"
19
+ :search-col="searchCol"
20
+ :show-query-btn="showQueryBtn"
21
+ :show-reset-btn="showResetBtn"
22
+ :suffix="btnSuffix"
23
+ :is-card="searchCard"
24
+ :valid-query="validQuery"
25
+ >
26
+ <template #rightBtn>
27
+ <div v-if="!!slots.rightBtn" class="mr5 flx-left-center" style="height: 100%">
28
+ <slot name="rightBtn">
29
+ <!-- <el-button v-if="showQueryBtn" type="primary" @click="_search">搜索</el-button>
30
+ <el-button v-if="showQueryBtn" plain @click="reset">重置</el-button> -->
31
+ </slot>
32
+ </div>
33
+ </template>
34
+ <template #formTopSlot>
35
+ <slot name="formTopSlot"></slot>
36
+ <el-tabs
37
+ v-if="tabIsTop && tabsList && tabsList.length"
38
+ v-model="tabActiveValue"
39
+ class="table-tabs mb10"
40
+ style="margin-top: -8px"
41
+ :class="tabType ? tabsClass?.[tabType] : 'spd-line-card'"
42
+ :type="tabType"
43
+ @tab-change="handleTabChange"
44
+ >
45
+ <el-tab-pane
46
+ v-for="tab in tabsList"
47
+ :label="tab[tabsProps?.label || 'label']"
48
+ :name="tab[tabsProps?.value || 'value']"
49
+ ></el-tab-pane>
50
+ </el-tabs>
51
+ </template>
52
+ </JztSearchForm>
53
+ <slot>
54
+ <!-- 表格主体 -->
55
+ <div
56
+ v-loading="displayLoading"
57
+ :class="['table-main', isFullscreen ? 'fullscreen-page' : '', tableCard ? 'spd-card' : 'noCardGap']"
58
+ >
59
+ <slot name="tableTops" />
60
+ <slot name="queryTableMain">
61
+ <el-tabs
62
+ v-if="!tabIsTop && tabsList && tabsList.length"
63
+ v-model="tabActiveValue"
64
+ class="table-tabs"
65
+ :class="tabType ? tabsClass?.[tabType] : 'spd-line-card'"
66
+ :type="tabType"
67
+ @tab-change="handleTabChange"
68
+ >
69
+ <el-tab-pane
70
+ v-for="tab in tabsList"
71
+ :label="tab[tabsProps?.label || 'label']"
72
+ :name="tab[tabsProps?.value || 'value']"
73
+ >
74
+ <template #label>
75
+ <span class="custom-tabs-label flx-left-center">
76
+ <component v-if="tab?.leftRender" :is="tab?.leftRender" />
77
+ <span>{{ tab[tabsProps?.label || 'label'] }}</span>
78
+ <span v-if="!isEmpty(tab?.tabCount)">
79
+ ({{ tabsProps?.isFold && Number(tab?.tabCount) > 99 ? '99+' : tab.tabCount }})
80
+ </span>
81
+ <component v-if="tab?.rightRender" :is="tab?.rightRender" />
82
+ </span>
83
+ </template>
84
+ </el-tab-pane>
85
+ </el-tabs>
86
+ <!-- <slot name="btnTop" /> -->
87
+ <!-- 表格头部 操作按钮 -->
88
+ <div class="table-header flx-between-top" v-if="(topList && topList.length) || hasSlotContent || toolButton">
89
+ <slot name="headerTitle" />
90
+ <div class="header-button-lf">
91
+ <div class="flx-left-center flx-wrap p2">
92
+ <slot
93
+ name="toolBtnLeft"
94
+ :selected-list="selectedList"
95
+ :selected-list-ids="selectedListIds"
96
+ :is-selected="isSelected"
97
+ />
98
+ <JztButtonList
99
+ v-if="topList && topList.length"
100
+ :buttonList="topList"
101
+ @onClick="handleButtonClick"
102
+ ></JztButtonList>
103
+ <slot
104
+ name="toolBtnRight"
105
+ :selected-list="selectedList"
106
+ :selected-list-ids="selectedListIds"
107
+ :is-selected="isSelected"
108
+ />
109
+ </div>
110
+ </div>
111
+ <div v-if="toolButton" class="header-button-ri">
112
+ <slot
113
+ name="settingBtnLeft"
114
+ :selected-list="selectedList"
115
+ :selected-list-ids="selectedListIds"
116
+ :is-selected="isSelected"
117
+ />
118
+ <slot
119
+ name="toolButton"
120
+ :selected-list="selectedList"
121
+ :selected-list-ids="selectedListIds"
122
+ :is-selected="isSelected"
123
+ >
124
+ <slot
125
+ name="toolLeft"
126
+ :selected-list="selectedList"
127
+ :selected-list-ids="selectedListIds"
128
+ :is-selected="isSelected"
129
+ />
130
+ <el-button
131
+ v-if="showToolButton('refresh') && requestApi"
132
+ :icon="Refresh"
133
+ :icon-size="16"
134
+ title="刷新"
135
+ class="tool_btn grey-button"
136
+ plain
137
+ @click="getTableList"
138
+ />
139
+ <el-button
140
+ v-if="showToolButton('fullScreen')"
141
+ :title="isFullscreen ? '退出全屏' : '全屏'"
142
+ class="tool_btn grey-button"
143
+ plain
144
+ @click="isFullscreen = !isFullscreen"
145
+ >
146
+ <i :class="['iconfont', isFullscreen ? 'icon-shiyingchuangkou' : 'icon-manping']"></i>
147
+ </el-button>
148
+ <el-button
149
+ v-if="showToolButton('setting') && tableColumns.length"
150
+ title="列配置"
151
+ :icon="Setting"
152
+ class="tool_btn grey-button"
153
+ plain
154
+ @click.native.stop="changeDialog"
155
+ />
156
+ <!-- <ColumnsSetting
157
+ v-if="showToolButton('setting') && tableColumns.length"
158
+ v-model:columns="tableColumns"
159
+ :column-types="columnTypes"
160
+ v-model:loading="loading1"
161
+ @refreshSetting="getSettingConfig"
162
+ >
163
+ <template #reference>
164
+ <el-button title="列设置" :icon="Setting" class="tool_btn" />
165
+ </template>
166
+ </ColumnsSetting> -->
167
+ <!-- <el-button
168
+ v-if="showToolButton('search') && searchColumns?.length"
169
+ title="显示/隐藏查询"
170
+ class="tool_btn"
171
+ :icon="Search"
172
+ @click="isShowSearch = !isShowSearch"
173
+ /> -->
174
+ </slot>
175
+ </div>
176
+ </div>
177
+ <!-- 表格上方插槽 -->
178
+ <slot
179
+ name="tableHeader"
180
+ :selected-list="selectedList"
181
+ :selected-list-ids="selectedListIds"
182
+ :is-selected="isSelected"
183
+ />
184
+ <!-- <div style="padding: 0 10px; height: 100%"> -->
185
+ <!-- 表格主体 -->
186
+
187
+ <el-table
188
+ v-if="isShow"
189
+ ref="tableRef"
190
+ v-bind="$attrs"
191
+ :id="uuid"
192
+ :row-class-name="
193
+ ({ row, rowIndex }) => {
194
+ // 处理 rowClassName 为函数的情况
195
+ if (typeof rowClassName === 'function') {
196
+ return rowClassName({ row, rowIndex })
197
+ }
198
+ const isSelected = rowKey && radio && radio === row[rowKey]
199
+ // 处理 rowClassName 为字符串的情况
200
+ return isSelected ? rowClassName || 'highlight-current-row' : ''
201
+ }
202
+ "
203
+ :data="processTableData"
204
+ :default-expand-all="isExpandAll"
205
+ :border="border"
206
+ :row-key="rowKey"
207
+ :class="[
208
+ addTableNoEmpty && (!processTableData || !processTableData.length) ? 'noDataEmpty' : '',
209
+ addTableNoEmpty ? 'tableNoHeight' : ''
210
+ ]"
211
+ @cell-click="handleCellClick"
212
+ @selection-change="tableSelectionChange"
213
+ @header-dragend="headerDragend"
214
+ @row-click="handleRowClick"
215
+ >
216
+ <!-- 默认插槽 -->
217
+ <slot />
218
+
219
+ <template
220
+ v-for="item in typeof columnConfig?.layout === 'string'
221
+ ? columnConfig?.layout?.split?.(',')
222
+ : columnConfig?.layout"
223
+ :key="item"
224
+ >
225
+ <el-table-column
226
+ v-if="columnTypes.includes(item?.trim?.() as TypeProps)"
227
+ v-bind="{ type: item, ...(columnConfig?.[`${item}Props`] || {}) }"
228
+ :width="columnConfig?.[`${item}Props`]?.width ?? (['expand', 'sort'].includes(item) ? 100 : 80)"
229
+ :align="columnConfig?.[`${item}Props`]?.align ?? 'center'"
230
+ :reserve-selection="
231
+ columnConfig?.[`${item}Props`].reserveSelection ?? columnConfig?.[`${item}Props`]?.type == 'selection'
232
+ "
233
+ >
234
+ <template #default="scope">
235
+ <template v-if="item.includes('expand')">
236
+ <component
237
+ :is="columnConfig?.expandProps?.render"
238
+ v-bind="scope"
239
+ v-if="columnConfig?.expandProps?.render"
240
+ />
241
+ <slot v-else :name="columnConfig?.expandProps?.type" v-bind="scope" />
242
+ </template>
243
+ <el-radio v-if="item.includes('radio')" v-model="radio" :label="scope.row[rowKey]">
244
+ <i></i>
245
+ </el-radio>
246
+ <el-tag v-if="item.includes('sort')" class="move">
247
+ <el-icon><DCaret /></el-icon>
248
+ </el-tag>
249
+ </template>
250
+ </el-table-column>
251
+ </template>
252
+ <template v-for="(item, index) in selectColumns" :key="item">
253
+ <el-table-column
254
+ v-if="item.type && columnTypes.includes(item.type)"
255
+ v-bind="item"
256
+ :align="item.align ?? 'center'"
257
+ :reserve-selection="item.reserveSelection ?? item.type == 'selection'"
258
+ :selectable="item.type === 'selection' && item?.selectable ? item?.selectable : () => true"
259
+ >
260
+ <template #default="scope">
261
+ <!-- <div style="width: 100%; height: 100%"> -->
262
+ <template v-if="item.type == 'expand'">
263
+ <component :is="item.render" v-bind="scope" v-if="item.render" />
264
+ <slot v-else :name="item.type" v-bind="scope" />
265
+ </template>
266
+ <el-radio
267
+ v-if="item.type == 'radio'"
268
+ v-model="radio"
269
+ :name="'radio' + index"
270
+ :label="scope.row[rowKey]"
271
+ @change="setRadioItem(scope.row[rowKey], scope.row, item)"
272
+ >
273
+ <i></i>
274
+ </el-radio>
275
+ <el-tag v-if="item.type == 'sort'" type="primary" class="move" size="small">
276
+ <i class="iconfont icon-tuozhuai fontS-20"></i>
277
+ </el-tag>
278
+ <!-- </div> -->
279
+ </template>
280
+ </el-table-column>
281
+ </template>
282
+ <template v-for="item in tableColumns" :key="item">
283
+ <!-- other -->
284
+ <TableColumn v-if="item.prop !== 'operation' && item.isShow && !item.hidden" :column="item">
285
+ <template v-for="slot in Object.keys($slots)" #[slot]="scope">
286
+ <slot :name="slot" v-bind="scope" />
287
+ </template>
288
+ </TableColumn>
289
+ <!-- 自定义表格操作列 -->
290
+ </template>
291
+ <!-- 自定义表格操作列 -->
292
+ <template v-for="(item, index) in operationColumns">
293
+ <el-table-column v-if="item.isShow !== false" v-bind="item" :align="item.align ?? 'left'">
294
+ <template #default="{ row }">
295
+ <template v-for="(btnItem, index) in item.btnLList" :key="index">
296
+ <span v-if="hasPermissionFn(btnItem, row)">
297
+ <component v-if="btnItem.render" :is="btnItem.render" v-bind="row" style="margin: 0 12px" />
298
+ <span v-else-if="btnItem.slotName">
299
+ <slot :name="btnItem.slotName" v-bind="row" />
300
+ </span>
301
+ <el-button
302
+ v-else
303
+ @click="btnItem.fun && tableOperationFn(btnItem, row)"
304
+ :disabled="btnItem.disabledFn && btnItem.disabledFn(row)"
305
+ :link="btnItem.link === false ? false : true"
306
+ :type="btnItem.type || 'primary'"
307
+ :icon="btnItem.icon || ''"
308
+ v-bind="(btnItem as any).$attr"
309
+ style="margin: 0 6px"
310
+ >
311
+ <span>{{ btnItem.text }}</span>
312
+ </el-button>
313
+ </span>
314
+ </template>
315
+ </template>
316
+ </el-table-column>
317
+ </template>
318
+ <!-- 插入表格最后一行之后的插槽 -->
319
+ <template #append>
320
+ <slot name="append" />
321
+ </template>
322
+ <!-- 无数据 -->
323
+ <template #empty>
324
+ <div class="table-empty">
325
+ <slot name="empty">
326
+ <JztEmpty :emptyText="emptyText" />
327
+ </slot>
328
+ <slot name="empty-desc"></slot>
329
+ </div>
330
+ </template>
331
+ </el-table>
332
+ <!-- 新增按钮放在这个tableBottom插槽,class使用jzt-table-add-btn 就可以 -->
333
+ <slot name="tableBottom" />
334
+ <!-- 分页组件 -->
335
+ <slot name="pagination">
336
+ <Pagination
337
+ v-if="pagination"
338
+ :pageable="pageable"
339
+ :show-check-total="showCheckTotal"
340
+ :checkTotal="selectedListIds.length || 0"
341
+ :handle-size-change="handleSizeChange"
342
+ :handle-current-change="handleCurrentChange"
343
+ />
344
+ </slot>
345
+ </slot>
346
+ <slot name="pageFooter" :loading="displayLoading" :pageable="pageable" />
347
+ </div>
348
+ </slot>
349
+ </div>
350
+ <teleport to="body">
351
+ <ColumnsSetting
352
+ ref="columnsSettingRef"
353
+ v-if="showToolButton('setting') && tableColumns.length && gridParams1?.url"
354
+ v-model:columns="tableColumns"
355
+ :column-types="columnTypes"
356
+ :grid-params="gridParams1"
357
+ v-model:loading="loading1"
358
+ @refresh-column-table="refreshColumnTable"
359
+ @changeColumns="changeColumns"
360
+ ></ColumnsSetting>
361
+ </teleport>
362
+ </template>
363
+
364
+ <script setup lang="ts" name="ProTable">
365
+ import { Refresh, Setting } from '@element-plus/icons-vue'
366
+ import { isEmpty, removeEmpty, hasPermission } from '@jzt-spd/utils'
367
+ import { GUIDNoneValue } from '@jzt-spd/utils/consts'
368
+ import { ElTable, ElTableColumn } from 'element-plus'
369
+ import * as _ from 'lodash-es'
370
+ import { cloneDeep, debounce } from 'lodash-es'
371
+ import Sortable from 'sortablejs'
372
+ import { computed, onBeforeUnmount, onMounted, provide, ref, unref, useAttrs, useSlots, watch } from 'vue'
373
+ // import { hasPermission } from '../hooks/useAuthButtons'
374
+ // import { localGet, isEmpty } from '@jzt-spd/utils'
375
+ import { useFormSetByUserType } from '../hooks/useFormByUserType'
376
+ import { useTableEvents } from '../hooks/useTableEvents'
377
+ import JztButtonList from '../JztButtonList/index.vue'
378
+ import JztEmpty from '../JztEmpty/index.vue'
379
+ import { BreakPoint } from '../JztGrid/interface'
380
+ import JztSearchForm from '../JztSearchForm/index.vue'
381
+ import { generateUUID, handleProp } from '../utils'
382
+ import ColumnsSetting from './components/ColumnsSetting.vue'
383
+ import Pagination from './components/Pagination.vue'
384
+ import TableColumn from './components/TableColumn.vue'
385
+ import { useOperation, useTable } from './hooks/useQueryTable'
386
+ import { useSelection } from './hooks/useSelection'
387
+ import {
388
+ ColumnConfigProps,
389
+ ColumnProps,
390
+ GridParams,
391
+ TableConfigProps,
392
+ tabsListProps,
393
+ TabsProps,
394
+ TopButtonProps,
395
+ TypeProps
396
+ } from './interface'
397
+ // const tabActive = ref(0)
398
+ export interface ProTableProps {
399
+ tableLoading?: boolean // 表格加载中
400
+ tabActiveName?: string | number // tabs绑定值的key
401
+ tabActive?: string | number // tabs绑定值
402
+ tabsList?: tabsListProps[] //tablist list数据
403
+ tabsProps?: TabsProps // tablist 配置项
404
+ tableConfig: TableConfigProps // 列配置项 ==> 必传
405
+ columnConfig?: ColumnConfigProps // 列多选,显示序号,单选等配置项 非必传
406
+ // columns: ColumnProps[]; // 列配置项 ==> 必传
407
+ data?: any[] // 静态 table data 数据,若存在则不会使用 requestApi 返回的 data ==> 非必传
408
+ requestApi?: (params: any) => Promise<any> // 请求表格数据的 api ==> 非必传
409
+ requestAuto?: boolean // 是否自动执行请求 api ==> 非必传(默认为true)
410
+ requestError?: (params: any) => void // 表格 api 请求错误监听 ==> 非必传
411
+ dataCallback?: (data: any) => any // 返回数据的回调函数,可以对数据进行处理 ==> 非必传
412
+ title?: string // 表格标题 ==> 非必传
413
+ pagination?: boolean // 是否需要分页组件 ==> 非必传(默认为true)
414
+ initParam?: any // 初始化请求参数 ==> 非必传(默认为{})
415
+ border?: boolean // 是否带有纵向边框 ==> 非必传(默认为true)
416
+ toolButton?: ('refresh' | 'setting' | 'search' | 'fullScreen')[] | boolean // 是否显示表格功能按钮 ==> 非必传(默认为true)
417
+ rowKey?: string // 行数据的 Key,用来优化 Table 的渲染,当表格数据多选时,所指定的 id ==> 非必传(默认为 id)
418
+ searchCol?: number | Record<BreakPoint, number> // 表格搜索项 每列占比配置 ==> 非必传 { xs: 1, sm: 2, md: 3, lg: 4, xl: 5,xxl:6 }
419
+ searchFilter?: (data: any) => Record<string, any> // 查询时处理数据的函数 可对查询前的数据进行编辑后再查询 ==> 非必传
420
+ allowCollapsed?: boolean // 查询表单是否折叠
421
+ collapsed?: boolean // 查询表单默认是否折叠
422
+ tableLoaded?: (data: any) => void // 表格加载完成的回调函数 ==> 非必传
423
+ showQueryBtn?: boolean // 是否展示查询重构按钮
424
+ showResetBtn?: boolean // 是否展示重置按钮
425
+ customForm?: boolean // 是否是默认表单(可设置labelWidth、search.width),并且不会随着屏幕变化
426
+ btnSuffix?: boolean // 按钮是否总是在最后(不跟随)
427
+ gridParams?: GridParams // 列配置 gridId url toolButton 开启列配置必须传递
428
+ tabType?: '' | 'card' | 'border-card' // el-tabs 的type
429
+ tableCard?: boolean // 是需要card样式,某下弹框不需要的话 设置false
430
+ searchCard?: boolean // 是需要card样式,某下弹框不需要的话 设置false
431
+ addTableNoEmpty?: boolean // 添加表格时,是否需要置空图
432
+ showCheckTotal?: boolean // 是否显示表格合计
433
+ validQuery?: boolean // 是否校验查询条件
434
+ tabIsTop?: boolean // tab是否在顶部
435
+ rowClassName?: Function | string // 行样式ClassName
436
+ emptyText?: string // 置空图文案
437
+ searchClear?: boolean // 是否显示查询清空选中
438
+ minHeight?: string | boolean // 表格最小高度
439
+ tabChangeSearch?: boolean // tab change 自动请求
440
+ }
441
+
442
+ // 自定义 el-tabs 样式 类名
443
+ const tabsClass = {
444
+ card: 'spd-card-tabs',
445
+ 'border-card': 'spd-border-card-tabs'
446
+ }
447
+
448
+ // 接受父组件参数,配置默认值
449
+ const props = withDefaults(defineProps<ProTableProps>(), {
450
+ tableLoading: false, // 表格加载中
451
+ tabsProps: () => ({
452
+ label: 'label',
453
+ value: 'value',
454
+ isFold: false // 超过99 是否显示99+
455
+ }),
456
+ columns: () => [],
457
+ requestAuto: true,
458
+ pagination: true,
459
+ initParam: () => ({}),
460
+ border: true,
461
+ toolButton: true,
462
+ rowKey: 'id',
463
+ searchCol: () => ({ xs: 1, sm: 2, md: 3, lg: 4, xl: 5, xxl: 6 }),
464
+ allowCollapsed: true,
465
+ collapsed: true,
466
+ showQueryBtn: true,
467
+ showResetBtn: true,
468
+ customForm: false,
469
+ btnSuffix: false,
470
+ tabType: '',
471
+ tableCard: true,
472
+ searchCard: true,
473
+ addTableNoEmpty: false,
474
+ validQuery: false,
475
+ tabIsTop: false,
476
+ emptyText: '暂无内容',
477
+ searchClear: true,
478
+ minHeight: '170px',
479
+ tabChangeSearch: false
480
+ })
481
+ const { useButtonClickFn } = useTableEvents()
482
+ const slots = useSlots()
483
+ const hasSlotContent = computed(() => {
484
+ return !!(slots.toolBtnLeft || slots.toolBtnRight)
485
+ })
486
+ // const tabActiveValue = computed(() => {
487
+ // if (!isEmpty(props.tabActive)) {
488
+ // return props.tabActive
489
+ // } else if (!isEmpty(props.tabActiveName)) {
490
+ // return props.initParam[props.tabActiveName]
491
+ // }
492
+ // })
493
+ const gridParams1 = ref<GridParams>({
494
+ gridId: props.gridParams?.gridId || '',
495
+ url: props.gridParams?.url || ''
496
+ })
497
+ const tabActiveValue = computed({
498
+ get() {
499
+ if (!isEmpty(props.tabActive)) {
500
+ changeTabsSetting(props.tabActive)
501
+ return props.tabActive
502
+ } else if (props.tabActiveName) {
503
+ changeTabsSetting(props.initParam[props.tabActiveName])
504
+ return props.initParam[props.tabActiveName]
505
+ }
506
+ // 修复: 当两个条件都不满足时,返回空字符串避免 undefined
507
+ return ''
508
+ },
509
+ set(val: string) {
510
+ if (!isEmpty(props.tabActiveName) && props.tabActiveName) {
511
+ const params = {
512
+ ...props.initParam,
513
+ [props.tabActiveName]: val
514
+ }
515
+ emit('update:initParam', params)
516
+ changeInitParams(params)
517
+ }
518
+ if (!isEmpty(props.tabActive)) {
519
+ emit('update:tabActive', val)
520
+ }
521
+ if (!showToolButton('setting')) {
522
+ // 有列配置的时候,需要先请求数据 在获取表格数据,因为这里涉及到pageSize,所以需要等待
523
+ getTableList()
524
+ }
525
+ }
526
+ })
527
+ // const isShowSetting = computed(() => {
528
+ // return showToolButton('setting') ? 500 : 0
529
+ // })
530
+
531
+ // 表格全屏
532
+ const isFullscreen = ref(false)
533
+ const searchFormRef = ref()
534
+ // table 实例
535
+ const tableRef = ref<InstanceType<typeof ElTable>>()
536
+
537
+ // 生成组件唯一id
538
+ const uuid = ref('id-' + generateUUID())
539
+
540
+ // column 列类型
541
+ const columnTypes: TypeProps[] = ['selection', 'radio', 'index', 'expand', 'sort']
542
+
543
+ // 是否显示搜索模块
544
+ const isShowSearch = ref(true)
545
+
546
+ // 控制 ToolButton 显示
547
+ const showToolButton = (key: 'refresh' | 'setting' | 'search' | 'fullScreen') => {
548
+ return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton
549
+ }
550
+
551
+ // // 判断单选 多选类型
552
+ // const hasCheckType = (key: 'radio' | 'selection' | 'expand') => {
553
+ // if (!key) return false
554
+ // return selectColumns.value.some(item => item.type === key)
555
+ // }
556
+
557
+ // 是否需要防抖
558
+ const debounceTime = computed(() => (showToolButton('setting') && props.gridParams?.url ? 500 : 0))
559
+
560
+ const changeTabsSetting = debounce((tabActive?: string | number) => {
561
+ changeTable(props.tableConfig.columns)
562
+ if (!props?.tabsList || !props?.tabsList?.length) return
563
+ // 所有 tab 的 gridId 一致(或都没有)时,不切换 gridId,共享同一份列配置
564
+ if (isTabColl.value) return
565
+ const index = props.tabsList.findIndex(item => item.value === tabActive)
566
+ if (index !== -1) {
567
+ // 如果有则取 tabsList,无则不取(适用于所有的tab字段不一致,每个 tab 单独配置)
568
+ gridParams1.value.gridId = props.tabsList[index]?.gridId || gridParams1.value.gridId || ''
569
+ }
570
+ }, debounceTime.value)
571
+
572
+ // 单选值
573
+ const radio = ref('')
574
+ // 单选值- 选中row
575
+ const radioItem = ref()
576
+
577
+ // 设置单选值
578
+ const setRadioItem = (key: string, row: any, item?: ColumnProps) => {
579
+ tableRef.value?.setCurrentRow(row)
580
+ radio.value = key
581
+ radioItem.value = row
582
+ emit('radioChange', key, row)
583
+ }
584
+ // 设置默认选中行
585
+ const setExpandRow = debounce(() => {
586
+ if (!processTableData.value || !processTableData.value.length) {
587
+ expandedRows.value = []
588
+ return
589
+ }
590
+ if (isExpandAll.value) {
591
+ const rowKey = props.rowKey
592
+ expandedRows.value = processTableData.value.map(item => {
593
+ return item[rowKey]
594
+ })
595
+ } else {
596
+ expandedRows.value = []
597
+ }
598
+ }, 200)
599
+
600
+ // 当前展开的行(如果你想支持只展开一个)
601
+ const expandedRows = ref<any[]>([])
602
+ // 行点击事件
603
+ // const handleRowClick = (row, column, event) => {
604
+ // const rowKey = props.rowKey
605
+ // if (column.type === 'radio') {
606
+ // setRadioItem(row[rowKey], row)
607
+ // } else if (column.type === 'selection') {
608
+ // // 最后一个参数必须是 false
609
+ // tableRef.value!.toggleRowSelection(row, undefined, false)
610
+ // } else if (column.type === 'expand') {
611
+ // const isExpanded = expandedRows.value.includes(row[rowKey])
612
+ // if (isExpanded) {
613
+ // // 折叠
614
+ // tableRef.value!.toggleRowExpansion(row, false)
615
+ // expandedRows.value = expandedRows.value.filter(item => item !== row[rowKey])
616
+ // } else {
617
+ // // 展开
618
+ // tableRef.value!.toggleRowExpansion(row, true)
619
+ // expandedRows.value.push(row[rowKey])
620
+ // }
621
+ // }
622
+ // }
623
+ // 单元格点击事件
624
+ const handleCellClick = (row, column, cell) => {
625
+ emit('cellClick', row, column)
626
+ const rowKey = props.rowKey
627
+ if (column.type === 'radio') {
628
+ setRadioItem(row[rowKey], row)
629
+ } else if (column.type === 'selection') {
630
+ // 最后一个参数必须是 false
631
+ tableRef.value!.toggleRowSelection(row, undefined, false)
632
+ } else if (column.type === 'expand') {
633
+ const isExpanded = expandedRows.value.includes(row[rowKey])
634
+ if (isExpanded) {
635
+ // 折叠
636
+ tableRef.value!.toggleRowExpansion(row, false)
637
+ expandedRows.value = expandedRows.value.filter(item => item !== row[rowKey])
638
+ } else {
639
+ // 展开
640
+ tableRef.value!.toggleRowExpansion(row, true)
641
+ expandedRows.value.push(row[rowKey])
642
+ }
643
+ }
644
+ }
645
+ // 行点击事件
646
+ const handleRowClick = (row: any, column: any) => {
647
+ const rowKey = props.rowKey
648
+ setRadioItem(row[rowKey], row)
649
+ emit('rowClick', row, column)
650
+ }
651
+
652
+ // 效验查询条件必填
653
+ const validateSearchForm = async () => {
654
+ if (!props.validQuery || !searchFormRef.value) {
655
+ // 需要初始化数据
656
+ return true
657
+ } else {
658
+ const validate = await searchFormRef.value.validateForm()
659
+ return validate
660
+ }
661
+ }
662
+ // 所有 tab 的 gridId 是否一致(不存在或全部一致),一致时共享同一份列配置,切换 tab 不改变 gridId
663
+ // true: isTabColl.value说明所有tab 用同一个列配置
664
+ const isTabColl = computed(() => {
665
+ // 收集所有存在的 gridId(过滤掉 undefined / null / 空值)
666
+ const gridIds = props.tabsList?.map(item => item.gridId).filter(val => !isEmpty(val))
667
+ // 没有 gridId 字段,或所有 gridId 完全一致,都返回 true
668
+ if (gridIds?.length === 0) return true
669
+ return gridIds?.every(val => val === gridIds[0])
670
+ })
671
+ // tab切换
672
+ const handleTabChange = (val: string | number) => {
673
+ // isTabColl.value说明所有tab 用同一个列配置
674
+ if (isTabColl.value && props.tabChangeSearch) {
675
+ searchAndClear()
676
+ }
677
+ emit('tabChange', val)
678
+ }
679
+ // 表格多选 Hooks
680
+ const { selectionChange, selectedList, selectedListIds, isSelected, headerDragend } = useSelection(props.rowKey)
681
+ const loading = ref<boolean>(false)
682
+ const loading1 = ref<boolean>(false)
683
+ // 合并后的展示用 loading:开启即时、关闭延迟 200ms。
684
+ // 用于衔接「列配置 loading1」与「数据 loading」两个先后周期,避免出现 loading→结束→loading 的断层。
685
+ const displayLoading = ref(false)
686
+ let loadingOffTimer: ReturnType<typeof setTimeout> | null = null
687
+ watch(
688
+ () => loading.value || loading1.value || props.tableLoading,
689
+ val => {
690
+ if (val) {
691
+ // 任一 loading 开启:立即显示,并清掉待关闭的定时器
692
+ if (loadingOffTimer) {
693
+ clearTimeout(loadingOffTimer)
694
+ loadingOffTimer = null
695
+ }
696
+ displayLoading.value = true
697
+ } else {
698
+ // 全部关闭:延迟 200ms 再隐藏,若期间又有 loading 开启则会被上面的 clearTimeout 取消
699
+ loadingOffTimer = setTimeout(() => {
700
+ displayLoading.value = false
701
+ loadingOffTimer = null
702
+ }, 200)
703
+ }
704
+ },
705
+ { immediate: true }
706
+ )
707
+ // 组件卸载时清理待执行的关闭定时器,避免卸载后仍触发 ref 赋值
708
+ onBeforeUnmount(() => {
709
+ if (loadingOffTimer) {
710
+ clearTimeout(loadingOffTimer)
711
+ loadingOffTimer = null
712
+ }
713
+ })
714
+
715
+ // 表格 tableSelectionChange多选事件
716
+ const tableSelectionChange = e => {
717
+ selectionChange(e)
718
+ emit('selectionChange', {
719
+ isSelected: isSelected.value,
720
+ selectedList: selectedList.value,
721
+ selectedListIds: selectedListIds.value
722
+ })
723
+ }
724
+ const formatterForm = form => {
725
+ let searchForm = cloneDeep(form)
726
+ searchColumns.value.forEach(item => {
727
+ // isSetEmptyData:空值默认值&& 未选择或输入数据 -> 设置默认值进行查询
728
+ if (item?.search && item.search?.isSetEmptyData && isEmpty(searchForm[item.prop])) {
729
+ searchForm[item.prop] = item.search.emptyNullValue || GUIDNoneValue
730
+ }
731
+ // 如果是数组 切设置了切割字段 -> 切割数组到开始与结束字段 并删除原字段
732
+ if (item.search && item.search?.startField && item.search?.endField) {
733
+ const { startField, endField } = item.search
734
+ const timeList = searchForm[item.prop] || []
735
+ if (item?.search?.el !== 'JztNumericalRange' && item?.search?.el !== 'JztDateSelect') {
736
+ // 只有时间才需要删除字段
737
+ searchForm[startField] = timeList[0] ?? ''
738
+ searchForm[endField] = timeList[1] ?? ''
739
+ }
740
+ delete searchForm[item.prop]
741
+ }
742
+ })
743
+ const endFrom = removeEmpty(searchForm)
744
+ return endFrom
745
+ }
746
+
747
+ const { isExpandAll, isShow, toggleExpandAll } = useOperation()
748
+ // 表格操作 Hooks
749
+ const {
750
+ tableData,
751
+ pageable,
752
+ searchParam,
753
+ searchInitParam,
754
+ getTableList,
755
+ search,
756
+ reset,
757
+ handleSizeChange,
758
+ handleCurrentChange,
759
+ changeInitParams
760
+ } = useTable(
761
+ validateSearchForm,
762
+ props.requestApi,
763
+ props.initParam,
764
+ props.pagination,
765
+ props.dataCallback,
766
+ props.requestError,
767
+ props.searchFilter,
768
+ props.tableLoaded,
769
+ loading,
770
+ formatterForm
771
+ )
772
+ // 清空选中数据列表
773
+ const clearSelection = () => tableRef.value?.clearSelection()
774
+
775
+ // 是否可以发起请求(等待所有 isSetFirstValue 的异步 enum 加载完成)
776
+ const isReadyToRequest = ref(true)
777
+
778
+ // 监听 waitFirstValueCount 改变,为了等待所有isSetFirstValue=true的获取完成后再执行请求,需要携带参数
779
+ const setFirstValue = count => {
780
+ if (count == waitFirstValueCount.value) {
781
+ isReadyToRequest.value = true
782
+ props.requestAuto && _search()
783
+ }
784
+ }
785
+
786
+ // 初始化表格数据 && 拖拽排序
787
+ onMounted(async () => {
788
+ dragSort()
789
+ // 未设置过 isSetFirstValue=true,直接请求
790
+ // 判断列设置是因为,必须要判断pageSize
791
+ if (!waitFirstValueCount.value && !showToolButton('setting')) {
792
+ props.requestAuto && _search()
793
+ }
794
+ })
795
+
796
+ // 处理表格数据
797
+ // let processTableData = computed(() => {
798
+ // if (!props.data) return tableData.value
799
+ // if (!props.pagination) return props.data
800
+ // return props.data.slice(
801
+ // (pageable.value.pageIndex - 1) * pageable.value.pageSize,
802
+ // pageable.value.pageSize * pageable.value.pageIndex
803
+ // )
804
+ // })
805
+ // 处理表格数据
806
+ const processTableData = computed({
807
+ get() {
808
+ // radio.value = ''
809
+ // radioItem.value = {}
810
+ if (!props.data) return tableData.value
811
+ if (!props.pagination) return props.data
812
+ return props.data.slice(
813
+ (pageable.value.pageIndex - 1) * pageable.value.pageSize,
814
+ pageable.value.pageSize * pageable.value.pageIndex
815
+ )
816
+ },
817
+ set(val) {
818
+ if (!Array.isArray(val)) return
819
+ tableData.value = val
820
+ }
821
+ })
822
+ // 监听表格数据,处理默认展开行的数据
823
+ watch(
824
+ () => processTableData.value,
825
+ list => {
826
+ radio.value = ''
827
+ radioItem.value = {}
828
+ setExpandRow()
829
+ },
830
+ {
831
+ deep: true,
832
+ immediate: true
833
+ }
834
+ )
835
+
836
+ // 监听页面 initParam 改化,重新获取表格数据
837
+ // !删除该功能 在tab切换时已经处理掉接口了
838
+ // watch(
839
+ // () => props.initParam[props.tabActiveName],
840
+ // () => {
841
+ // if (props.initParam && !isEmpty(props.tabActiveName)) {
842
+ // getTableList
843
+ // }
844
+ // },
845
+ // { deep: true }
846
+ // )
847
+
848
+ const tableColumns = ref<ColumnProps[]>([])
849
+
850
+ // props.tableConfig.columns 动态更新后及时更新tableColumns
851
+ // watch(
852
+ // () => props.tableConfig.columns,
853
+ // () => {
854
+ // tableColumns.value = props.tableConfig.columns.filter(item => item.prop && !item.type)
855
+ // tableColumns.value = props.tableConfig.columns.filter(item => item.prop && !item.type)
856
+ // }
857
+ // )
858
+ // 列表操作列 'index', 'selection', 'radio', 'expand', 'sort'
859
+ const selectColumns = computed<ColumnProps[]>(() => {
860
+ const selectCol = props.tableConfig.columns.filter(item => {
861
+ if (item.type && columnTypes.includes(item.type)) {
862
+ return getColIsShow(item)
863
+ }
864
+ })
865
+ return selectCol || []
866
+ })
867
+ // 获取当前的列是否展示
868
+ const getColIsShow = item => {
869
+ let isShow = item.isShow
870
+ const type = typeof item.isShow
871
+ if (type === 'object') {
872
+ isShow = item.isShow?.value
873
+ } else if (type === 'function') {
874
+ isShow = item.isShow?.()
875
+ }
876
+ return isShow !== false
877
+ }
878
+ const operationColumns = computed<ColumnProps[]>(() => {
879
+ const operationCol = props.tableConfig.columns.filter(item => {
880
+ if (item.prop === 'operation') {
881
+ return getColIsShow(item)
882
+ }
883
+ })
884
+ return operationCol || []
885
+ })
886
+
887
+ watch(
888
+ () => props.tableConfig.columns,
889
+ columns => {
890
+ changeTable(columns)
891
+ },
892
+ {
893
+ deep: true
894
+ }
895
+ )
896
+ watch(
897
+ () => gridParams1.value.gridId,
898
+ gridId => {
899
+ setTimeout(() => {
900
+ columnsSettingRef.value?.changeTableColumns(props.tableConfig.columns, gridParams1.value)
901
+ }, 0)
902
+ },
903
+ {
904
+ deep: true,
905
+ immediate: true
906
+ }
907
+ )
908
+
909
+ const changeTable = debounce((columns: ColumnProps[]) => {
910
+ const settingColumns = columnsSettingRef.value?.getCurrentTableColumns() || []
911
+ if (settingColumns && settingColumns.length) {
912
+ buildTableColumns(settingColumns)
913
+ } else {
914
+ flatColumnsFunc(columns)
915
+ }
916
+ }, debounceTime.value)
917
+
918
+ // 接收 queryItems 并设置为响应式
919
+ const queryItems = computed(() => props.tableConfig.queryItems || [])
920
+ //reactive<QueryItemsProps[]>(props.tableConfig.queryItems || []);
921
+
922
+ // 接收 topList 并设置为响应式 TopButtonProps
923
+ const topList = computed<TopButtonProps[]>(() => props.tableConfig.topList || [])
924
+
925
+ // 定义 enumMap 存储 enum 值(避免异步请求无法格式化单元格内容 || 无法填充搜索下拉选择)
926
+ const enumMap = ref(new Map<string, { [key: string]: any }[]>())
927
+ const setEnumMap = async ({ prop, enum: enumValue }: ColumnProps) => {
928
+ if (!enumValue) return
929
+
930
+ // 如果当前 enumMap 存在相同的值 return
931
+ if (enumMap.value.has(prop!) && (typeof enumValue === 'function' || enumMap.value.get(prop!) === enumValue)) return
932
+
933
+ // 当前 enum 为静态数据,则直接存储到 enumMap
934
+ if (typeof enumValue !== 'function') return enumMap.value.set(prop!, unref(enumValue!))
935
+
936
+ // 为了防止接口执行慢,而存储慢,导致重复请求,所以预先存储为[],接口返回后再二次存储
937
+ // enumMap.value.set(prop!, [])
938
+
939
+ // // 当前 enum 为后台数据需要请求数据,则调用该请求接口,并存储到 enumMap
940
+ // const { data, result } = await enumValue()
941
+ // enumMap.value.set(prop!, data || result)
942
+ }
943
+ const originalColumn = ref<any[]>([])
944
+
945
+ // 处理数据,递归处理
946
+ const flatColumnsFunc = async (columns: ColumnProps[]) => {
947
+ tableColumns.value = await processColumns(columns)
948
+ originalColumn.value = _.cloneDeep(tableColumns.value)
949
+ }
950
+ // 初始化 处理数据
951
+ const processColumns = async (columns: ColumnProps[]): Promise<ColumnProps[]> => {
952
+ const result: ColumnProps[] = []
953
+ for (const col of columns) {
954
+ if (col.prop && !col.type && col.isShow !== false && col.prop !== 'operation') {
955
+ const newCol: ColumnProps = {
956
+ ...col,
957
+ isShow: col.isShow ?? true,
958
+ isSetting: unref(col.isSetting) ?? true,
959
+ isFilterEnum: col.isFilterEnum ?? true
960
+ }
961
+ if (col._children?.length) {
962
+ newCol._children = await processColumns(col._children)
963
+ }
964
+ await setEnumMap(newCol)
965
+ result.push(newCol)
966
+ }
967
+ }
968
+ return result
969
+ }
970
+ const buildTableColumns = (asyncColumns: ColumnProps[]) => {
971
+ const result: ColumnProps[] = []
972
+ for (const col of asyncColumns) {
973
+ if (!col.prop || col.type || col.isShow === false || col.prop === 'operation' || col.hidden) {
974
+ continue
975
+ } else {
976
+ result.push({
977
+ ...col,
978
+ isShow: unref(col.isShow) ?? true,
979
+ isSetting: unref(col.isSetting) ?? true,
980
+ isFilterEnum: col.isFilterEnum ?? true,
981
+ // hidden: col.hidden === true,
982
+ isSetShow: col.hidden !== false
983
+ })
984
+ }
985
+ }
986
+
987
+ tableColumns.value = result
988
+ originalColumn.value = _.cloneDeep(result)
989
+ }
990
+
991
+ flatColumnsFunc(props.tableConfig.columns)
992
+ // 注入 enumColumnMap
993
+ provide('enumColumnMap', enumMap)
994
+ // 获取类配置数据
995
+
996
+ const changeColumns = (columns: ColumnProps[], pageSize: number, id: string) => {
997
+ // 1️⃣ 构建 tableColumns(一次性)
998
+ const nextTableColumns = cloneDeep(columns)
999
+
1000
+ // 2️⃣ 构建 emit 所需数据
1001
+ const allColumns: any[] = []
1002
+ const checkColumns: any[] = []
1003
+ for (const item of columns) {
1004
+ if (!item.prop) continue
1005
+ const colInfo = {
1006
+ fieldName: item.prop,
1007
+ titleName: item.label
1008
+ }
1009
+ allColumns.push(colInfo)
1010
+ if (item.isShow && !item.hidden) {
1011
+ checkColumns.push(colInfo)
1012
+ }
1013
+ }
1014
+
1015
+ // 3️⃣ 一次性更新响应式状态
1016
+ tableColumns.value = nextTableColumns
1017
+ pageable.value.pageSize = pageSize
1018
+
1019
+ // 4️⃣ emit(数据已稳定)
1020
+ emit('getEndColumns', {
1021
+ allColumns,
1022
+ checkColumns,
1023
+ pageSize,
1024
+ id
1025
+ })
1026
+
1027
+ // 5️⃣ 最后决定是否刷新
1028
+ if (props.requestAuto && !waitFirstValueCount.value && isReadyToRequest.value) {
1029
+ handleSizeChange(pageSize)
1030
+ }
1031
+ }
1032
+
1033
+ // const changeColumns = (columns: ColumnProps[], pageSize: number, id: string) => {
1034
+ // // console.log('changeColumns', !waitFirstValueCount.value, !isAllMounted.value)
1035
+ // tableColumns.value = cloneDeep(columns)
1036
+ // if (props.requestAuto && !waitFirstValueCount.value && isAllMounted.value) {
1037
+ // // 刷新数据
1038
+ // handleSizeChange(pageSize)
1039
+ // }
1040
+ // const endCol = [] as any
1041
+ // const allCol = [] as any
1042
+
1043
+ // columns.forEach(item => {
1044
+ // allCol.push({
1045
+ // fieldName: item.prop,
1046
+ // titleName: item.label
1047
+ // })
1048
+ // if (item.isShow && !item.hidden) {
1049
+ // endCol.push({
1050
+ // fieldName: item.prop,
1051
+ // titleName: item.label
1052
+ // })
1053
+ // }
1054
+ // })
1055
+ // emit('getEndColumns', {
1056
+ // allColumns: allCol,
1057
+ // checkColumns: endCol,
1058
+ // pageSize: pageSize,
1059
+ // id: id
1060
+ // })
1061
+ // pageable.value.pageSize = pageSize
1062
+ // }
1063
+ // TODO 暂时不走配置
1064
+ // const getSettingConfig = () => {
1065
+ // getTableSetting(showToolButton('setting'), tableColumns.value, loading1).then(res => {
1066
+ // tableColumns.value = res
1067
+ // // tableColumns.value = tableColumns.value
1068
+ // })
1069
+ // }
1070
+ // getSettingConfig()
1071
+
1072
+ // 过滤需要搜索的配置项 && 排序
1073
+ const searchColumns = computed(() => {
1074
+ return queryItems.value
1075
+ ?.filter(item => item.search?.el || item.search?.render)
1076
+ .sort((a, b) => a.search!.order! - b.search!.order!)
1077
+ })
1078
+ const waitFirstValueCount = ref<number>(0) // 需要等待的次数
1079
+
1080
+ const initSearchParam = () => {
1081
+ // 设置 搜索表单默认排序 && 搜索表单项的默认值
1082
+ searchColumns.value?.forEach((column, index) => {
1083
+ // column.search!.order = column.search?.order ?? index + 2
1084
+ if (!column.search) return
1085
+ column.search.order ??= index + 2
1086
+ if (useFormSetByUserType(column?.isSetFirstValue, column.prop) && typeof column.enum === 'function') {
1087
+ // 设置isSetFirstValue && enum设置的是函数时,需要等待函数返回数据,才能设置默认值
1088
+ waitFirstValueCount.value++
1089
+ isReadyToRequest.value = false
1090
+ }
1091
+ const key = column.search?.key ?? handleProp(column.prop!)
1092
+ // 计算默认值(优先级明确)
1093
+ let defaultValue
1094
+ if (!isEmpty(column.search?.defaultValue)) {
1095
+ defaultValue = column.search?.defaultValue
1096
+ } else if (!isEmpty(props.initParam[column.prop])) {
1097
+ defaultValue = props.initParam[column.prop]
1098
+ }
1099
+ // 设置默认值
1100
+ if (!isEmpty(defaultValue)) {
1101
+ searchParam.value[key] = defaultValue
1102
+ searchInitParam.value[key] = defaultValue
1103
+ }
1104
+ })
1105
+ }
1106
+ initSearchParam()
1107
+
1108
+ const columnsSettingRef = ref()
1109
+ const changeDialog = event => {
1110
+ event.stopPropagation()
1111
+ columnsSettingRef.value?.changeDialog(gridParams1.value)
1112
+ }
1113
+ // 外部手动保存列配置
1114
+ const saveColumns = async () => {
1115
+ return new Promise(async (resolve, reject) => {
1116
+ const { id, columns } = await columnsSettingRef.value?._saveColumn(false)
1117
+ const endCol = [] as any
1118
+ columns.forEach(item => {
1119
+ if (item.isShow && !item.hidden) {
1120
+ endCol.push({
1121
+ fieldName: item.prop,
1122
+ titleName: item.label
1123
+ })
1124
+ }
1125
+ })
1126
+ resolve({
1127
+ columns: endCol,
1128
+ id
1129
+ })
1130
+ })
1131
+ }
1132
+
1133
+ // setTimeout(async () => {
1134
+ // const id = await saveColumns()
1135
+ // }, 2000)
1136
+ // 定义 emit 事件
1137
+ const emit = defineEmits<{
1138
+ search: [any]
1139
+ reset: []
1140
+ dragSort: [{ newIndex?: number; oldIndex?: number }]
1141
+ radioChange: [any, any]
1142
+ tabChange: [any]
1143
+ selectionChange: [
1144
+ {
1145
+ isSelected: boolean
1146
+ selectedList: any[]
1147
+ selectedListIds: string[]
1148
+ }
1149
+ ]
1150
+ 'update:initParam': [any]
1151
+ 'update:tabActive': [any]
1152
+ getEndColumns: [any]
1153
+ refreshColumnTable: [any]
1154
+ rowClick: any
1155
+ cellClick: any
1156
+ }>()
1157
+ // 格式化后的搜索参数
1158
+ const formattedSearchForm = ref({})
1159
+ // 搜索清空
1160
+ const _search = () => {
1161
+ search()
1162
+ if (props.searchClear) {
1163
+ clearSelection()
1164
+ setRadioItem('', {})
1165
+ selectionChange([])
1166
+ tableRef.value?.setCurrentRow({})
1167
+ }
1168
+
1169
+ // 处理格式化数据
1170
+ const searchForm = formatterForm({
1171
+ ...searchParam.value,
1172
+ ...props.initParam
1173
+ })
1174
+ formattedSearchForm.value = searchForm
1175
+ emit('search', searchForm)
1176
+ }
1177
+ // 重新搜索并清空表格的勾选
1178
+ const searchAndClear = () => {
1179
+ clearSelection()
1180
+ setRadioItem('', {})
1181
+ selectionChange([])
1182
+ _search()
1183
+ tableRef.value?.setCurrentRow({})
1184
+ }
1185
+
1186
+ const _reset = () => {
1187
+ reset()
1188
+ clearSelection()
1189
+ emit('reset')
1190
+ }
1191
+
1192
+ // 表格拖拽排序
1193
+ let sortableInstance: Sortable | null = null
1194
+ const dragSort = () => {
1195
+ const tbody = document.querySelector(`#${uuid.value} tbody`) as HTMLElement
1196
+ if (!tbody) return
1197
+ sortableInstance = Sortable.create(tbody, {
1198
+ handle: '.move',
1199
+ animation: 300,
1200
+ onEnd({ newIndex, oldIndex }) {
1201
+ // 修复: 边界检查,防止 undefined 或越界
1202
+ if (newIndex === undefined || oldIndex === undefined) return
1203
+ if (newIndex < 0 || oldIndex < 0) return
1204
+ if (newIndex >= processTableData.value.length || oldIndex >= processTableData.value.length) return
1205
+
1206
+ const [removedItem] = processTableData.value.splice(oldIndex, 1)
1207
+ processTableData.value.splice(newIndex, 0, removedItem)
1208
+ emit('dragSort', { newIndex, oldIndex })
1209
+ }
1210
+ })
1211
+ }
1212
+
1213
+ // 组件卸载时清理 Sortable 实例
1214
+ onBeforeUnmount(() => {
1215
+ if (sortableInstance) {
1216
+ sortableInstance.destroy()
1217
+ sortableInstance = null
1218
+ }
1219
+ })
1220
+
1221
+ // // 是否显示表格操作按钮
1222
+ const hasPermissionFn = (btnItem: TopButtonProps, row: any) => {
1223
+ // console.log(btnItem.text, btnItem.hideFun ? !btnItem.hideFun(row) : true, hasPermission(btnItem.hasPerm))
1224
+
1225
+ return (btnItem.hideFun ? !btnItem.hideFun(row) : true) && hasPermission(btnItem.hasPerm)
1226
+ }
1227
+ // 按钮点击
1228
+ const handleButtonClick = (fun: Function) => {
1229
+ const searchForm = formatterForm({
1230
+ ...searchParam.value,
1231
+ ...props.initParam
1232
+ })
1233
+ fun &&
1234
+ fun({
1235
+ radio: radio.value,
1236
+ radioItem: radioItem.value,
1237
+ isSelected: isSelected.value,
1238
+ selectedList: selectedList.value,
1239
+ selectedListIds: selectedListIds.value,
1240
+ searchParam: searchParam.value,
1241
+ searchForm // 经过格式化的数据 ,未删除searchParam, 兼容上面被使用的情况
1242
+ })
1243
+ }
1244
+ // 操作列点击操作
1245
+ const tableOperationFn = (item: any, row: any) => {
1246
+ useButtonClickFn(item, row)
1247
+ // let secondConfirm = ''
1248
+ // if (item.secondConfirm) {
1249
+ // secondConfirm = item.secondConfirm
1250
+ // } else if (item.id === 'delete') {
1251
+ // secondConfirm = `确定${item.text || '删除'}该条数据吗?`
1252
+ // }
1253
+ // if (secondConfirm) {
1254
+ // ElMessageBox.confirm(secondConfirm, '警告', {
1255
+ // confirmButtonText: '确定',
1256
+ // cancelButtonText: '取消',
1257
+ // type: 'warning'
1258
+ // })
1259
+ // .then(() => {
1260
+ // item.fun(row)
1261
+ // })
1262
+ // .catch(error => {
1263
+ // })
1264
+ // } else {
1265
+ // item.fun(row)
1266
+ // }
1267
+ }
1268
+
1269
+ /*
1270
+ 动态更改表格数据,只需要是内部请求时才需要调用
1271
+ 外部使用方法 传入一个回调 处理数组后返回最终结果即可
1272
+ 外部使用方式:
1273
+ jztQueryTableRef?.value?.changeTableData(data => {
1274
+ return newData
1275
+ })
1276
+ */
1277
+ const changeTableData = (dataCallback: any) => {
1278
+ const endData = dataCallback(processTableData.value)
1279
+ processTableData.value = endData || []
1280
+ }
1281
+ // 获取表格的选中值
1282
+ const getTableCheckedValue = () => {
1283
+ return {
1284
+ radio: radio.value,
1285
+ radioItem: radioItem.value,
1286
+ isSelected: isSelected.value,
1287
+ selectedList: selectedList.value,
1288
+ selectedListIds: selectedListIds.value
1289
+ }
1290
+ }
1291
+ //列配置 确定 重置 刷新列表
1292
+ const refreshColumnTable = (type: string) => {
1293
+ emit('refreshColumnTable', type)
1294
+ }
1295
+
1296
+ // 暴露给父组件的参数和方法 (外部需要什么,都可以从这里暴露出去)
1297
+ defineExpose({
1298
+ element: tableRef,
1299
+ tableData: processTableData,
1300
+ // radio, // radio 如果有人后面用到这个,联调自测的时候请改用 getTableCheckedValue去获取(谢谢)
1301
+ // radioItem,
1302
+ pageable,
1303
+ searchParam,
1304
+ searchInitParam,
1305
+ formattedSearchForm, // 格式化后的搜索参数
1306
+ isSelected,
1307
+ selectedList,
1308
+ selectedListIds,
1309
+
1310
+ // 下面为 function
1311
+ getTableList, // 如果纯查询,在当前页面查询的话,就掉用这个接口
1312
+ search, // 查询,会重置页码
1313
+ reset,
1314
+ handleSizeChange,
1315
+ handleCurrentChange,
1316
+ clearSelection,
1317
+ enumMap,
1318
+ toggleExpandAll,
1319
+ changeTableData,
1320
+ setRadioItem,
1321
+ searchAndClear, // 查询并清空表格单选与多选数据 会触发emit
1322
+ getTableCheckedValue,
1323
+ saveColumns
1324
+ })
1325
+
1326
+ // 获取 attrs
1327
+ const attrs = useAttrs()
1328
+ // const maxHeight = computed(() => {
1329
+ // const maxHeight = props.maxHeight //attrs['max-height'] || attrs['maxHeight']
1330
+ // if (!maxHeight) return '100%'
1331
+ // if (typeof maxHeight === 'number') {
1332
+ // return maxHeight + 'px'
1333
+ // } else {
1334
+ // return maxHeight
1335
+ // }
1336
+ // })
1337
+
1338
+ // 表格固定高度
1339
+ const height = computed(() => {
1340
+ if (attrs['height']) {
1341
+ if (typeof attrs['height'] === 'number') {
1342
+ return attrs['height'] + 'px'
1343
+ } else {
1344
+ return attrs['height']
1345
+ }
1346
+ }
1347
+ return '100%'
1348
+ })
1349
+ // 页面固定高度
1350
+ const pageHeight = computed(() => {
1351
+ const height = attrs['page-height'] || attrs['pageHeight']
1352
+ if (!height) return '100%'
1353
+ if (typeof height === 'number') {
1354
+ return height + 'px'
1355
+ } else {
1356
+ return height
1357
+ }
1358
+ })
1359
+ // 表格最小高度
1360
+ const minHeight = computed(() => {
1361
+ const minHeight = props.minHeight // attrs['min-height'] || attrs['minHeight']
1362
+ if (minHeight === false) {
1363
+ return '100%'
1364
+ } else if (typeof minHeight === 'string') {
1365
+ return minHeight || '170px'
1366
+ } else {
1367
+ return '170px'
1368
+ }
1369
+ })
1370
+ </script>
1371
+ <style lang="scss" scoped>
1372
+ @use './index';
1373
+
1374
+ .jzt-query-page {
1375
+ display: flex;
1376
+ flex-direction: column;
1377
+ justify-content: flex-start;
1378
+ height: v-bind(pageHeight);
1379
+ :deep(.el-tabs .el-tabs__header.is-top) {
1380
+ margin-bottom: 2px !important;
1381
+ }
1382
+ :deep(.jzt-table-add-btn) {
1383
+ height: 42px;
1384
+ padding: 8px;
1385
+ font-size: 14px;
1386
+ border-left: 1px solid var(--el-border-color);
1387
+ border-right: 1px solid var(--el-border-color);
1388
+ border-bottom: 1px solid var(--el-border-color);
1389
+ border-bottom-left-radius: 2px;
1390
+ border-bottom-right-radius: 2px;
1391
+ }
1392
+ }
1393
+ :deep(.header-button-lf) {
1394
+ .el-button {
1395
+ margin: 4px 8px 4px 0;
1396
+ }
1397
+ }
1398
+ :deep(.header-button-ri) {
1399
+ .el-button {
1400
+ margin: 4px 4px 4px 4px;
1401
+ }
1402
+ }
1403
+ .table-main {
1404
+ flex: 1;
1405
+ overflow: hidden;
1406
+ :deep(.el-scrollbar__wrap) {
1407
+ // height: 0;
1408
+ min-height: 0;
1409
+ }
1410
+ :deep(.el-table .el-scrollbar) {
1411
+ min-height: v-bind(minHeight);
1412
+ height: v-bind(height);
1413
+ }
1414
+ }
1415
+ .header-button-ri {
1416
+ flex-wrap: nowrap !important;
1417
+ }
1418
+ .noCardGap {
1419
+ padding: 0;
1420
+ }
1421
+ .el-table {
1422
+ flex: 1;
1423
+ // margin-top: 2px;
1424
+ .el-scrollbar {
1425
+ min-height: 150px;
1426
+ }
1427
+ }
1428
+
1429
+ :deep(.el-table) {
1430
+ .highlight-current-row {
1431
+ background-color: var(--el-color-primary-light-9) !important;
1432
+ }
1433
+ }
1434
+
1435
+ .el-table.noDataEmpty {
1436
+ :deep(.el-scrollbar) {
1437
+ display: none;
1438
+ }
1439
+ }
1440
+ .el-table.tableNoHeight {
1441
+ :deep(.el-scrollbar) {
1442
+ min-height: 0;
1443
+ }
1444
+ }
1445
+ </style>