af-mobile-client-vue3 1.3.35 → 1.3.37

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 (281) hide show
  1. package/.cursorrules +60 -60
  2. package/.editorconfig +9 -9
  3. package/.env +10 -10
  4. package/.env.development +1 -1
  5. package/.env.production +1 -1
  6. package/.node-version +1 -1
  7. package/.vscode/extensions.json +12 -12
  8. package/.vscode/settings.json +68 -68
  9. package/CLAUDE.md +218 -218
  10. package/README.md +182 -182
  11. package/af-example-mobile-vue-web.iml +9 -9
  12. package/build/vite/index.ts +98 -98
  13. package/build/vite/optimize.ts +34 -34
  14. package/build/vite/vconsole.ts +47 -47
  15. package/commitlint.config.ts +32 -32
  16. package/compress.js +36 -36
  17. package/eslint.config.ts +31 -31
  18. package/index.html +23 -23
  19. package/mock/data.ts +20 -20
  20. package/mock/index.ts +7 -7
  21. package/mock/modules/prose.mock.ts +13 -13
  22. package/mock/modules/user.mock.ts +95 -95
  23. package/mock/util.ts +19 -19
  24. package/netlify.toml +12 -12
  25. package/package.json +114 -114
  26. package/postcss.config.ts +27 -27
  27. package/public/favicon.svg +4 -4
  28. package/public/safari-pinned-tab.svg +4 -4
  29. package/scripts/verifyCommit.js +19 -19
  30. package/src/App.vue +79 -79
  31. package/src/api/auth/index.ts +77 -77
  32. package/src/api/auth/types.ts +200 -200
  33. package/src/api/mock/index.ts +30 -30
  34. package/src/api/user/index.ts +40 -40
  35. package/src/assets/img/user/login/background-shadow-1.svg +20 -20
  36. package/src/assets/img/user/login/logo-background.svg +20 -20
  37. package/src/bootstrap.ts +26 -26
  38. package/src/components/core/BeautifulLoading/index.vue +52 -52
  39. package/src/components/core/ImageUploader/index.vue +251 -251
  40. package/src/components/core/NavBar/index.vue +53 -53
  41. package/src/components/core/Tabbar/index.vue +32 -32
  42. package/src/components/core/Uploader/index.vue +124 -124
  43. package/src/components/core/XGridDropOption/index.vue +154 -154
  44. package/src/components/core/XMultiSelect/index.vue +183 -183
  45. package/src/components/core/XSelect/index.vue +149 -149
  46. package/src/components/data/CardContainer/CardContainer.vue +118 -118
  47. package/src/components/data/CardContainer/CardHeader.vue +99 -99
  48. package/src/components/data/InfoDisplay/index.vue +132 -132
  49. package/src/components/data/UserDetail/api.ts +24 -24
  50. package/src/components/data/UserDetail/index.vue +620 -620
  51. package/src/components/data/UserDetail/recordEntries.ts +159 -159
  52. package/src/components/data/UserDetail/types.ts +26 -26
  53. package/src/components/data/XBadge/index.vue +82 -82
  54. package/src/components/data/XCellDetail/index.vue +105 -105
  55. package/src/components/data/XCellList/XCellList.md +432 -432
  56. package/src/components/data/XCellList/index.vue +1436 -1436
  57. package/src/components/data/XCellListFilter/QrScanner/index.vue +207 -207
  58. package/src/components/data/XCellListFilter/QrScanner/startScanAnimation.ts +53 -53
  59. package/src/components/data/XCellListFilter/VpnRecognition/index.vue +119 -119
  60. package/src/components/data/XCellListFilter/index.vue +705 -705
  61. package/src/components/data/XForm/index.vue +659 -659
  62. package/src/components/data/XFormGroup/doc/DeviceForm.vue +122 -122
  63. package/src/components/data/XFormGroup/doc/FormGroupDemo.vue +56 -56
  64. package/src/components/data/XFormGroup/doc/README.md +286 -286
  65. package/src/components/data/XFormGroup/doc/UserForm.vue +102 -102
  66. package/src/components/data/XFormGroup/index.vue +240 -240
  67. package/src/components/data/XFormItem/index.vue +1311 -1311
  68. package/src/components/data/XOlMap/README.md +227 -227
  69. package/src/components/data/XOlMap/XLocationPicker/index.vue +226 -226
  70. package/src/components/data/XOlMap/index.vue +1490 -1490
  71. package/src/components/data/XOlMap/types.ts +149 -149
  72. package/src/components/data/XOlMap/utils/wgs84ToGcj02.ts +171 -171
  73. package/src/components/data/XReportForm/DateTimeSecondsPicker.vue +208 -208
  74. package/src/components/data/XReportForm/XReportFormJsonRender.vue +220 -220
  75. package/src/components/data/XReportForm/index.vue +1393 -1393
  76. package/src/components/data/XReportGrid/XAddReport/XAddReport.vue +198 -198
  77. package/src/components/data/XReportGrid/XAddReport/index.js +3 -3
  78. package/src/components/data/XReportGrid/XAddReport/index.md +53 -53
  79. package/src/components/data/XReportGrid/XAddReport/index.ts +10 -10
  80. package/src/components/data/XReportGrid/XReport.vue +960 -960
  81. package/src/components/data/XReportGrid/XReportDemo.vue +33 -33
  82. package/src/components/data/XReportGrid/XReportDesign.vue +597 -597
  83. package/src/components/data/XReportGrid/XReportDrawer/XReportDrawer.vue +148 -148
  84. package/src/components/data/XReportGrid/XReportDrawer/index.js +3 -3
  85. package/src/components/data/XReportGrid/XReportDrawer/index.ts +10 -10
  86. package/src/components/data/XReportGrid/XReportJsonRender.vue +399 -399
  87. package/src/components/data/XReportGrid/XReportTrGroup.vue +592 -592
  88. package/src/components/data/XReportGrid/index.md +46 -46
  89. package/src/components/data/XReportGrid/print.js +184 -184
  90. package/src/components/data/XSignature/index.vue +284 -284
  91. package/src/components/data/XTag/index.vue +10 -10
  92. package/src/components/layout/NormalDataLayout/index.vue +69 -69
  93. package/src/components/layout/TabBarLayout/index.vue +40 -40
  94. package/src/composables/dark.ts +5 -5
  95. package/src/config/routes.ts +9 -9
  96. package/src/constants/index.ts +2 -2
  97. package/src/enums/requestEnum.ts +25 -25
  98. package/src/expression/ExpressionRunner.ts +28 -28
  99. package/src/expression/TestExpression.ts +510 -510
  100. package/src/expression/core/Delegate.ts +116 -116
  101. package/src/expression/core/Expression.ts +1359 -1359
  102. package/src/expression/core/Program.ts +985 -985
  103. package/src/expression/core/Token.ts +29 -29
  104. package/src/expression/enums/ExpressionType.ts +81 -81
  105. package/src/expression/enums/TokenType.ts +11 -11
  106. package/src/expression/exception/BreakWayException.ts +2 -2
  107. package/src/expression/exception/ContinueWayException.ts +2 -2
  108. package/src/expression/exception/ExpressionException.ts +29 -29
  109. package/src/expression/exception/ReturnWayException.ts +14 -14
  110. package/src/expression/exception/ServiceException.ts +22 -22
  111. package/src/expression/instances/JSONArray.ts +52 -52
  112. package/src/expression/instances/JSONObject.ts +118 -118
  113. package/src/expression/instances/LogicConsole.ts +31 -31
  114. package/src/font-style/font.css +4 -4
  115. package/src/hooks/useBoolean.ts +26 -26
  116. package/src/hooks/useCommon.ts +9 -9
  117. package/src/hooks/useLoading.ts +16 -16
  118. package/src/hooks/useLogin.ts +97 -97
  119. package/src/icons/svg/check-in.svg +32 -32
  120. package/src/icons/svg/dark.svg +4 -4
  121. package/src/icons/svg/github.svg +4 -4
  122. package/src/icons/svg/light.svg +4 -4
  123. package/src/icons/svg/link.svg +4 -4
  124. package/src/icons/svgo.yml +22 -22
  125. package/src/layout/GridView/index.vue +16 -16
  126. package/src/layout/PageLayout.vue +9 -9
  127. package/src/layout/SingleLayout.vue +9 -9
  128. package/src/locales/en-US.json +128 -128
  129. package/src/locales/zh-CN.json +128 -128
  130. package/src/logic/LogicRunner.ts +67 -67
  131. package/src/logic/TestLogic.ts +13 -13
  132. package/src/logic/plugins/common/DateTools.ts +35 -35
  133. package/src/logic/plugins/common/VueTools.ts +30 -30
  134. package/src/logic/plugins/index.ts +7 -7
  135. package/src/main.ts +44 -44
  136. package/src/plugins/AppData.ts +38 -38
  137. package/src/plugins/GetLoginInfoService.ts +10 -10
  138. package/src/plugins/collectIcons.ts +10 -10
  139. package/src/plugins/index.ts +11 -11
  140. package/src/router/README.md +8 -8
  141. package/src/router/external-routes.ts +60 -60
  142. package/src/router/guards.ts +131 -131
  143. package/src/router/index.ts +35 -35
  144. package/src/router/invoiceRoutes.ts +33 -33
  145. package/src/router/routes.ts +421 -421
  146. package/src/services/api/Login.ts +6 -6
  147. package/src/services/api/common.ts +109 -109
  148. package/src/services/api/index.ts +7 -7
  149. package/src/services/api/manage.ts +8 -8
  150. package/src/services/api/search.ts +16 -16
  151. package/src/services/api/user.ts +17 -17
  152. package/src/services/restTools.ts +56 -56
  153. package/src/services/v3Api.ts +147 -147
  154. package/src/stores/index.ts +13 -13
  155. package/src/stores/modules/counter.ts +19 -19
  156. package/src/stores/modules/homeApp.ts +55 -55
  157. package/src/stores/modules/routeCache.ts +22 -22
  158. package/src/stores/modules/setting.ts +87 -87
  159. package/src/stores/modules/user.ts +326 -326
  160. package/src/stores/mutation-type.ts +12 -12
  161. package/src/styles/app.less +36 -36
  162. package/src/styles/login.less +109 -109
  163. package/src/styles/var.less +25 -25
  164. package/src/types/auth.ts +89 -89
  165. package/src/types/env.d.ts +16 -16
  166. package/src/types/platform.ts +194 -194
  167. package/src/types/settings.ts +1 -1
  168. package/src/types/vue-router.d.ts +13 -13
  169. package/src/utils/Storage.ts +124 -124
  170. package/src/utils/authority-utils.ts +84 -84
  171. package/src/utils/common.ts +41 -41
  172. package/src/utils/crypto.ts +39 -39
  173. package/src/utils/dataUtil.ts +42 -42
  174. package/src/utils/dictUtil.ts +52 -52
  175. package/src/utils/http/index.ts +201 -201
  176. package/src/utils/i18n.ts +72 -72
  177. package/src/utils/indexedDB.ts +195 -195
  178. package/src/utils/inline-px-to-vw.ts +28 -28
  179. package/src/utils/mobileUtil.ts +33 -33
  180. package/src/utils/platform-auth.ts +135 -135
  181. package/src/utils/progress.ts +19 -19
  182. package/src/utils/queryFormDefaultRangePicker.ts +57 -57
  183. package/src/utils/routerUtil.ts +271 -271
  184. package/src/utils/runEvalFunction.ts +13 -13
  185. package/src/utils/secureStorage.ts +70 -70
  186. package/src/utils/set-page-title.ts +5 -5
  187. package/src/utils/validate.ts +6 -6
  188. package/src/views/chat/index.vue +153 -153
  189. package/src/views/common/Forbidden.vue +97 -97
  190. package/src/views/common/LoadError.vue +63 -63
  191. package/src/views/common/NotFound.vue +67 -67
  192. package/src/views/component/EvaluateRecordView/index.vue +40 -40
  193. package/src/views/component/IconifyView/index.vue +504 -504
  194. package/src/views/component/UserDetailView/UserDetailPage.vue +77 -77
  195. package/src/views/component/UserDetailView/index.vue +234 -234
  196. package/src/views/component/XCellDetailView/index.vue +217 -217
  197. package/src/views/component/XCellListView/index.vue +108 -108
  198. package/src/views/component/XFormAppraiseView/index.vue +174 -174
  199. package/src/views/component/XFormGroupView/index.vue +78 -78
  200. package/src/views/component/XFormView/index.vue +27 -27
  201. package/src/views/component/XOlMapView/index.vue +434 -434
  202. package/src/views/component/XOlMapView/testData.ts +64 -64
  203. package/src/views/component/XReportFormIframeView/index.vue +47 -47
  204. package/src/views/component/XReportFormView/index.vue +13 -13
  205. package/src/views/component/XReportGridView/index.vue +17 -17
  206. package/src/views/component/XRequestView/index.vue +234 -234
  207. package/src/views/component/XSignatureView/index.vue +50 -50
  208. package/src/views/component/index.vue +181 -181
  209. package/src/views/component/menu.vue +117 -117
  210. package/src/views/component/notice.vue +46 -46
  211. package/src/views/component/topNav.vue +36 -36
  212. package/src/views/external/index.vue +152 -152
  213. package/src/views/invoiceShow/index.vue +61 -61
  214. package/src/views/loading/AuthLoading.vue +345 -345
  215. package/src/views/user/login/ForgetPasswordForm.vue +94 -94
  216. package/src/views/user/login/LoginForm.vue +350 -350
  217. package/src/views/user/login/LoginTitle.vue +76 -76
  218. package/src/views/user/login/LoginWave.vue +109 -109
  219. package/src/views/user/login/index.vue +22 -22
  220. package/src/views/user/my/comm/ModifyPassword.vue +346 -346
  221. package/src/views/user/my/index.vue +507 -507
  222. package/src/views/user/register/index.vue +952 -952
  223. package/src/views/userRecords/AbnormalAlarmRecords.vue +21 -21
  224. package/src/views/userRecords/CardReplacementRecords.vue +21 -21
  225. package/src/views/userRecords/ChangeRecords.vue +19 -19
  226. package/src/views/userRecords/CommandViewRecords.vue +20 -20
  227. package/src/views/userRecords/GasCompensationRecords.vue +20 -20
  228. package/src/views/userRecords/InstrumentCollectionRecords.vue +21 -21
  229. package/src/views/userRecords/MeterRecords.vue +20 -20
  230. package/src/views/userRecords/OperateRecords.vue +51 -51
  231. package/src/views/userRecords/OtherChargeRecords.vue +19 -19
  232. package/src/views/userRecords/PaymentRecords.vue +28 -28
  233. package/src/views/userRecords/PriceAdjustmentRecords.vue +19 -19
  234. package/src/views/userRecords/ReplacementRecords.vue +19 -19
  235. package/src/views/userRecords/SafetyRecords.vue +19 -19
  236. package/src/views/userRecords/TransactionRecords.vue +21 -21
  237. package/src/views/userRecords/TransferRecords.vue +19 -19
  238. package/src/views/userRecords/operateRecordDetail/index.vue +316 -316
  239. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AddUserDetail.vue +124 -124
  240. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AdvanceDeliveryDetail.vue +88 -88
  241. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AutoAccountsCancelDetail.vue +205 -205
  242. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AutoAccountsDetail.vue +192 -192
  243. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BankDkDetail.vue +192 -192
  244. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BankPayDetail.vue +192 -192
  245. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BlacklistDetail.vue +153 -153
  246. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CancellationDetail.vue +101 -101
  247. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardMeterCenterCancelDetail.vue +127 -127
  248. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardMeterCenterDetail.vue +153 -153
  249. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardOverUserDetail.vue +153 -153
  250. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ChangeMeterCancelDetail.vue +166 -166
  251. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ChangeMeterDetail.vue +205 -205
  252. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/DisableManageDetail.vue +127 -127
  253. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/EnableManageDetail.vue +114 -114
  254. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/FaZheChangeDetail.vue +124 -124
  255. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/FeeDeductionDetail.vue +153 -153
  256. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/GasPriceChangeDetail.vue +126 -126
  257. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/InputtorChangeDetail.vue +126 -126
  258. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotMeterCenterCancelDetail.vue +114 -114
  259. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotMeterCenterDetail.vue +127 -127
  260. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotOpenDetail.vue +88 -88
  261. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineCardDetail.vue +101 -101
  262. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineMeterCenterCancelDetail.vue +218 -218
  263. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineMeterCenterDetail.vue +153 -153
  264. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OffGasAddGasDetail.vue +140 -140
  265. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OtherChargeCancelDetail.vue +127 -127
  266. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OtherChargeDetail.vue +114 -114
  267. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OverUserChangeDetail.vue +127 -127
  268. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReBillDetail.vue +127 -127
  269. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/RefundDetail.vue +114 -114
  270. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReplaceCardManageCancelDetail.vue +127 -127
  271. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReplaceCardManageDetail.vue +114 -114
  272. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/SaleCardGasDetail.vue +140 -140
  273. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/TransferManageCancelDetail.vue +152 -152
  274. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/TransferManageDetail.vue +178 -178
  275. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/UserChangeDetail.vue +123 -123
  276. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/WechatPayDetail.vue +192 -192
  277. package/src/views/userRecords/types.ts +66 -66
  278. package/tsconfig.json +39 -39
  279. package/uno.config.ts +82 -82
  280. package/vite.config.ts +114 -114
  281. package/.claude/settings.local.json +0 -14
