af-mobile-client-vue3 1.3.30 → 1.3.32

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 (284) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.cursorrules +60 -60
  3. package/.editorconfig +9 -9
  4. package/.env +10 -10
  5. package/.env.development +1 -1
  6. package/.env.production +1 -1
  7. package/.node-version +1 -1
  8. package/.vscode/extensions.json +12 -12
  9. package/.vscode/settings.json +68 -66
  10. package/CLAUDE.md +218 -189
  11. package/README.md +182 -182
  12. package/af-example-mobile-vue-web.iml +9 -9
  13. package/build/vite/index.ts +98 -98
  14. package/build/vite/optimize.ts +34 -34
  15. package/build/vite/vconsole.ts +47 -47
  16. package/commitlint.config.ts +32 -32
  17. package/compress.js +36 -36
  18. package/eslint.config.ts +31 -30
  19. package/index.html +23 -23
  20. package/mock/data.ts +20 -20
  21. package/mock/index.ts +7 -7
  22. package/mock/modules/prose.mock.ts +13 -13
  23. package/mock/modules/user.mock.ts +95 -152
  24. package/mock/util.ts +19 -19
  25. package/netlify.toml +12 -12
  26. package/package.json +135 -114
  27. package/postcss.config.ts +27 -27
  28. package/public/favicon.svg +4 -4
  29. package/public/safari-pinned-tab.svg +4 -4
  30. package/scripts/verifyCommit.js +19 -19
  31. package/src/App.vue +79 -79
  32. package/src/api/auth/index.ts +77 -0
  33. package/src/api/auth/types.ts +200 -0
  34. package/src/api/mock/index.ts +30 -30
  35. package/src/api/user/index.ts +40 -40
  36. package/src/assets/img/user/login/background-shadow-1.svg +20 -20
  37. package/src/assets/img/user/login/logo-background.svg +20 -20
  38. package/src/bootstrap.ts +26 -26
  39. package/src/components/core/BeautifulLoading/index.vue +52 -52
  40. package/src/components/core/ImageUploader/index.vue +251 -251
  41. package/src/components/core/NavBar/index.vue +53 -53
  42. package/src/components/core/Tabbar/index.vue +32 -32
  43. package/src/components/core/Uploader/index.vue +124 -124
  44. package/src/components/core/XGridDropOption/index.vue +154 -154
  45. package/src/components/core/XMultiSelect/index.vue +183 -183
  46. package/src/components/core/XSelect/index.vue +149 -149
  47. package/src/components/data/CardContainer/CardContainer.vue +118 -118
  48. package/src/components/data/CardContainer/CardHeader.vue +99 -99
  49. package/src/components/data/InfoDisplay/index.vue +132 -132
  50. package/src/components/data/UserDetail/api.ts +24 -24
  51. package/src/components/data/UserDetail/index.vue +620 -620
  52. package/src/components/data/UserDetail/recordEntries.ts +159 -159
  53. package/src/components/data/UserDetail/types.ts +26 -26
  54. package/src/components/data/XBadge/index.vue +82 -82
  55. package/src/components/data/XCellDetail/index.vue +105 -105
  56. package/src/components/data/XCellList/XCellList.md +432 -432
  57. package/src/components/data/XCellList/index.vue +1436 -1436
  58. package/src/components/data/XCellListFilter/QrScanner/index.vue +207 -207
  59. package/src/components/data/XCellListFilter/QrScanner/startScanAnimation.ts +53 -53
  60. package/src/components/data/XCellListFilter/VpnRecognition/index.vue +119 -119
  61. package/src/components/data/XCellListFilter/index.vue +705 -705
  62. package/src/components/data/XForm/index.vue +659 -659
  63. package/src/components/data/XFormGroup/doc/DeviceForm.vue +122 -122
  64. package/src/components/data/XFormGroup/doc/FormGroupDemo.vue +56 -56
  65. package/src/components/data/XFormGroup/doc/README.md +286 -286
  66. package/src/components/data/XFormGroup/doc/UserForm.vue +102 -102
  67. package/src/components/data/XFormGroup/index.vue +240 -240
  68. package/src/components/data/XFormItem/index.vue +1310 -1310
  69. package/src/components/data/XOlMap/README.md +227 -227
  70. package/src/components/data/XOlMap/XLocationPicker/index.vue +226 -226
  71. package/src/components/data/XOlMap/index.vue +1490 -1490
  72. package/src/components/data/XOlMap/types.ts +149 -149
  73. package/src/components/data/XOlMap/utils/{wgs84ToGcj02.js → wgs84ToGcj02.ts} +171 -154
  74. package/src/components/data/XReportForm/DateTimeSecondsPicker.vue +208 -208
  75. package/src/components/data/XReportForm/XReportFormJsonRender.vue +220 -220
  76. package/src/components/data/XReportForm/index.vue +1393 -1393
  77. package/src/components/data/XReportGrid/XAddReport/XAddReport.vue +198 -198
  78. package/src/components/data/XReportGrid/XAddReport/index.js +3 -3
  79. package/src/components/data/XReportGrid/XAddReport/index.md +53 -53
  80. package/src/components/data/XReportGrid/XAddReport/index.ts +10 -10
  81. package/src/components/data/XReportGrid/XReport.vue +960 -960
  82. package/src/components/data/XReportGrid/XReportDemo.vue +33 -33
  83. package/src/components/data/XReportGrid/XReportDesign.vue +597 -597
  84. package/src/components/data/XReportGrid/XReportDrawer/XReportDrawer.vue +148 -148
  85. package/src/components/data/XReportGrid/XReportDrawer/index.js +3 -3
  86. package/src/components/data/XReportGrid/XReportDrawer/index.ts +10 -10
  87. package/src/components/data/XReportGrid/XReportJsonRender.vue +399 -399
  88. package/src/components/data/XReportGrid/XReportTrGroup.vue +592 -592
  89. package/src/components/data/XReportGrid/index.md +46 -46
  90. package/src/components/data/XReportGrid/print.js +184 -184
  91. package/src/components/data/XSignature/index.vue +284 -284
  92. package/src/components/data/XTag/index.vue +10 -10
  93. package/src/components/layout/NormalDataLayout/index.vue +69 -69
  94. package/src/components/layout/TabBarLayout/index.vue +40 -40
  95. package/src/composables/dark.ts +5 -5
  96. package/src/config/routes.ts +9 -9
  97. package/src/constants/index.ts +2 -2
  98. package/src/enums/requestEnum.ts +25 -25
  99. package/src/expression/ExpressionRunner.ts +28 -28
  100. package/src/expression/TestExpression.ts +510 -510
  101. package/src/expression/core/Delegate.ts +116 -116
  102. package/src/expression/core/Expression.ts +1359 -1359
  103. package/src/expression/core/Program.ts +985 -985
  104. package/src/expression/core/Token.ts +29 -29
  105. package/src/expression/enums/ExpressionType.ts +81 -81
  106. package/src/expression/enums/TokenType.ts +11 -11
  107. package/src/expression/exception/BreakWayException.ts +2 -2
  108. package/src/expression/exception/ContinueWayException.ts +2 -2
  109. package/src/expression/exception/ExpressionException.ts +29 -29
  110. package/src/expression/exception/ReturnWayException.ts +14 -14
  111. package/src/expression/exception/ServiceException.ts +22 -22
  112. package/src/expression/instances/JSONArray.ts +52 -52
  113. package/src/expression/instances/JSONObject.ts +118 -118
  114. package/src/expression/instances/LogicConsole.ts +31 -31
  115. package/src/font-style/font.css +4 -4
  116. package/src/hooks/useBoolean.ts +26 -26
  117. package/src/hooks/useCommon.ts +9 -9
  118. package/src/hooks/useLoading.ts +16 -16
  119. package/src/hooks/useLogin.ts +97 -97
  120. package/src/icons/svg/check-in.svg +32 -32
  121. package/src/icons/svg/dark.svg +4 -4
  122. package/src/icons/svg/github.svg +4 -4
  123. package/src/icons/svg/light.svg +4 -4
  124. package/src/icons/svg/link.svg +4 -4
  125. package/src/icons/svgo.yml +22 -22
  126. package/src/index.ts +4 -0
  127. package/src/layout/GridView/index.vue +16 -16
  128. package/src/layout/PageLayout.vue +9 -9
  129. package/src/layout/SingleLayout.vue +9 -9
  130. package/src/locales/en-US.json +128 -128
  131. package/src/locales/zh-CN.json +128 -128
  132. package/src/logic/LogicRunner.ts +67 -67
  133. package/src/logic/TestLogic.ts +13 -13
  134. package/src/logic/plugins/common/DateTools.ts +35 -35
  135. package/src/logic/plugins/common/VueTools.ts +30 -30
  136. package/src/logic/plugins/index.ts +7 -7
  137. package/src/main.ts +44 -44
  138. package/src/plugins/AppData.ts +38 -38
  139. package/src/plugins/GetLoginInfoService.ts +10 -10
  140. package/src/plugins/collectIcons.ts +10 -10
  141. package/src/plugins/index.ts +11 -11
  142. package/src/router/README.md +8 -8
  143. package/src/router/external-routes.ts +60 -0
  144. package/src/router/guards.ts +131 -59
  145. package/src/router/index.ts +35 -35
  146. package/src/router/invoiceRoutes.ts +33 -33
  147. package/src/router/routes.ts +426 -347
  148. package/src/services/api/Login.ts +6 -6
  149. package/src/services/api/common.ts +109 -109
  150. package/src/services/api/index.ts +7 -7
  151. package/src/services/api/manage.ts +8 -8
  152. package/src/services/api/search.ts +16 -16
  153. package/src/services/api/user.ts +17 -17
  154. package/src/services/restTools.ts +56 -56
  155. package/src/services/v3Api.ts +147 -147
  156. package/src/stores/index.ts +13 -13
  157. package/src/stores/modules/counter.ts +19 -19
  158. package/src/stores/modules/homeApp.ts +55 -55
  159. package/src/stores/modules/routeCache.ts +22 -23
  160. package/src/stores/modules/setting.ts +87 -87
  161. package/src/stores/modules/user.ts +326 -235
  162. package/src/stores/mutation-type.ts +12 -7
  163. package/src/styles/app.less +36 -36
  164. package/src/styles/login.less +109 -109
  165. package/src/styles/var.less +25 -25
  166. package/src/types/auth.ts +85 -0
  167. package/src/types/env.d.ts +16 -16
  168. package/src/types/platform.ts +194 -0
  169. package/src/types/settings.ts +1 -1
  170. package/src/types/vue-router.d.ts +13 -9
  171. package/src/utils/Storage.ts +124 -124
  172. package/src/utils/authority-utils.ts +84 -84
  173. package/src/utils/common.ts +41 -41
  174. package/src/utils/crypto.ts +39 -39
  175. package/src/utils/dataUtil.ts +42 -42
  176. package/src/utils/dictUtil.ts +52 -52
  177. package/src/utils/http/index.ts +201 -199
  178. package/src/utils/i18n.ts +72 -72
  179. package/src/utils/indexedDB.ts +195 -195
  180. package/src/utils/inline-px-to-vw.ts +28 -28
  181. package/src/utils/mobileUtil.ts +33 -34
  182. package/src/utils/platform-auth.ts +134 -0
  183. package/src/utils/progress.ts +19 -19
  184. package/src/utils/routerUtil.ts +271 -271
  185. package/src/utils/runEvalFunction.ts +13 -13
  186. package/src/utils/secureStorage.ts +70 -71
  187. package/src/utils/set-page-title.ts +5 -5
  188. package/src/utils/validate.ts +6 -6
  189. package/src/views/chat/index.vue +153 -153
  190. package/src/views/common/Forbidden.vue +97 -0
  191. package/src/views/common/LoadError.vue +63 -63
  192. package/src/views/common/NotFound.vue +67 -67
  193. package/src/views/component/EvaluateRecordView/index.vue +40 -40
  194. package/src/views/component/IconifyView/index.vue +504 -504
  195. package/src/views/component/UserDetailView/UserDetailPage.vue +77 -77
  196. package/src/views/component/UserDetailView/index.vue +234 -234
  197. package/src/views/component/XCellDetailView/index.vue +217 -217
  198. package/src/views/component/XCellListView/index.vue +108 -129
  199. package/src/views/component/XFormAppraiseView/index.vue +174 -174
  200. package/src/views/component/XFormGroupView/index.vue +78 -82
  201. package/src/views/component/XFormView/index.vue +27 -27
  202. package/src/views/component/XOlMapView/XLocationPicker/index.vue +118 -118
  203. package/src/views/component/XOlMapView/index.vue +434 -434
  204. package/src/views/component/XOlMapView/testData.ts +64 -64
  205. package/src/views/component/XReportFormIframeView/index.vue +47 -47
  206. package/src/views/component/XReportFormView/index.vue +13 -13
  207. package/src/views/component/XReportGridView/index.vue +17 -17
  208. package/src/views/component/XRequestView/index.vue +234 -234
  209. package/src/views/component/XSignatureView/index.vue +50 -50
  210. package/src/views/component/index.vue +181 -181
  211. package/src/views/component/menu.vue +117 -117
  212. package/src/views/component/notice.vue +46 -46
  213. package/src/views/component/topNav.vue +36 -36
  214. package/src/views/external/index.vue +152 -0
  215. package/src/views/invoiceShow/index.vue +61 -61
  216. package/src/views/loading/AuthLoading.vue +345 -0
  217. package/src/views/user/login/ForgetPasswordForm.vue +94 -94
  218. package/src/views/user/login/LoginForm.vue +350 -347
  219. package/src/views/user/login/LoginTitle.vue +76 -76
  220. package/src/views/user/login/LoginWave.vue +109 -109
  221. package/src/views/user/login/index.vue +22 -22
  222. package/src/views/user/my/comm/ModifyPassword.vue +346 -346
  223. package/src/views/user/my/index.vue +507 -507
  224. package/src/views/user/register/index.vue +952 -952
  225. package/src/views/userRecords/AbnormalAlarmRecords.vue +21 -21
  226. package/src/views/userRecords/CardReplacementRecords.vue +21 -21
  227. package/src/views/userRecords/ChangeRecords.vue +19 -19
  228. package/src/views/userRecords/CommandViewRecords.vue +20 -20
  229. package/src/views/userRecords/GasCompensationRecords.vue +20 -20
  230. package/src/views/userRecords/InstrumentCollectionRecords.vue +21 -21
  231. package/src/views/userRecords/MeterRecords.vue +20 -20
  232. package/src/views/userRecords/OperateRecords.vue +51 -51
  233. package/src/views/userRecords/OtherChargeRecords.vue +19 -19
  234. package/src/views/userRecords/PaymentRecords.vue +28 -28
  235. package/src/views/userRecords/PriceAdjustmentRecords.vue +19 -19
  236. package/src/views/userRecords/ReplacementRecords.vue +19 -19
  237. package/src/views/userRecords/SafetyRecords.vue +19 -19
  238. package/src/views/userRecords/TransactionRecords.vue +21 -21
  239. package/src/views/userRecords/TransferRecords.vue +19 -19
  240. package/src/views/userRecords/operateRecordDetail/index.vue +316 -316
  241. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AddUserDetail.vue +124 -124
  242. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AdvanceDeliveryDetail.vue +88 -88
  243. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AutoAccountsCancelDetail.vue +205 -205
  244. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AutoAccountsDetail.vue +192 -192
  245. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BankDkDetail.vue +192 -192
  246. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BankPayDetail.vue +192 -192
  247. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BlacklistDetail.vue +153 -153
  248. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CancellationDetail.vue +101 -101
  249. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardMeterCenterCancelDetail.vue +127 -127
  250. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardMeterCenterDetail.vue +153 -153
  251. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardOverUserDetail.vue +153 -153
  252. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ChangeMeterCancelDetail.vue +166 -166
  253. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ChangeMeterDetail.vue +205 -205
  254. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/DisableManageDetail.vue +127 -127
  255. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/EnableManageDetail.vue +114 -114
  256. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/FaZheChangeDetail.vue +124 -124
  257. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/FeeDeductionDetail.vue +153 -153
  258. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/GasPriceChangeDetail.vue +126 -126
  259. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/InputtorChangeDetail.vue +126 -126
  260. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotMeterCenterCancelDetail.vue +114 -114
  261. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotMeterCenterDetail.vue +127 -127
  262. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotOpenDetail.vue +88 -88
  263. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineCardDetail.vue +101 -101
  264. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineMeterCenterCancelDetail.vue +218 -218
  265. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineMeterCenterDetail.vue +153 -153
  266. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OffGasAddGasDetail.vue +140 -140
  267. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OtherChargeCancelDetail.vue +127 -127
  268. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OtherChargeDetail.vue +114 -114
  269. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OverUserChangeDetail.vue +127 -127
  270. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReBillDetail.vue +127 -127
  271. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/RefundDetail.vue +114 -114
  272. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReplaceCardManageCancelDetail.vue +127 -127
  273. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReplaceCardManageDetail.vue +114 -114
  274. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/SaleCardGasDetail.vue +140 -140
  275. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/TransferManageCancelDetail.vue +152 -152
  276. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/TransferManageDetail.vue +178 -178
  277. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/UserChangeDetail.vue +123 -123
  278. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/WechatPayDetail.vue +192 -192
  279. package/src/views/userRecords/types.ts +66 -66
  280. package/tsconfig.json +39 -39
  281. package/uno.config.ts +82 -82
  282. package/vite.config.ts +119 -118
  283. package/src/router/types.ts +0 -7
  284. package/src/utils/wechatUtil.ts +0 -9
@@ -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>