@@ -1,1436 +1,1436 @@
1
- <script setup lang="ts">
2
- import XBadge from '@af-mobile-client-vue3/components/data/XBadge/index.vue'
3
- import XCellListFilter from '@af-mobile-client-vue3/components/data/XCellListFilter/index.vue'
4
- import { getConfigByName, query } from '@af-mobile-client-vue3/services/api/common'
5
- import useUserStore from '@af-mobile-client-vue3/stores/modules/user'
6
- import { getRangeByType } from '@af-mobile-client-vue3/utils/queryFormDefaultRangePicker'
7
- import { executeStrFunctionByContext } from '@af-mobile-client-vue3/utils/runEvalFunction'
8
- import LoadError from '@af-mobile-client-vue3/views/common/LoadError.vue'
9
- import {
10
- showConfirmDialog,
11
- showToast,
12
- ActionSheet as VanActionSheet,
13
- BackTop as VanBackTop,
14
- Button as VanButton,
15
- Checkbox as VanCheckbox,
16
- Col as VanCol,
17
- Icon as VanIcon,
18
- List as VanList,
19
- Popover as VanPopover,
20
- PullRefresh as VanPullRefresh,
21
- Row as VanRow,
22
- Search as VanSearch,
23
- Space as VanSpace,
24
- Tag as VanTag,
25
- } from 'vant'
26
- import { computed, defineEmits, defineProps, getCurrentInstance, onBeforeMount, ref, useSlots, watch } from 'vue'
27
- import { useRouter } from 'vue-router'
28
-
29
- const {
30
- configName = '',
31
- fixQueryForm = null,
32
- idKey = 'o_id',
33
- serviceName,
34
- scanOptions,
35
- customAdd = false,
36
- customEdit = false,
37
- customDelete = false,
38
- hideAllActions = false,
39
- // 多选相关配置
40
- enableMultiSelect = false, // 是否启用多选功能
41
- multiSelectActions = [], // 多选操作按钮配置
42
- } = defineProps<{
43
- configName?: string
44
- fixQueryForm?: object
45
- idKey?: string
46
- serviceName?: string
47
- scanOptions?: {
48
- show?: boolean // 是否显示扫码按钮
49
- type?: string | string[] // 显示类型:可以是单个类型或类型数组 'nfc' / ['scan', 'nfc']
50
- defaultMode?: string // 默认模式
51
- }
52
- // 是否自定义新增、编辑、删除按钮
53
- customAdd?: boolean
54
- customEdit?: boolean
55
- customDelete?: boolean
56
- // 是否隐藏所有操作按钮
57
- hideAllActions?: boolean
58
- // 多选相关配置
59
- enableMultiSelect?: boolean // 是否启用多选功能
60
- multiSelectActions?: Array<{
61
- name: string
62
- key?: string
63
- color?: string
64
- icon?: string
65
- }> // 多选操作按钮配置
66
- }>()
67
-
68
- const emit = defineEmits<{
69
- (e: 'toDetail', item: any): void
70
- (e: 'update', item: any): void
71
- (e: 'delete', item: any): void
72
- (e: 'add'): void
73
- (e: string, item: any): void
74
- (e: 'updateCondition', params: any): void
75
- // 多选相关事件
76
- (e: 'multiSelectAction', action: string, selectedItems: any[], selectedItemsArray: any[]): void
77
- (e: 'selectionChange', selectedItems: any[]): void
78
- }>()
79
-
80
- const userState = useUserStore().getLogin()
81
-
82
- const router = useRouter()
83
-
84
- const orderVal = ref(undefined)
85
-
86
- const sortordVal = ref(undefined)
87
-
88
- // 配置内容
89
- const configContent = ref({})
90
-
91
- // 表单加载后是否立即执行查询
92
- const isInitQuery = ref(false)
93
-
94
- // 主列
95
- const mainColumns = ref([])
96
-
97
- // 副标题列
98
- const subTitleColumns = ref([])
99
-
100
- // 详情列
101
- const detailColumns = ref([])
102
-
103
- // 标签列
104
- const tagList = ref([])
105
-
106
- // 标题按钮列
107
- const btnList = ref([])
108
-
109
- // 底部列
110
- const footColumns = ref([])
111
-
112
- // 所有的复杂操作列
113
- const allActions = ref([])
114
-
115
- // 复杂操作列前三项
116
- const mainActions = ref([])
117
-
118
- // 复杂操作列其余项
119
- const otherActions = ref([])
120
-
121
- // 数据集
122
- const list = ref([])
123
-
124
- // 每个 Popover 的显示状态
125
- const showPopover = ref<boolean[]>(list.value.map(() => false))
126
-
127
- // 排序集
128
- const orderList = ref([])
129
-
130
- // 表单查询数组
131
- const formQueryList = ref([])
132
-
133
- // 总数据
134
- const totalCount = ref(0)
135
-
136
- // 当前页数
137
- const pageNo = ref(1)
138
-
139
- // 每页数量
140
- const pageSize = 20
141
-
142
- const searchValue = ref('')
143
-
144
- // 数据加载状态
145
- const loading = ref(false)
146
- const refreshing = ref(false)
147
- const finished = ref(false)
148
- const isError = ref(false)
149
- const finishedText = ref('加载完成')
150
- // 避免查询多次
151
- const isLastPage = ref(false)
152
-
153
- // 多选相关状态
154
- const isMultiSelectMode = ref(false) // 是否处于多选模式
155
- const selectedItems = ref<Set<string>>(new Set()) // 选中的项目ID集合
156
- const longPressTimer = ref<number | null>(null) // 长按定时器
157
- const longPressDelay = 500 // 长按延迟时间(毫秒)
158
-
159
- // 条件参数(查询框)
160
- const conditionParams = ref(undefined)
161
-
162
- // 主要按钮的状态
163
- const buttonState = ref(undefined)
164
-
165
- // 新增or查询的表单配置
166
- const groupFormItems = ref({})
167
- const title = ref('')
168
-
169
- // 按钮权限
170
- const buttonPermissions = ref([])
171
-
172
- // 默认参数
173
- const defaultParams = {}
174
-
175
- // ActionSheet相关状态
176
- const showActionSheet = ref(false)
177
- const actionSheetActions = ref([])
178
-
179
- const slots = useSlots()
180
-
181
- // 当前组件实例(不推荐使用,可能会在后续的版本更迭中调整,暂时用来绑定函数的上下文)
182
- const currInst = getCurrentInstance()
183
-
184
- // 列表底部的文字显示
185
- function finishedBottomText() {
186
- if (!hideAllActions && buttonState.value?.add && buttonState.value.add === true && (filterButtonPermissions('add').state === false || ((filterButtonPermissions('add').state === true && userState.f.resources.f_role_name.includes((filterButtonPermissions('add').roleStr))))))
187
- return '已加载全部内容,如需新增请点击右上角的 + 号'
188
- else
189
- return '已加载全部内容'
190
- }
191
-
192
- onBeforeMount(() => {
193
- initComponent()
194
- })
195
-
196
- // 组件初始化
197
- function initComponent() {
198
- getConfigByName(configName, (result) => {
199
- groupFormItems.value = result
200
- title.value = result?.title
201
- const isQuery = result.createdQuery
202
- for (let i = 0; i < result.columnJson.length; i++) {
203
- const item = result.columnJson[i]
204
- item.span = item.flexSpan
205
- if (item.slotType === 'badge')
206
- item.dictName = item.slotKeyMap
207
-
208
- if (item.mobileColumnType === 'mobile_header_column') {
209
- mainColumns.value.push(item)
210
- }
211
- else if (item.mobileColumnType === 'mobile_subtitle_column') {
212
- subTitleColumns.value.push(item)
213
- }
214
- else if (item.mobileColumnType === 'mobile_details_column') {
215
- detailColumns.value.push(item)
216
- }
217
- else if (item.mobileColumnType === 'mobile_footer_column') {
218
- footColumns.value.push(item)
219
- }
220
- else if (item.mobileColumnType === 'mobile_tag_column') {
221
- tagList.value.push(item)
222
- }
223
- else if (item.slotType === 'action' && item.actionArr) {
224
- for (let j = 0; j < item.actionArr.length; j++) {
225
- allActions.value.push({
226
- text: item.actionArr[j].text,
227
- func: item.actionArr[j].func,
228
- customFunction: item.actionArr[j].customFunction,
229
- })
230
- }
231
- }
232
-
233
- if (item.btnIcon)
234
- btnList.value.push(item)
235
-
236
- if (result.showSortIcon && item.sortable) {
237
- orderList.value.push({
238
- title: item.title,
239
- value: item.dataIndex,
240
- })
241
- }
242
- }
243
- configContent.value = result
244
- // 扁平化 type=group 的 groupItems,保持顺序
245
- const flatFormJson = []
246
- result.formJson.forEach((item) => {
247
- if (item.type === 'group' && Array.isArray(item.groupItems)) {
248
- item.groupItems.forEach((groupItem) => {
249
- if (!groupItem.isOnlyAddOrEdit) {
250
- flatFormJson.push(groupItem)
251
- }
252
- })
253
- }
254
- else {
255
- if (!item.isOnlyAddOrEdit && item.type !== 'group') {
256
- flatFormJson.push(item)
257
- }
258
- }
259
- })
260
- formQueryList.value = flatFormJson
261
- console.log('formQueryList', formQueryList.value)
262
- if (result.buttonState) {
263
- buttonState.value = result.buttonState
264
- buttonPermissions.value = result.buttonPermissions
265
- if (buttonState.value.edit && buttonState.value.edit === true && (filterButtonPermissions('edit').state === false || ((filterButtonPermissions('edit').state === true && userState.f.resources.f_role_name.includes((filterButtonPermissions('edit').roleStr))))))
266
- allActions.value.push({ text: '修改', func: 'update' })
267
- if (buttonState.value.delete && buttonState.value.delete === true && (filterButtonPermissions('delete').state === false || ((filterButtonPermissions('delete').state === true && userState.f.resources.f_role_name.includes((filterButtonPermissions('delete').roleStr))))))
268
- allActions.value.push({ text: '删除', func: 'delete' })
269
- }
270
- splitArrayAt(allActions.value, 3)
271
-
272
- // 初始化条件参数(从表单默认值中获取)
273
- initConditionParams(result.formJson, isQuery)
274
- }, serviceName)
275
- }
276
-
277
- // 初始化条件参数
278
- function initConditionParams(formItems, isQuery) {
279
- let hasDefaults: boolean
280
-
281
- // 从表单配置中获取所有默认值
282
- formItems.forEach((item) => {
283
- if (item.model) {
284
- // 根据查询模式获取对应的默认值
285
- if (item.queryFormDefault !== undefined && item.queryFormDefault !== null) {
286
- if (item.type === 'rangePicker' && item.queryType === 'BETWEEN') {
287
- defaultParams[item.model] = getRangeByType(item.queryFormDefault, false)
288
- }
289
- else if (['treeSelect', 'select', 'checkbox'].includes(item.type) && ['curOrgId', 'curDepId', 'curUserId'].includes(item.queryFormDefault)) {
290
- if (item.queryFormDefault === 'curOrgId') {
291
- defaultParams[item.model] = item.type === 'select' ? userState.f.resources.orgid : [userState.f.resources.orgid]
292
- }
293
- if (item.queryFormDefault === 'curDepId') {
294
- defaultParams[item.model] = item.type === 'select' ? userState.f.resources.depids : [userState.f.resources.depids]
295
- }
296
- if (item.queryFormDefault === 'curUserId') {
297
- defaultParams[item.model] = item.type === 'select' ? userState.f.resources.id : [userState.f.resources.id]
298
- }
299
- }
300
- else {
301
- defaultParams[item.model] = item.queryFormDefault
302
- }
303
- }
304
- }
305
- })
306
- // eslint-disable-next-line prefer-const
307
- hasDefaults = true
308
-
309
- // 如果有默认值,则设置到条件参数中并立即执行查询
310
- isInitQuery.value = isQuery
311
- if (hasDefaults && isInitQuery.value) {
312
- // 延迟执行第一次查询,确保组件完全加载
313
- setTimeout(() => {
314
- loading.value = true
315
- onLoad(defaultParams)
316
- }, 100)
317
- }
318
- }
319
-
320
- // 刷新数据
321
- function onRefresh() {
322
- isError.value = false
323
- setTimeout(() => {
324
- // 重新加载数据
325
- // 将 loading 设置为 true,表示处于加载状态
326
- refreshing.value = true
327
- finishedText.value = '加载完成'
328
- finished.value = false
329
- loading.value = true
330
- onLoad(defaultParams)
331
- }, 100)
332
- }
333
-
334
- // 加载数据
335
- function onLoad(defaultParams = {}) {
336
- if (refreshing.value) {
337
- list.value = []
338
- pageNo.value = 1
339
- isLastPage.value = false
340
- }
341
- if (!isLastPage.value) {
342
- let searchVal = searchValue.value
343
- if (searchVal === '')
344
- searchVal = undefined
345
-
346
- // 确保conditionParams不是undefined
347
- if (conditionParams.value === undefined)
348
- conditionParams.value = {}
349
- const mergedParams = mergeParams(defaultParams, conditionParams.value)
350
- // 输出查询条件,便于调试
351
- console.log('查询条件:', {
352
- pageNo: pageNo.value,
353
- pageSize,
354
- conditionParams: {
355
- $queryValue: searchVal,
356
- ...fixQueryForm,
357
- ...mergedParams,
358
- },
359
- })
360
-
361
- query({
362
- queryParamsName: configName,
363
- pageNo: pageNo.value,
364
- pageSize,
365
- conditionParams: {
366
- $queryValue: searchVal,
367
- ...fixQueryForm,
368
- ...mergedParams,
369
- },
370
- sortField: orderVal?.value,
371
- sortOrder: sortordVal?.value,
372
- }, serviceName).then((res: any) => {
373
- totalCount.value = res.totalCount
374
- if (res.data.length === 0)
375
- isLastPage.value = true
376
-
377
- for (const item of res.data)
378
- list.value.push(item)
379
- if (list.value.length >= res.totalCount)
380
- finished.value = true
381
- else
382
- pageNo.value = pageNo.value + 1
383
- }).catch(() => {
384
- finishedText.value = ''
385
- finished.value = true
386
- isError.value = true
387
- }).finally(() => {
388
- // 加载状态结束
389
- loading.value = false
390
- refreshing.value = false
391
- })
392
- }
393
- }
394
-
395
- // 合并参数
396
- function mergeParams(defaultParams: object, overrideParams: object) {
397
- const result = { ...defaultParams }
398
- for (const [key, value] of Object.entries(overrideParams)) {
399
- // 只有当overrideParams中的值不是undefined或空字符串时才覆盖
400
- if (value !== undefined) {
401
- result[key] = value
402
- }
403
- }
404
- return result
405
- }
406
-
407
- // 区分主要操作列与其他操作列
408
- function splitArrayAt<T>(array: T[], index: number) {
409
- mainActions.value = array.slice(0, index)
410
- otherActions.value = array.slice(index)
411
- }
412
-
413
- // 新增:动态获取按钮分组
414
- function getActionGroups(item: any, index: number) {
415
- // 先过滤出当前行可见的按钮
416
- const visibleActions = allActions.value.filter(action =>
417
- evaluateCustomFunction(action.customFunction, item, index),
418
- )
419
- // 前3个为主按钮,其余为更多按钮,保持逆序
420
- return {
421
- main: visibleActions.slice(0, 3).reverse(),
422
- more: visibleActions.slice(3).reverse(),
423
- }
424
- }
425
-
426
- watch(() => searchValue.value, (newVal) => {
427
- if (newVal === '')
428
- onRefresh()
429
- })
430
-
431
- // 配置中心->表单项展示函数
432
- function handleFunctionStyle(funcString, record) {
433
- if (!funcString) {
434
- return {}
435
- }
436
-
437
- try {
438
- // 同步执行函数
439
- const obj = executeStrFunctionByContext(currInst, funcString, [record])
440
- // 如果返回的是对象,则直接返回
441
- if (obj && typeof obj === 'object') {
442
- return obj
443
- }
444
- // 其他情况返回空对象
445
- return {}
446
- }
447
- catch (error) {
448
- console.error('Error in handleFunctionStyle:', error)
449
- return {}
450
- }
451
- }
452
-
453
- // 逆序排列主要按钮
454
- const reversedMainActions = computed(() => {
455
- return [...mainActions.value].reverse()
456
- })
457
-
458
- // 设置 Popover 的事件
459
- function onSelectMenu(item: any, event: any) {
460
- if (event.text === '删除') {
461
- if (customDelete) {
462
- emit('delete', item)
463
- }
464
- else {
465
- showConfirmDialog({
466
- title: '删除',
467
- message:
468
- '请确认是否删除!!!',
469
- }).then(() => {
470
- emit(event.func, item)
471
- }).catch(() => {
472
- })
473
- }
474
- }
475
- else if (event.text === '修改') {
476
- if (customEdit) {
477
- emit('update', item)
478
- }
479
- else {
480
- // 默认行为 - 导航到XForm页面
481
- router.push({
482
- name: 'XForm',
483
- // params: { id: item },
484
- query: {
485
- groupFormItems: JSON.stringify(groupFormItems.value),
486
- serviceName,
487
- formData: JSON.stringify(item),
488
- mode: '修改',
489
- },
490
- })
491
- }
492
- }
493
- else {
494
- emit(event.func, item)
495
- }
496
- }
497
-
498
- // 抛出新增按钮的事件
499
- function addOption() {
500
- if (customAdd) {
501
- emit('add')
502
- }
503
- else {
504
- // 默认行为 - 导航到XForm页面
505
- router.push({
506
- name: 'XForm',
507
- query: {
508
- groupFormItems: JSON.stringify(groupFormItems.value),
509
- serviceName,
510
- formData: JSON.stringify({}),
511
- mode: '新增',
512
- },
513
- })
514
- }
515
- }
516
-
517
- // 按钮权限信息筛选
518
- function filterButtonPermissions(btn) {
519
- return buttonPermissions.value.find(item => item.btnName === btn)
520
- }
521
-
522
- // 处理按钮点击
523
- function handleButtonClick(btn, item) {
524
- emit(`${btn.btnIcon}`, item)
525
- }
526
-
527
- // 处理自定义函数
528
- function evaluateCustomFunction(funcString: string | undefined, record: any, index: number): boolean {
529
- try {
530
- // 如果 customFunction 不存在,返回 true 表示正常显示
531
- if (!funcString || funcString === '')
532
- return true
533
-
534
- // 匹配参数名、函数体
535
- const innerFuncRegex = /function\s*\((\w+)\s*,\s*(\w+)\)\s*\{([\s\S]*)\}/
536
- const matches = funcString.match(innerFuncRegex)
537
-
538
- if (!matches)
539
- return true
540
-
541
- const [, param1, param2, functionBody] = matches
542
-
543
- // eslint-disable-next-line no-new-func
544
- const func = new Function(param1, param2, functionBody)
545
- return func(record, index)
546
- }
547
- catch (error) {
548
- console.error('Error evaluating custom function:', error)
549
- return true
550
- }
551
- }
552
-
553
- /**
554
- * 函数描述: 传入自定义条件进行查询(传入字段必须在琉璃中配置生成查询项才会生效)
555
- * @param {string} params - 查询条件map 例: { os_id:1 }
556
- * // 小提示:此处传入的条件会覆盖掉fixQueryForm(固定查询条件参数)
557
- */
558
- function updateConditionAndRefresh(params: any) {
559
- if (params) {
560
- // 遍历参数,更新对应的表单值
561
- Object.entries(params).forEach(([key, value]) => {
562
- // 找到对应的表单项
563
- conditionParams.value[key] = value
564
- })
565
- }
566
-
567
- // 触发刷新
568
- onRefresh()
569
- }
570
-
571
- // 暴露方法给父组件
572
- defineExpose({
573
- updateConditionAndRefresh,
574
- onRefresh,
575
- })
576
-
577
- // 多选相关计算属性
578
- const selectedItemsArray = computed(() => {
579
- return list.value.filter(item => selectedItems.value.has(item[idKey]))
580
- })
581
-
582
- const hasSelectedItems = computed(() => {
583
- return selectedItems.value.size > 0
584
- })
585
-
586
- // 多选相关方法
587
- function startLongPress(item: any) {
588
- if (!enableMultiSelect)
589
- return
590
-
591
- longPressTimer.value = window.setTimeout(() => {
592
- enterMultiSelectMode(item)
593
- }, longPressDelay)
594
- }
595
-
596
- function cancelLongPress() {
597
- if (longPressTimer.value) {
598
- clearTimeout(longPressTimer.value)
599
- longPressTimer.value = null
600
- }
601
- }
602
-
603
- function enterMultiSelectMode(item: any) {
604
- isMultiSelectMode.value = true
605
- selectedItems.value.clear()
606
- const itemId = item[idKey]
607
- selectedItems.value.add(itemId)
608
- updateActionSheetActions()
609
- }
610
-
611
- function exitMultiSelectMode() {
612
- isMultiSelectMode.value = false
613
- selectedItems.value.clear()
614
- showActionSheet.value = false
615
- }
616
-
617
- function toggleItemSelection(item: any) {
618
- if (!isMultiSelectMode.value)
619
- return
620
-
621
- const itemId = item[idKey]
622
- console.log('Toggle selection:', { itemId, currentSelectedItems: Array.from(selectedItems.value) })
623
-
624
- if (selectedItems.value.has(itemId)) {
625
- selectedItems.value.delete(itemId)
626
- }
627
- else {
628
- selectedItems.value.add(itemId)
629
- }
630
-
631
- console.log('After toggle:', { selectedItems: Array.from(selectedItems.value) })
632
-
633
- // 更新ActionSheet状态,但不退出多选模式
634
- updateActionSheetActions()
635
-
636
- emit('selectionChange', selectedItemsArray.value)
637
- }
638
-
639
- function updateActionSheetActions() {
640
- if (multiSelectActions.length > 0) {
641
- actionSheetActions.value = multiSelectActions.map(action => ({
642
- name: action.name,
643
- key: action.key || action.name,
644
- color: action.color || '#000000',
645
- icon: action.icon || 'records-o',
646
- }))
647
- }
648
- else {
649
- // 默认操作按钮
650
- actionSheetActions.value = [
651
- { name: '批量操作', key: 'batchOperation', color: '#000000', icon: 'records-o' },
652
- ]
653
- }
654
-
655
- if (hasSelectedItems.value) {
656
- showActionSheet.value = true
657
- }
658
- }
659
-
660
- function handleActionSheetSelect(action: any) {
661
- if (Array.from(selectedItems.value).length === 0 || selectedItemsArray.value.length === 0) {
662
- showToast('请选择至少一条数据')
663
- return
664
- }
665
- showActionSheet.value = false
666
- emit('multiSelectAction', action, Array.from(selectedItems.value), selectedItemsArray.value)
667
-
668
- // 执行操作后退出多选模式
669
- exitMultiSelectMode()
670
- }
671
-
672
- function handleCardClick(item: any, event: any) {
673
- // 如果处于多选模式,阻止默认的详情跳转
674
- if (isMultiSelectMode.value) {
675
- event.preventDefault()
676
- event.stopPropagation()
677
- return
678
- }
679
-
680
- // 正常点击跳转到详情
681
- emit('toDetail', item)
682
- }
683
-
684
- function handleTitleClick(item: any, event: any) {
685
- event.stopPropagation()
686
-
687
- if (isMultiSelectMode.value) {
688
- // 多选模式下,点击标题切换选中状态
689
- toggleItemSelection(item)
690
- }
691
- else {
692
- // 非多选模式下,正常跳转到详情
693
- emit('toDetail', item)
694
- }
695
- }
696
-
697
- function handleCheckboxChange(item: any, checked: boolean) {
698
- if (!isMultiSelectMode.value)
699
- return
700
-
701
- const itemId = item[idKey]
702
- console.log('Checkbox change:', { itemId, checked, currentSelectedItems: Array.from(selectedItems.value) })
703
-
704
- if (checked) {
705
- selectedItems.value.add(itemId)
706
- }
707
- else {
708
- selectedItems.value.delete(itemId)
709
- }
710
-
711
- console.log('After change:', { selectedItems: Array.from(selectedItems.value) })
712
-
713
- // 更新ActionSheet状态,但不退出多选模式
714
- updateActionSheetActions()
715
-
716
- emit('selectionChange', selectedItemsArray.value)
717
- }
718
- </script>
719
-
720
- <template>
721
- <div id="XCellList">
722
- <VanRow class="filter-condition">
723
- <!-- 左侧动态插槽区域 -->
724
- <template v-for="(_, name) in slots" :key="name">
725
- <template v-if="typeof name === 'string' && name.startsWith('search-left-')">
726
- <div class="filter-icon-box">
727
- <slot :name="name" />
728
- </div>
729
- </template>
730
- </template>
731
- <VanCol class="search-col">
732
- <VanSearch
733
- v-model="searchValue"
734
- class="title-search"
735
- :class="{ 'multi-select-mode': isMultiSelectMode }"
736
- clearable
737
- placeholder="综合查询框..."
738
- shape="round"
739
- @search="onRefresh"
740
- />
741
- </VanCol>
742
- <!-- 新增按钮,放在查询框后、查询条件下拉按钮前 -->
743
- <VanCol v-if="!hideAllActions && buttonState?.add && buttonState.add === true && (filterButtonPermissions('add').state === false || ((filterButtonPermissions('add').state === true && userState.f.resources.f_role_name.includes((filterButtonPermissions('add').roleStr)))))" class="add-col">
744
- <VanButton
745
- icon="add"
746
- type="primary"
747
- size="small"
748
- class="add-action-btn"
749
- :disabled="isMultiSelectMode"
750
- @click="addOption"
751
- />
752
- </VanCol>
753
- <!-- 右侧动态插槽区域 -->
754
- <template v-for="(_, name) in slots" :key="name">
755
- <template v-if="typeof name === 'string' && name.startsWith('search-right-')">
756
- <div class="filter-icon-box">
757
- <slot :name="name" />
758
- </div>
759
- </template>
760
- </template>
761
- <VanCol class="search-icon">
762
- <XCellListFilter
763
- v-model:sortord-val="sortordVal"
764
- v-model:order-val="orderVal"
765
- v-model:condition-params="conditionParams"
766
- :fix-query-form="fixQueryForm"
767
- :service-name="serviceName"
768
- :order-list="orderList"
769
- :form-query="formQueryList"
770
- :button-state="buttonState"
771
- :button-permissions="buttonPermissions"
772
- :scan-options="scanOptions"
773
- @on-refresh="onRefresh"
774
- />
775
- </VanCol>
776
- </VanRow>
777
- <slot name="search-after" />
778
-
779
- <!-- 多选功能提示 -->
780
- <div v-if="enableMultiSelect && !isMultiSelectMode" class="multi-select-tip">
781
- <VanIcon name="info-o" />
782
- <span>长按卡片可进入多选模式</span>
783
- </div>
784
-
785
- <!-- 多选模式提示 -->
786
- <div v-if="isMultiSelectMode" class="multi-select-tip">
787
- <VanIcon name="info-o" />
788
- <span>多选模式 - 已选择 {{ selectedItems.size }} 项</span>
789
- <VanButton
790
- size="small"
791
- type="default"
792
- @click="exitMultiSelectMode"
793
- >
794
- 退出
795
- </VanButton>
796
- </div>
797
-
798
- <div class="main">
799
- <VanPullRefresh v-model="refreshing" :success-text="finishedText" head-height="70" @refresh="onRefresh">
800
- <template v-if="!isError">
801
- <VanList
802
- v-model:loading="loading"
803
- class="list_main"
804
- :finished="finished"
805
- :finished-text="finishedBottomText()"
806
- :immediate-check="isInitQuery"
807
- @load="onLoad"
808
- >
809
- <div
810
- v-for="(item, index) in list"
811
- :key="`card_${index}`"
812
- class="card_item_main"
813
- :class="{ 'multi-select-mode': isMultiSelectMode }"
814
- @touchstart="startLongPress(item)"
815
- @touchend="cancelLongPress"
816
- @touchcancel="cancelLongPress"
817
- @mousedown="startLongPress(item)"
818
- @mouseup="cancelLongPress"
819
- @mouseleave="cancelLongPress"
820
- >
821
- <VanRow gutter="20" class="card_item_header" align="center" @click="handleCardClick(item, $event)">
822
- <VanCol :span="24">
823
- <div class="title-row" :class="{ 'multi-select-title-row': isMultiSelectMode }">
824
- <!-- 多选模式下的选择框 -->
825
- <div v-if="isMultiSelectMode" class="selection-checkbox">
826
- <VanCheckbox
827
- :model-value="selectedItems.has(item[idKey])"
828
- shape="square"
829
- @update:model-value="(checked) => handleCheckboxChange(item, checked)"
830
- />
831
- </div>
832
- <div v-for="(column) in mainColumns" :key="`main_${column.dataIndex}`" class="main-title">
833
- <p
834
- class="card_item_title"
835
- :style="handleFunctionStyle(column.styleFunctionForValue, item)"
836
- :class="{ 'selectable-title': isMultiSelectMode }"
837
- @click="handleTitleClick(item, $event)"
838
- >
839
- {{ item[column.dataIndex] ?? '--' }}
840
- </p>
841
- </div>
842
- <div v-for="(column) in subTitleColumns" :key="`subtitle_${column.dataIndex}`" class="sub-title">
843
- <p
844
- class="card_item_subtitle"
845
- :style="handleFunctionStyle(column.styleFunctionForValue, item)"
846
- >
847
- <XBadge
848
- :style="handleFunctionStyle(column.styleFunctionForValue, item)"
849
- :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
850
- :service-name="serviceName"
851
- />
852
- </p>
853
- </div>
854
- <div v-if="!hideAllActions" class="action-buttons">
855
- <VanButton
856
- v-for="btn in btnList"
857
- :key="btn.dataIndex"
858
- class="action-button"
859
- :icon="btn.btnIcon"
860
- size="small"
861
- :disabled="isMultiSelectMode"
862
- @click.stop="handleButtonClick(btn, item)"
863
- />
864
- </div>
865
- </div>
866
- </VanCol>
867
- </VanRow>
868
- <VanRow gutter="20" class="card_item_details" @click="handleCardClick(item, $event)">
869
- <VanCol v-for="column of detailColumns" :key="`details_${column.dataIndex}`" :span="column.span">
870
- <p>
871
- {{ (column.showLabel === undefined || column.showLabel) ? `${column.title}: ` : '' }}
872
- <XBadge
873
- :style="handleFunctionStyle(column.styleFunctionForValue, item)"
874
- :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
875
- :service-name="serviceName"
876
- />
877
- </p>
878
- </VanCol>
879
- </VanRow>
880
- <VanRow v-if="tagList.length > 0" gutter="20" class="tag-row" @click="handleCardClick(item, $event)">
881
- <VanCol :span="24">
882
- <div class="tag-container">
883
- <div class="tag-wrapper">
884
- <template
885
- v-for="column of tagList"
886
- :key="`tag_${column.dataIndex}`"
887
- >
888
- <VanTag
889
- :text-color="column.tagColor"
890
- :color="column.tagBorderColor"
891
- size="large"
892
- class="tag-item"
893
- >
894
- <div class="tag-content">
895
- <VanIcon v-if="column.tagIcon" :name="column.tagIcon" class="tag-icon" />
896
- <span v-if="column.showLabel === undefined || column.showLabel" class="tag-title">{{ `${column.title}: ` }}</span>
897
- <XBadge
898
- :dict-name="column.dictName"
899
- :dict-value="item[column.dataIndex]"
900
- :service-name="serviceName"
901
- />
902
- </div>
903
- </VanTag>
904
- </template>
905
- </div>
906
- </div>
907
- </VanCol>
908
- </VanRow>
909
- <VanRow
910
- v-if="footColumns && footColumns.length > 0"
911
- gutter="20"
912
- class="card_item_footer"
913
- @click="handleCardClick(item, $event)"
914
- >
915
- <VanCol v-for="column of footColumns" :key="`foot_${column.dataIndex}`" :span="12">
916
- <p>
917
- <span :style="handleFunctionStyle(column.styleFunctionForTitle, item)">
918
- {{ (column.showLabel === undefined || column.showLabel) ? `${column.title}: ` : '' }}
919
- </span>
920
- <XBadge
921
- :style="handleFunctionStyle(column.styleFunctionForValue, item)"
922
- :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
923
- :service-name="serviceName"
924
- />
925
- </p>
926
- </VanCol>
927
- </VanRow>
928
- <!-- 添加详情插槽 -->
929
- <slot name="item-detail" :item="item" />
930
- <VanRow v-if="!hideAllActions && allActions.length > 0" gutter="20" class="card_item_bottom">
931
- <VanCol span="4">
932
- <VanPopover
933
- v-if="getActionGroups(item, index).more.length"
934
- v-model:show="showPopover[index]"
935
- placement="bottom-start"
936
- :actions="getActionGroups(item, index).more"
937
- @select="onSelectMenu(item, $event)"
938
- >
939
- <template #reference>
940
- <div class="more-button" :class="{ disabled: isMultiSelectMode }">
941
- <span>⋯</span>
942
- </div>
943
- </template>
944
- </VanPopover>
945
- </VanCol>
946
- <VanCol span="20">
947
- <VanRow justify="end">
948
- <VanSpace>
949
- <VanButton
950
- v-for="button in getActionGroups(item, index).main"
951
- :key="button.func"
952
- type="primary"
953
- size="normal"
954
- class="action-btn"
955
- :disabled="isMultiSelectMode"
956
- @click="onSelectMenu(item, button)"
957
- >
958
- {{ button.text }}
959
- </VanButton>
960
- </VanSpace>
961
- </VanRow>
962
- </VanCol>
963
- </VanRow>
964
- </div>
965
- </VanList>
966
- </template>
967
- <template v-else>
968
- <LoadError />
969
- </template>
970
- </VanPullRefresh>
971
- <VanBackTop />
972
- </div>
973
-
974
- <!-- 多选操作面板 -->
975
- <VanActionSheet
976
- v-model:show="showActionSheet"
977
- :actions="actionSheetActions"
978
- :overlay="false"
979
- @select="handleActionSheetSelect"
980
- />
981
- </div>
982
- </template>
983
-
984
- <style scoped lang="less">
985
- #XCellList {
986
- height: calc(100vh - var(--van-nav-bar-height) - 20px);
987
- display: flex;
988
- flex-direction: column;
989
- --van-search-padding: 3px;
990
- --van-dropdown-menu-title-padding: 3px;
991
- --van-text-color-2: rgb(75, 85, 99);
992
- --van-button-normal-font-size: 13px;
993
- .main {
994
- flex: 1;
995
- min-height: 0;
996
- overflow-y: auto;
997
- background-color: var(--van-background);
998
- padding: var(--van-padding-base) var(--van-padding-sm);
999
-
1000
- p {
1001
- white-space: nowrap;
1002
- overflow: hidden;
1003
- text-overflow: ellipsis;
1004
- margin: 0;
1005
- }
1006
-
1007
- .card_item_main {
1008
- background-color: var(--van-background-2);
1009
- border-radius: var(--van-radius-lg);
1010
- margin: 0 0 var(--van-padding-xs) 0;
1011
- padding: var(--van-padding-sm);
1012
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
1013
- transition: all 0.3s ease;
1014
- border: 1px solid rgba(0, 0, 0, 0.04);
1015
- position: relative;
1016
-
1017
- &.multi-select-mode {
1018
- border-color: var(--van-primary-color);
1019
- background-color: rgba(25, 137, 250, 0.02);
1020
- }
1021
-
1022
- .selection-checkbox {
1023
- position: relative;
1024
- margin-right: 8px;
1025
- z-index: 1;
1026
- flex-shrink: 0;
1027
-
1028
- :deep(.van-checkbox__icon) {
1029
- border-color: var(--van-primary-color);
1030
- border-radius: 2px;
1031
- }
1032
-
1033
- :deep(.van-checkbox__icon--checked) {
1034
- background-color: var(--van-primary-color);
1035
- border-color: var(--van-primary-color);
1036
- border-radius: 2px;
1037
- }
1038
- }
1039
-
1040
- &:active {
1041
- transform: scale(0.98);
1042
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02);
1043
- }
1044
-
1045
- .card_item_header {
1046
- margin-bottom: var(--van-padding-base);
1047
-
1048
- .title-row {
1049
- display: flex;
1050
- align-items: center;
1051
- margin-bottom: 2px;
1052
- width: 100%;
1053
-
1054
- &.multi-select-title-row {
1055
- padding-left: 0;
1056
- }
1057
-
1058
- .main-title {
1059
- display: inline-flex;
1060
- align-items: center;
1061
- .card_item_title {
1062
- font-size: var(--van-font-size-lg);
1063
- font-weight: 700;
1064
- color: var(--van-text-color);
1065
- margin: 0;
1066
-
1067
- &.selectable-title {
1068
- cursor: pointer;
1069
- padding: 4px 8px;
1070
- border-radius: 4px;
1071
- transition: all 0.2s ease;
1072
-
1073
- &:hover {
1074
- background-color: rgba(25, 137, 250, 0.1);
1075
- }
1076
-
1077
- &:active {
1078
- background-color: rgba(25, 137, 250, 0.2);
1079
- }
1080
- }
1081
- }
1082
- }
1083
-
1084
- .sub-title {
1085
- display: inline-flex;
1086
- align-items: center;
1087
- margin-left: 8px;
1088
- .card_item_subtitle {
1089
- font-size: var(--van-font-size-md);
1090
- color: var(--van-text-color-2);
1091
- }
1092
- }
1093
-
1094
- .action-buttons {
1095
- display: flex;
1096
- align-items: center;
1097
- margin-left: auto;
1098
- .action-button {
1099
- margin-left: 6px;
1100
- width: 32px;
1101
- height: 32px;
1102
- padding: 0;
1103
- border: none;
1104
- color: var(--van-primary-color);
1105
- background-color: rgba(25, 137, 250, 0.1);
1106
- border-radius: 6px;
1107
- font-size: var(--van-font-size-lg);
1108
- display: flex;
1109
- align-items: center;
1110
- justify-content: center;
1111
- transition: all 0.2s ease;
1112
- &:active {
1113
- opacity: 0.7;
1114
- background-color: rgba(25, 137, 250, 0.2);
1115
- transform: scale(0.95);
1116
- }
1117
- }
1118
- }
1119
- }
1120
- }
1121
-
1122
- .tag-row {
1123
- margin-bottom: var(--van-padding-base);
1124
- }
1125
-
1126
- .tag-container {
1127
- width: 100%;
1128
- .tag-wrapper {
1129
- display: flex;
1130
- flex-wrap: wrap;
1131
- padding: 0 !important;
1132
- align-items: center;
1133
- width: 100%;
1134
- margin: 0 -4px;
1135
-
1136
- .tag-item {
1137
- width: auto;
1138
- font-size: var(--van-font-size-md);
1139
- margin: 4px 4px;
1140
- :deep(.van-tag) {
1141
- width: fit-content;
1142
- display: inline-flex;
1143
- align-items: center;
1144
- padding: 2px 8px;
1145
- border-radius: 4px;
1146
- }
1147
- .tag-content {
1148
- display: flex;
1149
- align-items: center;
1150
- //white-space: nowrap;
1151
- }
1152
- .tag-icon {
1153
- margin-right: 4px;
1154
- font-size: 14px;
1155
- display: inline-flex;
1156
- align-items: center;
1157
- }
1158
- .tag-title {
1159
- font-weight: normal;
1160
- }
1161
- }
1162
- }
1163
- }
1164
-
1165
- .card_item_details {
1166
- font-size: var(--van-font-size-md);
1167
- color: var(--van-text-color-2);
1168
- padding: 4px 0;
1169
-
1170
- .van-col {
1171
- margin-bottom: 4px;
1172
- p {
1173
- display: flex;
1174
- align-items: center;
1175
- gap: 4px;
1176
-
1177
- span {
1178
- flex: 1;
1179
- min-width: 0;
1180
- overflow: hidden;
1181
- text-overflow: ellipsis;
1182
- white-space: nowrap;
1183
- }
1184
-
1185
- :deep(.van-badge) {
1186
- flex-shrink: 0;
1187
- }
1188
- }
1189
- }
1190
- }
1191
-
1192
- .card_item_footer {
1193
- font-size: var(--van-font-size-md);
1194
- color: var(--van-text-color-2);
1195
- padding: 4px 0;
1196
- .van-col:last-child {
1197
- text-align: right;
1198
- }
1199
- .van-col:first-child {
1200
- text-align: left;
1201
- }
1202
- }
1203
-
1204
- .card_item_bottom {
1205
- margin-top: 8px;
1206
- padding-top: 10px;
1207
- border-top: 1px solid rgba(0, 0, 0, 0.06);
1208
-
1209
- .more-button {
1210
- width: 37px;
1211
- height: 37px;
1212
- display: flex;
1213
- align-items: center;
1214
- justify-content: center;
1215
- color: var(--van-text-color-2);
1216
- cursor: pointer;
1217
- font-size: var(--van-font-size-lg);
1218
- background-color: var(--van-background);
1219
- border-radius: 6px;
1220
- transition: all 0.2s ease;
1221
- border: 1px solid rgba(0, 0, 0, 0.06);
1222
- margin: 2px 0 0 0;
1223
- span {
1224
- line-height: 1;
1225
- margin-top: -2px;
1226
- }
1227
- &:active {
1228
- opacity: 0.7;
1229
- background-color: var(--van-background-2);
1230
- transform: scale(0.95);
1231
- }
1232
-
1233
- &.disabled {
1234
- opacity: 0.5;
1235
- cursor: not-allowed;
1236
- pointer-events: none;
1237
- }
1238
- }
1239
-
1240
- .action-btn {
1241
- --van-button-primary-border-color: #1890ff;
1242
- --van-button-primary-background: #1890ff;
1243
- min-width: 76px;
1244
- height: 40px;
1245
- border-radius: 10px;
1246
- padding: 0 14px;
1247
- // font-size: var(--van-font-size-md);
1248
- transition: all 0.2s ease;
1249
- &:active {
1250
- transform: scale(0.95);
1251
- }
1252
- }
1253
- }
1254
- }
1255
- }
1256
-
1257
- :deep(.van-search__field) {
1258
- padding: 0 !important;
1259
- height: 100%;
1260
- }
1261
-
1262
- :deep(.van-popup),
1263
- :deep(.van-popover) {
1264
- border: none !important;
1265
- outline: none !important;
1266
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
1267
- background-color: #ffffff !important;
1268
-
1269
- &::before,
1270
- &::after {
1271
- border: none !important;
1272
- outline: none !important;
1273
- }
1274
-
1275
- .van-popover__content {
1276
- background-color: #ffffff !important;
1277
- border: none !important;
1278
- outline: none !important;
1279
- border-radius: 8px !important;
1280
- }
1281
-
1282
- .van-popover__arrow,
1283
- .van-popover_arrow {
1284
- background-color: #ffffff !important;
1285
- border: none !important;
1286
- outline: none !important;
1287
- }
1288
-
1289
- .van-popover__action {
1290
- color: #333 !important;
1291
-
1292
- &:hover {
1293
- background-color: #f5f5f5 !important;
1294
- }
1295
- }
1296
- }
1297
-
1298
- .filter-condition {
1299
- display: flex;
1300
- align-items: center;
1301
- padding: 8px 12px;
1302
- background-color: #fff;
1303
- gap: 1px;
1304
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02);
1305
- position: sticky;
1306
- top: 0;
1307
- z-index: 100;
1308
-
1309
- .add-col {
1310
- display: flex;
1311
- align-items: center;
1312
- margin: 0 4px;
1313
- .add-action-btn {
1314
- width: 40px;
1315
- height: 40px;
1316
- border-radius: 10px;
1317
- background-color: var(--van-background);
1318
- color: #323233;
1319
- border: 1px solid rgba(0, 0, 0, 0.06);
1320
- display: flex;
1321
- align-items: center;
1322
- justify-content: center;
1323
- font-size: 16px;
1324
- padding: 0;
1325
- transition: all 0.2s;
1326
- &:hover,
1327
- &:active {
1328
- opacity: 0.85;
1329
- background-color: rgba(25, 137, 250, 0.08);
1330
- border-color: var(--van-primary-color);
1331
- color: var(--van-primary-color);
1332
- }
1333
- }
1334
- }
1335
- :deep(.van-search) {
1336
- width: 100%;
1337
- padding: var(--van-search-padding);
1338
- background-color: transparent;
1339
-
1340
- &.multi-select-mode {
1341
- opacity: 0.8;
1342
- }
1343
- }
1344
- :deep(.van-search__content) {
1345
- border-radius: 8px;
1346
- background-color: var(--van-background);
1347
- padding: 4px 12px;
1348
- border: 1px solid rgba(0, 0, 0, 0.01);
1349
- height: 40px;
1350
- }
1351
- :deep(.van-field__left-icon) {
1352
- color: var(--van-text-color-2);
1353
- }
1354
- :deep(.van-cell) {
1355
- background-color: transparent;
1356
- }
1357
- :deep(.van-field__control::placeholder) {
1358
- color: var(--van-text-color-2);
1359
- font-size: 14px;
1360
- }
1361
- .van-col {
1362
- display: flex;
1363
- align-items: center;
1364
- &.search-col {
1365
- flex: 1;
1366
- min-width: 0;
1367
- }
1368
- &.search-icon {
1369
- flex-shrink: 0;
1370
- padding: 0;
1371
- }
1372
- }
1373
- .filter-icon-box {
1374
- display: flex;
1375
- align-items: center;
1376
- justify-content: center;
1377
- width: 36px;
1378
- height: 36px;
1379
- border-radius: 8px;
1380
- background-color: var(--van-background);
1381
- cursor: pointer;
1382
- position: relative;
1383
- z-index: 1;
1384
- border: 1px solid rgba(0, 0, 0, 0.06);
1385
- transition: all 0.2s ease;
1386
- &:active {
1387
- opacity: 0.7;
1388
- transform: scale(0.95);
1389
- }
1390
- }
1391
- }
1392
-
1393
- .multi-select-hint {
1394
- display: flex;
1395
- align-items: center;
1396
- padding: 8px 12px;
1397
- background-color: rgba(0, 0, 0, 0.04);
1398
- border: 1px solid rgba(0, 0, 0, 0.08);
1399
- border-radius: 6px;
1400
- margin: 8px 12px;
1401
- font-size: var(--van-font-size-sm);
1402
- color: var(--van-text-color-2);
1403
-
1404
- .van-icon {
1405
- margin-right: 6px;
1406
- color: var(--van-text-color-3);
1407
- }
1408
-
1409
- span {
1410
- flex: 1;
1411
- }
1412
- }
1413
-
1414
- .multi-select-tip {
1415
- display: flex;
1416
- align-items: center;
1417
- justify-content: space-between;
1418
- padding: 8px 12px;
1419
- background-color: rgba(25, 137, 250, 0.1);
1420
- border: 1px solid rgba(25, 137, 250, 0.2);
1421
- border-radius: 6px;
1422
- margin: 8px 12px;
1423
- font-size: var(--van-font-size-sm);
1424
- color: var(--van-primary-color);
1425
-
1426
- .van-icon {
1427
- margin-right: 6px;
1428
- }
1429
-
1430
- span {
1431
- flex: 1;
1432
- margin-right: 8px;
1433
- }
1434
- }
1435
- }
1436
- </style>
1
+ <script setup lang="ts">
2
+ import XBadge from '@af-mobile-client-vue3/components/data/XBadge/index.vue'
3
+ import XCellListFilter from '@af-mobile-client-vue3/components/data/XCellListFilter/index.vue'
4
+ import { getConfigByName, query } from '@af-mobile-client-vue3/services/api/common'
5
+ import useUserStore from '@af-mobile-client-vue3/stores/modules/user'
6
+ import { getRangeByType } from '@af-mobile-client-vue3/utils/queryFormDefaultRangePicker'
7
+ import { executeStrFunctionByContext } from '@af-mobile-client-vue3/utils/runEvalFunction'
8
+ import LoadError from '@af-mobile-client-vue3/views/common/LoadError.vue'
9
+ import {
10
+ showConfirmDialog,
11
+ showToast,
12
+ ActionSheet as VanActionSheet,
13
+ BackTop as VanBackTop,
14
+ Button as VanButton,
15
+ Checkbox as VanCheckbox,
16
+ Col as VanCol,
17
+ Icon as VanIcon,
18
+ List as VanList,
19
+ Popover as VanPopover,
20
+ PullRefresh as VanPullRefresh,
21
+ Row as VanRow,
22
+ Search as VanSearch,
23
+ Space as VanSpace,
24
+ Tag as VanTag,
25
+ } from 'vant'
26
+ import { computed, defineEmits, defineProps, getCurrentInstance, onBeforeMount, ref, useSlots, watch } from 'vue'
27
+ import { useRouter } from 'vue-router'
28
+
29
+ const {
30
+ configName = '',
31
+ fixQueryForm = null,
32
+ idKey = 'o_id',
33
+ serviceName,
34
+ scanOptions,
35
+ customAdd = false,
36
+ customEdit = false,
37
+ customDelete = false,
38
+ hideAllActions = false,
39
+ // 多选相关配置
40
+ enableMultiSelect = false, // 是否启用多选功能
41
+ multiSelectActions = [], // 多选操作按钮配置
42
+ } = defineProps<{
43
+ configName?: string
44
+ fixQueryForm?: object
45
+ idKey?: string
46
+ serviceName?: string
47
+ scanOptions?: {
48
+ show?: boolean // 是否显示扫码按钮
49
+ type?: string | string[] // 显示类型:可以是单个类型或类型数组 'nfc' / ['scan', 'nfc']
50
+ defaultMode?: string // 默认模式
51
+ }
52
+ // 是否自定义新增、编辑、删除按钮
53
+ customAdd?: boolean
54
+ customEdit?: boolean
55
+ customDelete?: boolean
56
+ // 是否隐藏所有操作按钮
57
+ hideAllActions?: boolean
58
+ // 多选相关配置
59
+ enableMultiSelect?: boolean // 是否启用多选功能
60
+ multiSelectActions?: Array<{
61
+ name: string
62
+ key?: string
63
+ color?: string
64
+ icon?: string
65
+ }> // 多选操作按钮配置
66
+ }>()
67
+
68
+ const emit = defineEmits<{
69
+ (e: 'toDetail', item: any): void
70
+ (e: 'update', item: any): void
71
+ (e: 'delete', item: any): void
72
+ (e: 'add'): void
73
+ (e: string, item: any): void
74
+ (e: 'updateCondition', params: any): void
75
+ // 多选相关事件
76
+ (e: 'multiSelectAction', action: string, selectedItems: any[], selectedItemsArray: any[]): void
77
+ (e: 'selectionChange', selectedItems: any[]): void
78
+ }>()
79
+
80
+ const userState = useUserStore().getLogin()
81
+
82
+ const router = useRouter()
83
+
84
+ const orderVal = ref(undefined)
85
+
86
+ const sortordVal = ref(undefined)
87
+
88
+ // 配置内容
89
+ const configContent = ref({})
90
+
91
+ // 表单加载后是否立即执行查询
92
+ const isInitQuery = ref(false)
93
+
94
+ // 主列
95
+ const mainColumns = ref([])
96
+
97
+ // 副标题列
98
+ const subTitleColumns = ref([])
99
+
100
+ // 详情列
101
+ const detailColumns = ref([])
102
+
103
+ // 标签列
104
+ const tagList = ref([])
105
+
106
+ // 标题按钮列
107
+ const btnList = ref([])
108
+
109
+ // 底部列
110
+ const footColumns = ref([])
111
+
112
+ // 所有的复杂操作列
113
+ const allActions = ref([])
114
+
115
+ // 复杂操作列前三项
116
+ const mainActions = ref([])
117
+
118
+ // 复杂操作列其余项
119
+ const otherActions = ref([])
120
+
121
+ // 数据集
122
+ const list = ref([])
123
+
124
+ // 每个 Popover 的显示状态
125
+ const showPopover = ref<boolean[]>(list.value.map(() => false))
126
+
127
+ // 排序集
128
+ const orderList = ref([])
129
+
130
+ // 表单查询数组
131
+ const formQueryList = ref([])
132
+
133
+ // 总数据
134
+ const totalCount = ref(0)
135
+
136
+ // 当前页数
137
+ const pageNo = ref(1)
138
+
139
+ // 每页数量
140
+ const pageSize = 20
141
+
142
+ const searchValue = ref('')
143
+
144
+ // 数据加载状态
145
+ const loading = ref(false)
146
+ const refreshing = ref(false)
147
+ const finished = ref(false)
148
+ const isError = ref(false)
149
+ const finishedText = ref('加载完成')
150
+ // 避免查询多次
151
+ const isLastPage = ref(false)
152
+
153
+ // 多选相关状态
154
+ const isMultiSelectMode = ref(false) // 是否处于多选模式
155
+ const selectedItems = ref<Set<string>>(new Set()) // 选中的项目ID集合
156
+ const longPressTimer = ref<number | null>(null) // 长按定时器
157
+ const longPressDelay = 500 // 长按延迟时间(毫秒)
158
+
159
+ // 条件参数(查询框)
160
+ const conditionParams = ref(undefined)
161
+
162
+ // 主要按钮的状态
163
+ const buttonState = ref(undefined)
164
+
165
+ // 新增or查询的表单配置
166
+ const groupFormItems = ref({})
167
+ const title = ref('')
168
+
169
+ // 按钮权限
170
+ const buttonPermissions = ref([])
171
+
172
+ // 默认参数
173
+ const defaultParams = {}
174
+
175
+ // ActionSheet相关状态
176
+ const showActionSheet = ref(false)
177
+ const actionSheetActions = ref([])
178
+
179
+ const slots = useSlots()
180
+
181
+ // 当前组件实例(不推荐使用,可能会在后续的版本更迭中调整,暂时用来绑定函数的上下文)
182
+ const currInst = getCurrentInstance()
183
+
184
+ // 列表底部的文字显示
185
+ function finishedBottomText() {
186
+ if (!hideAllActions && buttonState.value?.add && buttonState.value.add === true && (filterButtonPermissions('add').state === false || ((filterButtonPermissions('add').state === true && userState.f.resources.f_role_name.includes((filterButtonPermissions('add').roleStr))))))
187
+ return '已加载全部内容,如需新增请点击右上角的 + 号'
188
+ else
189
+ return '已加载全部内容'
190
+ }
191
+
192
+ onBeforeMount(() => {
193
+ initComponent()
194
+ })
195
+
196
+ // 组件初始化
197
+ function initComponent() {
198
+ getConfigByName(configName, (result) => {
199
+ groupFormItems.value = result
200
+ title.value = result?.title
201
+ const isQuery = result.createdQuery
202
+ for (let i = 0; i < result.columnJson.length; i++) {
203
+ const item = result.columnJson[i]
204
+ item.span = item.flexSpan
205
+ if (item.slotType === 'badge')
206
+ item.dictName = item.slotKeyMap
207
+
208
+ if (item.mobileColumnType === 'mobile_header_column') {
209
+ mainColumns.value.push(item)
210
+ }
211
+ else if (item.mobileColumnType === 'mobile_subtitle_column') {
212
+ subTitleColumns.value.push(item)
213
+ }
214
+ else if (item.mobileColumnType === 'mobile_details_column') {
215
+ detailColumns.value.push(item)
216
+ }
217
+ else if (item.mobileColumnType === 'mobile_footer_column') {
218
+ footColumns.value.push(item)
219
+ }
220
+ else if (item.mobileColumnType === 'mobile_tag_column') {
221
+ tagList.value.push(item)
222
+ }
223
+ else if (item.slotType === 'action' && item.actionArr) {
224
+ for (let j = 0; j < item.actionArr.length; j++) {
225
+ allActions.value.push({
226
+ text: item.actionArr[j].text,
227
+ func: item.actionArr[j].func,
228
+ customFunction: item.actionArr[j].customFunction,
229
+ })
230
+ }
231
+ }
232
+
233
+ if (item.btnIcon)
234
+ btnList.value.push(item)
235
+
236
+ if (result.showSortIcon && item.sortable) {
237
+ orderList.value.push({
238
+ title: item.title,
239
+ value: item.dataIndex,
240
+ })
241
+ }
242
+ }
243
+ configContent.value = result
244
+ // 扁平化 type=group 的 groupItems,保持顺序
245
+ const flatFormJson = []
246
+ result.formJson.forEach((item) => {
247
+ if (item.type === 'group' && Array.isArray(item.groupItems)) {
248
+ item.groupItems.forEach((groupItem) => {
249
+ if (!groupItem.isOnlyAddOrEdit) {
250
+ flatFormJson.push(groupItem)
251
+ }
252
+ })
253
+ }
254
+ else {
255
+ if (!item.isOnlyAddOrEdit && item.type !== 'group') {
256
+ flatFormJson.push(item)
257
+ }
258
+ }
259
+ })
260
+ formQueryList.value = flatFormJson
261
+ console.log('formQueryList', formQueryList.value)
262
+ if (result.buttonState) {
263
+ buttonState.value = result.buttonState
264
+ buttonPermissions.value = result.buttonPermissions
265
+ if (buttonState.value.edit && buttonState.value.edit === true && (filterButtonPermissions('edit').state === false || ((filterButtonPermissions('edit').state === true && userState.f.resources.f_role_name.includes((filterButtonPermissions('edit').roleStr))))))
266
+ allActions.value.push({ text: '修改', func: 'update' })
267
+ if (buttonState.value.delete && buttonState.value.delete === true && (filterButtonPermissions('delete').state === false || ((filterButtonPermissions('delete').state === true && userState.f.resources.f_role_name.includes((filterButtonPermissions('delete').roleStr))))))
268
+ allActions.value.push({ text: '删除', func: 'delete' })
269
+ }
270
+ splitArrayAt(allActions.value, 3)
271
+
272
+ // 初始化条件参数(从表单默认值中获取)
273
+ initConditionParams(result.formJson, isQuery)
274
+ }, serviceName)
275
+ }
276
+
277
+ // 初始化条件参数
278
+ function initConditionParams(formItems, isQuery) {
279
+ let hasDefaults: boolean
280
+
281
+ // 从表单配置中获取所有默认值
282
+ formItems.forEach((item) => {
283
+ if (item.model) {
284
+ // 根据查询模式获取对应的默认值
285
+ if (item.queryFormDefault !== undefined && item.queryFormDefault !== null) {
286
+ if (item.type === 'rangePicker' && item.queryType === 'BETWEEN') {
287
+ defaultParams[item.model] = getRangeByType(item.queryFormDefault, false)
288
+ }
289
+ else if (['treeSelect', 'select', 'checkbox'].includes(item.type) && ['curOrgId', 'curDepId', 'curUserId'].includes(item.queryFormDefault)) {
290
+ if (item.queryFormDefault === 'curOrgId') {
291
+ defaultParams[item.model] = item.type === 'select' ? userState.f.resources.orgid : [userState.f.resources.orgid]
292
+ }
293
+ if (item.queryFormDefault === 'curDepId') {
294
+ defaultParams[item.model] = item.type === 'select' ? userState.f.resources.depids : [userState.f.resources.depids]
295
+ }
296
+ if (item.queryFormDefault === 'curUserId') {
297
+ defaultParams[item.model] = item.type === 'select' ? userState.f.resources.id : [userState.f.resources.id]
298
+ }
299
+ }
300
+ else {
301
+ defaultParams[item.model] = item.queryFormDefault
302
+ }
303
+ }
304
+ }
305
+ })
306
+ // eslint-disable-next-line prefer-const
307
+ hasDefaults = true
308
+
309
+ // 如果有默认值,则设置到条件参数中并立即执行查询
310
+ isInitQuery.value = isQuery
311
+ if (hasDefaults && isInitQuery.value) {
312
+ // 延迟执行第一次查询,确保组件完全加载
313
+ setTimeout(() => {
314
+ loading.value = true
315
+ onLoad(defaultParams)
316
+ }, 100)
317
+ }
318
+ }
319
+
320
+ // 刷新数据
321
+ function onRefresh() {
322
+ isError.value = false
323
+ setTimeout(() => {
324
+ // 重新加载数据
325
+ // 将 loading 设置为 true,表示处于加载状态
326
+ refreshing.value = true
327
+ finishedText.value = '加载完成'
328
+ finished.value = false
329
+ loading.value = true
330
+ onLoad(defaultParams)
331
+ }, 100)
332
+ }
333
+
334
+ // 加载数据
335
+ function onLoad(defaultParams = {}) {
336
+ if (refreshing.value) {
337
+ list.value = []
338
+ pageNo.value = 1
339
+ isLastPage.value = false
340
+ }
341
+ if (!isLastPage.value) {
342
+ let searchVal = searchValue.value
343
+ if (searchVal === '')
344
+ searchVal = undefined
345
+
346
+ // 确保conditionParams不是undefined
347
+ if (conditionParams.value === undefined)
348
+ conditionParams.value = {}
349
+ const mergedParams = mergeParams(defaultParams, conditionParams.value)
350
+ // 输出查询条件,便于调试
351
+ console.log('查询条件:', {
352
+ pageNo: pageNo.value,
353
+ pageSize,
354
+ conditionParams: {
355
+ $queryValue: searchVal,
356
+ ...fixQueryForm,
357
+ ...mergedParams,
358
+ },
359
+ })
360
+
361
+ query({
362
+ queryParamsName: configName,
363
+ pageNo: pageNo.value,
364
+ pageSize,
365
+ conditionParams: {
366
+ $queryValue: searchVal,
367
+ ...fixQueryForm,
368
+ ...mergedParams,
369
+ },
370
+ sortField: orderVal?.value,
371
+ sortOrder: sortordVal?.value,
372
+ }, serviceName).then((res: any) => {
373
+ totalCount.value = res.totalCount
374
+ if (res.data.length === 0)
375
+ isLastPage.value = true
376
+
377
+ for (const item of res.data)
378
+ list.value.push(item)
379
+ if (list.value.length >= res.totalCount)
380
+ finished.value = true
381
+ else
382
+ pageNo.value = pageNo.value + 1
383
+ }).catch(() => {
384
+ finishedText.value = ''
385
+ finished.value = true
386
+ isError.value = true
387
+ }).finally(() => {
388
+ // 加载状态结束
389
+ loading.value = false
390
+ refreshing.value = false
391
+ })
392
+ }
393
+ }
394
+
395
+ // 合并参数
396
+ function mergeParams(defaultParams: object, overrideParams: object) {
397
+ const result = { ...defaultParams }
398
+ for (const [key, value] of Object.entries(overrideParams)) {
399
+ // 只有当overrideParams中的值不是undefined或空字符串时才覆盖
400
+ if (value !== undefined) {
401
+ result[key] = value
402
+ }
403
+ }
404
+ return result
405
+ }
406
+
407
+ // 区分主要操作列与其他操作列
408
+ function splitArrayAt<T>(array: T[], index: number) {
409
+ mainActions.value = array.slice(0, index)
410
+ otherActions.value = array.slice(index)
411
+ }
412
+
413
+ // 新增:动态获取按钮分组
414
+ function getActionGroups(item: any, index: number) {
415
+ // 先过滤出当前行可见的按钮
416
+ const visibleActions = allActions.value.filter(action =>
417
+ evaluateCustomFunction(action.customFunction, item, index),
418
+ )
419
+ // 前3个为主按钮,其余为更多按钮,保持逆序
420
+ return {
421
+ main: visibleActions.slice(0, 3).reverse(),
422
+ more: visibleActions.slice(3).reverse(),
423
+ }
424
+ }
425
+
426
+ watch(() => searchValue.value, (newVal) => {
427
+ if (newVal === '')
428
+ onRefresh()
429
+ })
430
+
431
+ // 配置中心->表单项展示函数
432
+ function handleFunctionStyle(funcString, record) {
433
+ if (!funcString) {
434
+ return {}
435
+ }
436
+
437
+ try {
438
+ // 同步执行函数
439
+ const obj = executeStrFunctionByContext(currInst, funcString, [record])
440
+ // 如果返回的是对象,则直接返回
441
+ if (obj && typeof obj === 'object') {
442
+ return obj
443
+ }
444
+ // 其他情况返回空对象
445
+ return {}
446
+ }
447
+ catch (error) {
448
+ console.error('Error in handleFunctionStyle:', error)
449
+ return {}
450
+ }
451
+ }
452
+
453
+ // 逆序排列主要按钮
454
+ const reversedMainActions = computed(() => {
455
+ return [...mainActions.value].reverse()
456
+ })
457
+
458
+ // 设置 Popover 的事件
459
+ function onSelectMenu(item: any, event: any) {
460
+ if (event.text === '删除') {
461
+ if (customDelete) {
462
+ emit('delete', item)
463
+ }
464
+ else {
465
+ showConfirmDialog({
466
+ title: '删除',
467
+ message:
468
+ '请确认是否删除!!!',
469
+ }).then(() => {
470
+ emit(event.func, item)
471
+ }).catch(() => {
472
+ })
473
+ }
474
+ }
475
+ else if (event.text === '修改') {
476
+ if (customEdit) {
477
+ emit('update', item)
478
+ }
479
+ else {
480
+ // 默认行为 - 导航到XForm页面
481
+ router.push({
482
+ name: 'XForm',
483
+ // params: { id: item },
484
+ query: {
485
+ groupFormItems: JSON.stringify(groupFormItems.value),
486
+ serviceName,
487
+ formData: JSON.stringify(item),
488
+ mode: '修改',
489
+ },
490
+ })
491
+ }
492
+ }
493
+ else {
494
+ emit(event.func, item)
495
+ }
496
+ }
497
+
498
+ // 抛出新增按钮的事件
499
+ function addOption() {
500
+ if (customAdd) {
501
+ emit('add')
502
+ }
503
+ else {
504
+ // 默认行为 - 导航到XForm页面
505
+ router.push({
506
+ name: 'XForm',
507
+ query: {
508
+ groupFormItems: JSON.stringify(groupFormItems.value),
509
+ serviceName,
510
+ formData: JSON.stringify({}),
511
+ mode: '新增',
512
+ },
513
+ })
514
+ }
515
+ }
516
+
517
+ // 按钮权限信息筛选
518
+ function filterButtonPermissions(btn) {
519
+ return buttonPermissions.value.find(item => item.btnName === btn)
520
+ }
521
+
522
+ // 处理按钮点击
523
+ function handleButtonClick(btn, item) {
524
+ emit(`${btn.btnIcon}`, item)
525
+ }
526
+
527
+ // 处理自定义函数
528
+ function evaluateCustomFunction(funcString: string | undefined, record: any, index: number): boolean {
529
+ try {
530
+ // 如果 customFunction 不存在,返回 true 表示正常显示
531
+ if (!funcString || funcString === '')
532
+ return true
533
+
534
+ // 匹配参数名、函数体
535
+ const innerFuncRegex = /function\s*\((\w+)\s*,\s*(\w+)\)\s*\{([\s\S]*)\}/
536
+ const matches = funcString.match(innerFuncRegex)
537
+
538
+ if (!matches)
539
+ return true
540
+
541
+ const [, param1, param2, functionBody] = matches
542
+
543
+ // eslint-disable-next-line no-new-func
544
+ const func = new Function(param1, param2, functionBody)
545
+ return func(record, index)
546
+ }
547
+ catch (error) {
548
+ console.error('Error evaluating custom function:', error)
549
+ return true
550
+ }
551
+ }
552
+
553
+ /**
554
+ * 函数描述: 传入自定义条件进行查询(传入字段必须在琉璃中配置生成查询项才会生效)
555
+ * @param {string} params - 查询条件map 例: { os_id:1 }
556
+ * // 小提示:此处传入的条件会覆盖掉fixQueryForm(固定查询条件参数)
557
+ */
558
+ function updateConditionAndRefresh(params: any) {
559
+ if (params) {
560
+ // 遍历参数,更新对应的表单值
561
+ Object.entries(params).forEach(([key, value]) => {
562
+ // 找到对应的表单项
563
+ conditionParams.value[key] = value
564
+ })
565
+ }
566
+
567
+ // 触发刷新
568
+ onRefresh()
569
+ }
570
+
571
+ // 暴露方法给父组件
572
+ defineExpose({
573
+ updateConditionAndRefresh,
574
+ onRefresh,
575
+ })
576
+
577
+ // 多选相关计算属性
578
+ const selectedItemsArray = computed(() => {
579
+ return list.value.filter(item => selectedItems.value.has(item[idKey]))
580
+ })
581
+
582
+ const hasSelectedItems = computed(() => {
583
+ return selectedItems.value.size > 0
584
+ })
585
+
586
+ // 多选相关方法
587
+ function startLongPress(item: any) {
588
+ if (!enableMultiSelect)
589
+ return
590
+
591
+ longPressTimer.value = window.setTimeout(() => {
592
+ enterMultiSelectMode(item)
593
+ }, longPressDelay)
594
+ }
595
+
596
+ function cancelLongPress() {
597
+ if (longPressTimer.value) {
598
+ clearTimeout(longPressTimer.value)
599
+ longPressTimer.value = null
600
+ }
601
+ }
602
+
603
+ function enterMultiSelectMode(item: any) {
604
+ isMultiSelectMode.value = true
605
+ selectedItems.value.clear()
606
+ const itemId = item[idKey]
607
+ selectedItems.value.add(itemId)
608
+ updateActionSheetActions()
609
+ }
610
+
611
+ function exitMultiSelectMode() {
612
+ isMultiSelectMode.value = false
613
+ selectedItems.value.clear()
614
+ showActionSheet.value = false
615
+ }
616
+
617
+ function toggleItemSelection(item: any) {
618
+ if (!isMultiSelectMode.value)
619
+ return
620
+
621
+ const itemId = item[idKey]
622
+ console.log('Toggle selection:', { itemId, currentSelectedItems: Array.from(selectedItems.value) })
623
+
624
+ if (selectedItems.value.has(itemId)) {
625
+ selectedItems.value.delete(itemId)
626
+ }
627
+ else {
628
+ selectedItems.value.add(itemId)
629
+ }
630
+
631
+ console.log('After toggle:', { selectedItems: Array.from(selectedItems.value) })
632
+
633
+ // 更新ActionSheet状态,但不退出多选模式
634
+ updateActionSheetActions()
635
+
636
+ emit('selectionChange', selectedItemsArray.value)
637
+ }
638
+
639
+ function updateActionSheetActions() {
640
+ if (multiSelectActions.length > 0) {
641
+ actionSheetActions.value = multiSelectActions.map(action => ({
642
+ name: action.name,
643
+ key: action.key || action.name,
644
+ color: action.color || '#000000',
645
+ icon: action.icon || 'records-o',
646
+ }))
647
+ }
648
+ else {
649
+ // 默认操作按钮
650
+ actionSheetActions.value = [
651
+ { name: '批量操作', key: 'batchOperation', color: '#000000', icon: 'records-o' },
652
+ ]
653
+ }
654
+
655
+ if (hasSelectedItems.value) {
656
+ showActionSheet.value = true
657
+ }
658
+ }
659
+
660
+ function handleActionSheetSelect(action: any) {
661
+ if (Array.from(selectedItems.value).length === 0 || selectedItemsArray.value.length === 0) {
662
+ showToast('请选择至少一条数据')
663
+ return
664
+ }
665
+ showActionSheet.value = false
666
+ emit('multiSelectAction', action, Array.from(selectedItems.value), selectedItemsArray.value)
667
+
668
+ // 执行操作后退出多选模式
669
+ exitMultiSelectMode()
670
+ }
671
+
672
+ function handleCardClick(item: any, event: any) {
673
+ // 如果处于多选模式,阻止默认的详情跳转
674
+ if (isMultiSelectMode.value) {
675
+ event.preventDefault()
676
+ event.stopPropagation()
677
+ return
678
+ }
679
+
680
+ // 正常点击跳转到详情
681
+ emit('toDetail', item)
682
+ }
683
+
684
+ function handleTitleClick(item: any, event: any) {
685
+ event.stopPropagation()
686
+
687
+ if (isMultiSelectMode.value) {
688
+ // 多选模式下,点击标题切换选中状态
689
+ toggleItemSelection(item)
690
+ }
691
+ else {
692
+ // 非多选模式下,正常跳转到详情
693
+ emit('toDetail', item)
694
+ }
695
+ }
696
+
697
+ function handleCheckboxChange(item: any, checked: boolean) {
698
+ if (!isMultiSelectMode.value)
699
+ return
700
+
701
+ const itemId = item[idKey]
702
+ console.log('Checkbox change:', { itemId, checked, currentSelectedItems: Array.from(selectedItems.value) })
703
+
704
+ if (checked) {
705
+ selectedItems.value.add(itemId)
706
+ }
707
+ else {
708
+ selectedItems.value.delete(itemId)
709
+ }
710
+
711
+ console.log('After change:', { selectedItems: Array.from(selectedItems.value) })
712
+
713
+ // 更新ActionSheet状态,但不退出多选模式
714
+ updateActionSheetActions()
715
+
716
+ emit('selectionChange', selectedItemsArray.value)
717
+ }
718
+ </script>
719
+
720
+ <template>
721
+ <div id="XCellList">
722
+ <VanRow class="filter-condition">
723
+ <!-- 左侧动态插槽区域 -->
724
+ <template v-for="(_, name) in slots" :key="name">
725
+ <template v-if="typeof name === 'string' && name.startsWith('search-left-')">
726
+ <div class="filter-icon-box">
727
+ <slot :name="name" />
728
+ </div>
729
+ </template>
730
+ </template>
731
+ <VanCol class="search-col">
732
+ <VanSearch
733
+ v-model="searchValue"
734
+ class="title-search"
735
+ :class="{ 'multi-select-mode': isMultiSelectMode }"
736
+ clearable
737
+ placeholder="综合查询框..."
738
+ shape="round"
739
+ @search="onRefresh"
740
+ />
741
+ </VanCol>
742
+ <!-- 新增按钮,放在查询框后、查询条件下拉按钮前 -->
743
+ <VanCol v-if="!hideAllActions && buttonState?.add && buttonState.add === true && (filterButtonPermissions('add').state === false || ((filterButtonPermissions('add').state === true && userState.f.resources.f_role_name.includes((filterButtonPermissions('add').roleStr)))))" class="add-col">
744
+ <VanButton
745
+ icon="add"
746
+ type="primary"
747
+ size="small"
748
+ class="add-action-btn"
749
+ :disabled="isMultiSelectMode"
750
+ @click="addOption"
751
+ />
752
+ </VanCol>
753
+ <!-- 右侧动态插槽区域 -->
754
+ <template v-for="(_, name) in slots" :key="name">
755
+ <template v-if="typeof name === 'string' && name.startsWith('search-right-')">
756
+ <div class="filter-icon-box">
757
+ <slot :name="name" />
758
+ </div>
759
+ </template>
760
+ </template>
761
+ <VanCol class="search-icon">
762
+ <XCellListFilter
763
+ v-model:sortord-val="sortordVal"
764
+ v-model:order-val="orderVal"
765
+ v-model:condition-params="conditionParams"
766
+ :fix-query-form="fixQueryForm"
767
+ :service-name="serviceName"
768
+ :order-list="orderList"
769
+ :form-query="formQueryList"
770
+ :button-state="buttonState"
771
+ :button-permissions="buttonPermissions"
772
+ :scan-options="scanOptions"
773
+ @on-refresh="onRefresh"
774
+ />
775
+ </VanCol>
776
+ </VanRow>
777
+ <slot name="search-after" />
778
+
779
+ <!-- 多选功能提示 -->
780
+ <div v-if="enableMultiSelect && !isMultiSelectMode" class="multi-select-tip">
781
+ <VanIcon name="info-o" />
782
+ <span>长按卡片可进入多选模式</span>
783
+ </div>
784
+
785
+ <!-- 多选模式提示 -->
786
+ <div v-if="isMultiSelectMode" class="multi-select-tip">
787
+ <VanIcon name="info-o" />
788
+ <span>多选模式 - 已选择 {{ selectedItems.size }} 项</span>
789
+ <VanButton
790
+ size="small"
791
+ type="default"
792
+ @click="exitMultiSelectMode"
793
+ >
794
+ 退出
795
+ </VanButton>
796
+ </div>
797
+
798
+ <div class="main">
799
+ <VanPullRefresh v-model="refreshing" :success-text="finishedText" head-height="70" @refresh="onRefresh">
800
+ <template v-if="!isError">
801
+ <VanList
802
+ v-model:loading="loading"
803
+ class="list_main"
804
+ :finished="finished"
805
+ :finished-text="finishedBottomText()"
806
+ :immediate-check="isInitQuery"
807
+ @load="onLoad"
808
+ >
809
+ <div
810
+ v-for="(item, index) in list"
811
+ :key="`card_${index}`"
812
+ class="card_item_main"
813
+ :class="{ 'multi-select-mode': isMultiSelectMode }"
814
+ @touchstart="startLongPress(item)"
815
+ @touchend="cancelLongPress"
816
+ @touchcancel="cancelLongPress"
817
+ @mousedown="startLongPress(item)"
818
+ @mouseup="cancelLongPress"
819
+ @mouseleave="cancelLongPress"
820
+ >
821
+ <VanRow gutter="20" class="card_item_header" align="center" @click="handleCardClick(item, $event)">
822
+ <VanCol :span="24">
823
+ <div class="title-row" :class="{ 'multi-select-title-row': isMultiSelectMode }">
824
+ <!-- 多选模式下的选择框 -->
825
+ <div v-if="isMultiSelectMode" class="selection-checkbox">
826
+ <VanCheckbox
827
+ :model-value="selectedItems.has(item[idKey])"
828
+ shape="square"
829
+ @update:model-value="(checked) => handleCheckboxChange(item, checked)"
830
+ />
831
+ </div>
832
+ <div v-for="(column) in mainColumns" :key="`main_${column.dataIndex}`" class="main-title">
833
+ <p
834
+ class="card_item_title"
835
+ :style="handleFunctionStyle(column.styleFunctionForValue, item)"
836
+ :class="{ 'selectable-title': isMultiSelectMode }"
837
+ @click="handleTitleClick(item, $event)"
838
+ >
839
+ {{ item[column.dataIndex] ?? '--' }}
840
+ </p>
841
+ </div>
842
+ <div v-for="(column) in subTitleColumns" :key="`subtitle_${column.dataIndex}`" class="sub-title">
843
+ <p
844
+ class="card_item_subtitle"
845
+ :style="handleFunctionStyle(column.styleFunctionForValue, item)"
846
+ >
847
+ <XBadge
848
+ :style="handleFunctionStyle(column.styleFunctionForValue, item)"
849
+ :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
850
+ :service-name="serviceName"
851
+ />
852
+ </p>
853
+ </div>
854
+ <div v-if="!hideAllActions" class="action-buttons">
855
+ <VanButton
856
+ v-for="btn in btnList"
857
+ :key="btn.dataIndex"
858
+ class="action-button"
859
+ :icon="btn.btnIcon"
860
+ size="small"
861
+ :disabled="isMultiSelectMode"
862
+ @click.stop="handleButtonClick(btn, item)"
863
+ />
864
+ </div>
865
+ </div>
866
+ </VanCol>
867
+ </VanRow>
868
+ <VanRow gutter="20" class="card_item_details" @click="handleCardClick(item, $event)">
869
+ <VanCol v-for="column of detailColumns" :key="`details_${column.dataIndex}`" :span="column.span">
870
+ <p>
871
+ {{ (column.showLabel === undefined || column.showLabel) ? `${column.title}: ` : '' }}
872
+ <XBadge
873
+ :style="handleFunctionStyle(column.styleFunctionForValue, item)"
874
+ :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
875
+ :service-name="serviceName"
876
+ />
877
+ </p>
878
+ </VanCol>
879
+ </VanRow>
880
+ <VanRow v-if="tagList.length > 0" gutter="20" class="tag-row" @click="handleCardClick(item, $event)">
881
+ <VanCol :span="24">
882
+ <div class="tag-container">
883
+ <div class="tag-wrapper">
884
+ <template
885
+ v-for="column of tagList"
886
+ :key="`tag_${column.dataIndex}`"
887
+ >
888
+ <VanTag
889
+ :text-color="column.tagColor"
890
+ :color="column.tagBorderColor"
891
+ size="large"
892
+ class="tag-item"
893
+ >
894
+ <div class="tag-content">
895
+ <VanIcon v-if="column.tagIcon" :name="column.tagIcon" class="tag-icon" />
896
+ <span v-if="column.showLabel === undefined || column.showLabel" class="tag-title">{{ `${column.title}: ` }}</span>
897
+ <XBadge
898
+ :dict-name="column.dictName"
899
+ :dict-value="item[column.dataIndex]"
900
+ :service-name="serviceName"
901
+ />
902
+ </div>
903
+ </VanTag>
904
+ </template>
905
+ </div>
906
+ </div>
907
+ </VanCol>
908
+ </VanRow>
909
+ <VanRow
910
+ v-if="footColumns && footColumns.length > 0"
911
+ gutter="20"
912
+ class="card_item_footer"
913
+ @click="handleCardClick(item, $event)"
914
+ >
915
+ <VanCol v-for="column of footColumns" :key="`foot_${column.dataIndex}`" :span="12">
916
+ <p>
917
+ <span :style="handleFunctionStyle(column.styleFunctionForTitle, item)">
918
+ {{ (column.showLabel === undefined || column.showLabel) ? `${column.title}: ` : '' }}
919
+ </span>
920
+ <XBadge
921
+ :style="handleFunctionStyle(column.styleFunctionForValue, item)"
922
+ :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
923
+ :service-name="serviceName"
924
+ />
925
+ </p>
926
+ </VanCol>
927
+ </VanRow>
928
+ <!-- 添加详情插槽 -->
929
+ <slot name="item-detail" :item="item" />
930
+ <VanRow v-if="!hideAllActions && allActions.length > 0" gutter="20" class="card_item_bottom">
931
+ <VanCol span="4">
932
+ <VanPopover
933
+ v-if="getActionGroups(item, index).more.length"
934
+ v-model:show="showPopover[index]"
935
+ placement="bottom-start"
936
+ :actions="getActionGroups(item, index).more"
937
+ @select="onSelectMenu(item, $event)"
938
+ >
939
+ <template #reference>
940
+ <div class="more-button" :class="{ disabled: isMultiSelectMode }">
941
+ <span>⋯</span>
942
+ </div>
943
+ </template>
944
+ </VanPopover>
945
+ </VanCol>
946
+ <VanCol span="20">
947
+ <VanRow justify="end">
948
+ <VanSpace>
949
+ <VanButton
950
+ v-for="button in getActionGroups(item, index).main"
951
+ :key="button.func"
952
+ type="primary"
953
+ size="normal"
954
+ class="action-btn"
955
+ :disabled="isMultiSelectMode"
956
+ @click="onSelectMenu(item, button)"
957
+ >
958
+ {{ button.text }}
959
+ </VanButton>
960
+ </VanSpace>
961
+ </VanRow>
962
+ </VanCol>
963
+ </VanRow>
964
+ </div>
965
+ </VanList>
966
+ </template>
967
+ <template v-else>
968
+ <LoadError />
969
+ </template>
970
+ </VanPullRefresh>
971
+ <VanBackTop />
972
+ </div>
973
+
974
+ <!-- 多选操作面板 -->
975
+ <VanActionSheet
976
+ v-model:show="showActionSheet"
977
+ :actions="actionSheetActions"
978
+ :overlay="false"
979
+ @select="handleActionSheetSelect"
980
+ />
981
+ </div>
982
+ </template>
983
+
984
+ <style scoped lang="less">
985
+ #XCellList {
986
+ height: calc(100vh - var(--van-nav-bar-height) - 20px);
987
+ display: flex;
988
+ flex-direction: column;
989
+ --van-search-padding: 3px;
990
+ --van-dropdown-menu-title-padding: 3px;
991
+ --van-text-color-2: rgb(75, 85, 99);
992
+ --van-button-normal-font-size: 13px;
993
+ .main {
994
+ flex: 1;
995
+ min-height: 0;
996
+ overflow-y: auto;
997
+ background-color: var(--van-background);
998
+ padding: var(--van-padding-base) var(--van-padding-sm);
999
+
1000
+ p {
1001
+ white-space: nowrap;
1002
+ overflow: hidden;
1003
+ text-overflow: ellipsis;
1004
+ margin: 0;
1005
+ }
1006
+
1007
+ .card_item_main {
1008
+ background-color: var(--van-background-2);
1009
+ border-radius: var(--van-radius-lg);
1010
+ margin: 0 0 var(--van-padding-xs) 0;
1011
+ padding: var(--van-padding-sm);
1012
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
1013
+ transition: all 0.3s ease;
1014
+ border: 1px solid rgba(0, 0, 0, 0.04);
1015
+ position: relative;
1016
+
1017
+ &.multi-select-mode {
1018
+ border-color: var(--van-primary-color);
1019
+ background-color: rgba(25, 137, 250, 0.02);
1020
+ }
1021
+
1022
+ .selection-checkbox {
1023
+ position: relative;
1024
+ margin-right: 8px;
1025
+ z-index: 1;
1026
+ flex-shrink: 0;
1027
+
1028
+ :deep(.van-checkbox__icon) {
1029
+ border-color: var(--van-primary-color);
1030
+ border-radius: 2px;
1031
+ }
1032
+
1033
+ :deep(.van-checkbox__icon--checked) {
1034
+ background-color: var(--van-primary-color);
1035
+ border-color: var(--van-primary-color);
1036
+ border-radius: 2px;
1037
+ }
1038
+ }
1039
+
1040
+ &:active {
1041
+ transform: scale(0.98);
1042
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02);
1043
+ }
1044
+
1045
+ .card_item_header {
1046
+ margin-bottom: var(--van-padding-base);
1047
+
1048
+ .title-row {
1049
+ display: flex;
1050
+ align-items: center;
1051
+ margin-bottom: 2px;
1052
+ width: 100%;
1053
+
1054
+ &.multi-select-title-row {
1055
+ padding-left: 0;
1056
+ }
1057
+
1058
+ .main-title {
1059
+ display: inline-flex;
1060
+ align-items: center;
1061
+ .card_item_title {
1062
+ font-size: var(--van-font-size-lg);
1063
+ font-weight: 700;
1064
+ color: var(--van-text-color);
1065
+ margin: 0;
1066
+
1067
+ &.selectable-title {
1068
+ cursor: pointer;
1069
+ padding: 4px 8px;
1070
+ border-radius: 4px;
1071
+ transition: all 0.2s ease;
1072
+
1073
+ &:hover {
1074
+ background-color: rgba(25, 137, 250, 0.1);
1075
+ }
1076
+
1077
+ &:active {
1078
+ background-color: rgba(25, 137, 250, 0.2);
1079
+ }
1080
+ }
1081
+ }
1082
+ }
1083
+
1084
+ .sub-title {
1085
+ display: inline-flex;
1086
+ align-items: center;
1087
+ margin-left: 8px;
1088
+ .card_item_subtitle {
1089
+ font-size: var(--van-font-size-md);
1090
+ color: var(--van-text-color-2);
1091
+ }
1092
+ }
1093
+
1094
+ .action-buttons {
1095
+ display: flex;
1096
+ align-items: center;
1097
+ margin-left: auto;
1098
+ .action-button {
1099
+ margin-left: 6px;
1100
+ width: 32px;
1101
+ height: 32px;
1102
+ padding: 0;
1103
+ border: none;
1104
+ color: var(--van-primary-color);
1105
+ background-color: rgba(25, 137, 250, 0.1);
1106
+ border-radius: 6px;
1107
+ font-size: var(--van-font-size-lg);
1108
+ display: flex;
1109
+ align-items: center;
1110
+ justify-content: center;
1111
+ transition: all 0.2s ease;
1112
+ &:active {
1113
+ opacity: 0.7;
1114
+ background-color: rgba(25, 137, 250, 0.2);
1115
+ transform: scale(0.95);
1116
+ }
1117
+ }
1118
+ }
1119
+ }
1120
+ }
1121
+
1122
+ .tag-row {
1123
+ margin-bottom: var(--van-padding-base);
1124
+ }
1125
+
1126
+ .tag-container {
1127
+ width: 100%;
1128
+ .tag-wrapper {
1129
+ display: flex;
1130
+ flex-wrap: wrap;
1131
+ padding: 0 !important;
1132
+ align-items: center;
1133
+ width: 100%;
1134
+ margin: 0 -4px;
1135
+
1136
+ .tag-item {
1137
+ width: auto;
1138
+ font-size: var(--van-font-size-md);
1139
+ margin: 4px 4px;
1140
+ :deep(.van-tag) {
1141
+ width: fit-content;
1142
+ display: inline-flex;
1143
+ align-items: center;
1144
+ padding: 2px 8px;
1145
+ border-radius: 4px;
1146
+ }
1147
+ .tag-content {
1148
+ display: flex;
1149
+ align-items: center;
1150
+ //white-space: nowrap;
1151
+ }
1152
+ .tag-icon {
1153
+ margin-right: 4px;
1154
+ font-size: 14px;
1155
+ display: inline-flex;
1156
+ align-items: center;
1157
+ }
1158
+ .tag-title {
1159
+ font-weight: normal;
1160
+ }
1161
+ }
1162
+ }
1163
+ }
1164
+
1165
+ .card_item_details {
1166
+ font-size: var(--van-font-size-md);
1167
+ color: var(--van-text-color-2);
1168
+ padding: 4px 0;
1169
+
1170
+ .van-col {
1171
+ margin-bottom: 4px;
1172
+ p {
1173
+ display: flex;
1174
+ align-items: center;
1175
+ gap: 4px;
1176
+
1177
+ span {
1178
+ flex: 1;
1179
+ min-width: 0;
1180
+ overflow: hidden;
1181
+ text-overflow: ellipsis;
1182
+ white-space: nowrap;
1183
+ }
1184
+
1185
+ :deep(.van-badge) {
1186
+ flex-shrink: 0;
1187
+ }
1188
+ }
1189
+ }
1190
+ }
1191
+
1192
+ .card_item_footer {
1193
+ font-size: var(--van-font-size-md);
1194
+ color: var(--van-text-color-2);
1195
+ padding: 4px 0;
1196
+ .van-col:last-child {
1197
+ text-align: right;
1198
+ }
1199
+ .van-col:first-child {
1200
+ text-align: left;
1201
+ }
1202
+ }
1203
+
1204
+ .card_item_bottom {
1205
+ margin-top: 8px;
1206
+ padding-top: 10px;
1207
+ border-top: 1px solid rgba(0, 0, 0, 0.06);
1208
+
1209
+ .more-button {
1210
+ width: 37px;
1211
+ height: 37px;
1212
+ display: flex;
1213
+ align-items: center;
1214
+ justify-content: center;
1215
+ color: var(--van-text-color-2);
1216
+ cursor: pointer;
1217
+ font-size: var(--van-font-size-lg);
1218
+ background-color: var(--van-background);
1219
+ border-radius: 6px;
1220
+ transition: all 0.2s ease;
1221
+ border: 1px solid rgba(0, 0, 0, 0.06);
1222
+ margin: 2px 0 0 0;
1223
+ span {
1224
+ line-height: 1;
1225
+ margin-top: -2px;
1226
+ }
1227
+ &:active {
1228
+ opacity: 0.7;
1229
+ background-color: var(--van-background-2);
1230
+ transform: scale(0.95);
1231
+ }
1232
+
1233
+ &.disabled {
1234
+ opacity: 0.5;
1235
+ cursor: not-allowed;
1236
+ pointer-events: none;
1237
+ }
1238
+ }
1239
+
1240
+ .action-btn {
1241
+ --van-button-primary-border-color: #1890ff;
1242
+ --van-button-primary-background: #1890ff;
1243
+ min-width: 76px;
1244
+ height: 40px;
1245
+ border-radius: 10px;
1246
+ padding: 0 14px;
1247
+ // font-size: var(--van-font-size-md);
1248
+ transition: all 0.2s ease;
1249
+ &:active {
1250
+ transform: scale(0.95);
1251
+ }
1252
+ }
1253
+ }
1254
+ }
1255
+ }
1256
+
1257
+ :deep(.van-search__field) {
1258
+ padding: 0 !important;
1259
+ height: 100%;
1260
+ }
1261
+
1262
+ :deep(.van-popup),
1263
+ :deep(.van-popover) {
1264
+ border: none !important;
1265
+ outline: none !important;
1266
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
1267
+ background-color: #ffffff !important;
1268
+
1269
+ &::before,
1270
+ &::after {
1271
+ border: none !important;
1272
+ outline: none !important;
1273
+ }
1274
+
1275
+ .van-popover__content {
1276
+ background-color: #ffffff !important;
1277
+ border: none !important;
1278
+ outline: none !important;
1279
+ border-radius: 8px !important;
1280
+ }
1281
+
1282
+ .van-popover__arrow,
1283
+ .van-popover_arrow {
1284
+ background-color: #ffffff !important;
1285
+ border: none !important;
1286
+ outline: none !important;
1287
+ }
1288
+
1289
+ .van-popover__action {
1290
+ color: #333 !important;
1291
+
1292
+ &:hover {
1293
+ background-color: #f5f5f5 !important;
1294
+ }
1295
+ }
1296
+ }
1297
+
1298
+ .filter-condition {
1299
+ display: flex;
1300
+ align-items: center;
1301
+ padding: 8px 12px;
1302
+ background-color: #fff;
1303
+ gap: 1px;
1304
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02);
1305
+ position: sticky;
1306
+ top: 0;
1307
+ z-index: 100;
1308
+
1309
+ .add-col {
1310
+ display: flex;
1311
+ align-items: center;
1312
+ margin: 0 4px;
1313
+ .add-action-btn {
1314
+ width: 40px;
1315
+ height: 40px;
1316
+ border-radius: 10px;
1317
+ background-color: var(--van-background);
1318
+ color: #323233;
1319
+ border: 1px solid rgba(0, 0, 0, 0.06);
1320
+ display: flex;
1321
+ align-items: center;
1322
+ justify-content: center;
1323
+ font-size: 16px;
1324
+ padding: 0;
1325
+ transition: all 0.2s;
1326
+ &:hover,
1327
+ &:active {
1328
+ opacity: 0.85;
1329
+ background-color: rgba(25, 137, 250, 0.08);
1330
+ border-color: var(--van-primary-color);
1331
+ color: var(--van-primary-color);
1332
+ }
1333
+ }
1334
+ }
1335
+ :deep(.van-search) {
1336
+ width: 100%;
1337
+ padding: var(--van-search-padding);
1338
+ background-color: transparent;
1339
+
1340
+ &.multi-select-mode {
1341
+ opacity: 0.8;
1342
+ }
1343
+ }
1344
+ :deep(.van-search__content) {
1345
+ border-radius: 8px;
1346
+ background-color: var(--van-background);
1347
+ padding: 4px 12px;
1348
+ border: 1px solid rgba(0, 0, 0, 0.01);
1349
+ height: 40px;
1350
+ }
1351
+ :deep(.van-field__left-icon) {
1352
+ color: var(--van-text-color-2);
1353
+ }
1354
+ :deep(.van-cell) {
1355
+ background-color: transparent;
1356
+ }
1357
+ :deep(.van-field__control::placeholder) {
1358
+ color: var(--van-text-color-2);
1359
+ font-size: 14px;
1360
+ }
1361
+ .van-col {
1362
+ display: flex;
1363
+ align-items: center;
1364
+ &.search-col {
1365
+ flex: 1;
1366
+ min-width: 0;
1367
+ }
1368
+ &.search-icon {
1369
+ flex-shrink: 0;
1370
+ padding: 0;
1371
+ }
1372
+ }
1373
+ .filter-icon-box {
1374
+ display: flex;
1375
+ align-items: center;
1376
+ justify-content: center;
1377
+ width: 36px;
1378
+ height: 36px;
1379
+ border-radius: 8px;
1380
+ background-color: var(--van-background);
1381
+ cursor: pointer;
1382
+ position: relative;
1383
+ z-index: 1;
1384
+ border: 1px solid rgba(0, 0, 0, 0.06);
1385
+ transition: all 0.2s ease;
1386
+ &:active {
1387
+ opacity: 0.7;
1388
+ transform: scale(0.95);
1389
+ }
1390
+ }
1391
+ }
1392
+
1393
+ .multi-select-hint {
1394
+ display: flex;
1395
+ align-items: center;
1396
+ padding: 8px 12px;
1397
+ background-color: rgba(0, 0, 0, 0.04);
1398
+ border: 1px solid rgba(0, 0, 0, 0.08);
1399
+ border-radius: 6px;
1400
+ margin: 8px 12px;
1401
+ font-size: var(--van-font-size-sm);
1402
+ color: var(--van-text-color-2);
1403
+
1404
+ .van-icon {
1405
+ margin-right: 6px;
1406
+ color: var(--van-text-color-3);
1407
+ }
1408
+
1409
+ span {
1410
+ flex: 1;
1411
+ }
1412
+ }
1413
+
1414
+ .multi-select-tip {
1415
+ display: flex;
1416
+ align-items: center;
1417
+ justify-content: space-between;
1418
+ padding: 8px 12px;
1419
+ background-color: rgba(25, 137, 250, 0.1);
1420
+ border: 1px solid rgba(25, 137, 250, 0.2);
1421
+ border-radius: 6px;
1422
+ margin: 8px 12px;
1423
+ font-size: var(--van-font-size-sm);
1424
+ color: var(--van-primary-color);
1425
+
1426
+ .van-icon {
1427
+ margin-right: 6px;
1428
+ }
1429
+
1430
+ span {
1431
+ flex: 1;
1432
+ margin-right: 8px;
1433
+ }
1434
+ }
1435
+ }
1436
+ </style>