af-mobile-client-vue3 1.3.11 → 1.3.13

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 (268) 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 +66 -66
  10. package/CLAUDE.md +189 -184
  11. package/README.md +182 -181
  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 +30 -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 +152 -152
  24. package/mock/util.ts +19 -19
  25. package/netlify.toml +12 -12
  26. package/package.json +114 -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/mock/index.ts +30 -30
  33. package/src/api/user/index.ts +40 -40
  34. package/src/assets/img/user/login/background-shadow-1.svg +20 -20
  35. package/src/assets/img/user/login/logo-background.svg +20 -20
  36. package/src/bootstrap.ts +26 -26
  37. package/src/components/core/BeautifulLoading/index.vue +52 -52
  38. package/src/components/core/ImageUploader/index.vue +244 -244
  39. package/src/components/core/NavBar/index.vue +53 -53
  40. package/src/components/core/Tabbar/index.vue +32 -32
  41. package/src/components/core/Uploader/index.vue +124 -124
  42. package/src/components/core/XGridDropOption/index.vue +154 -156
  43. package/src/components/core/XMultiSelect/index.vue +183 -183
  44. package/src/components/core/XSelect/index.vue +149 -149
  45. package/src/components/data/InfoDisplay/index.vue +132 -0
  46. package/src/components/data/UserDetail/api.ts +24 -0
  47. package/src/components/data/UserDetail/index.vue +539 -0
  48. package/src/components/data/UserDetail/recordEntries.ts +159 -0
  49. package/src/components/data/UserDetail/types.ts +26 -0
  50. package/src/components/data/XBadge/index.vue +82 -82
  51. package/src/components/data/XCellDetail/index.vue +105 -105
  52. package/src/components/data/XCellList/XCellList.md +313 -313
  53. package/src/components/data/XCellList/index.vue +1075 -1075
  54. package/src/components/data/XCellListFilter/QrScanner/index.vue +207 -207
  55. package/src/components/data/XCellListFilter/QrScanner/startScanAnimation.ts +53 -53
  56. package/src/components/data/XCellListFilter/VpnRecognition/index.vue +119 -119
  57. package/src/components/data/XCellListFilter/index.vue +705 -705
  58. package/src/components/data/XForm/index.vue +659 -659
  59. package/src/components/data/XFormGroup/doc/DeviceForm.vue +121 -121
  60. package/src/components/data/XFormGroup/doc/FormGroupDemo.vue +56 -56
  61. package/src/components/data/XFormGroup/doc/README.md +286 -273
  62. package/src/components/data/XFormGroup/doc/UserForm.vue +101 -101
  63. package/src/components/data/XFormGroup/index.vue +240 -240
  64. package/src/components/data/XFormItem/index.vue +1310 -1310
  65. package/src/components/data/XOlMap/README.md +227 -227
  66. package/src/components/data/XOlMap/XLocationPicker/index.vue +226 -225
  67. package/src/components/data/XOlMap/index.vue +1490 -1490
  68. package/src/components/data/XOlMap/types.ts +149 -149
  69. package/src/components/data/XOlMap/utils/wgs84ToGcj02.js +154 -154
  70. package/src/components/data/XReportForm/DateTimeSecondsPicker.vue +208 -208
  71. package/src/components/data/XReportForm/XReportFormJsonRender.vue +220 -220
  72. package/src/components/data/XReportForm/index.vue +1393 -1393
  73. package/src/components/data/XReportGrid/XAddReport/XAddReport.vue +198 -198
  74. package/src/components/data/XReportGrid/XAddReport/index.js +3 -3
  75. package/src/components/data/XReportGrid/XAddReport/index.md +53 -52
  76. package/src/components/data/XReportGrid/XAddReport/index.ts +10 -10
  77. package/src/components/data/XReportGrid/XReport.vue +960 -960
  78. package/src/components/data/XReportGrid/XReportDesign.vue +597 -597
  79. package/src/components/data/XReportGrid/XReportDrawer/XReportDrawer.vue +148 -148
  80. package/src/components/data/XReportGrid/XReportDrawer/index.js +3 -3
  81. package/src/components/data/XReportGrid/XReportDrawer/index.ts +10 -10
  82. package/src/components/data/XReportGrid/XReportJsonRender.vue +399 -399
  83. package/src/components/data/XReportGrid/XReportTrGroup.vue +592 -592
  84. package/src/components/data/XReportGrid/index.md +46 -42
  85. package/src/components/data/XSignature/index.vue +284 -285
  86. package/src/components/data/XTag/index.vue +10 -10
  87. package/src/components/layout/NormalDataLayout/index.vue +69 -69
  88. package/src/components/layout/TabBarLayout/index.vue +40 -40
  89. package/src/composables/dark.ts +5 -5
  90. package/src/config/routes.ts +9 -9
  91. package/src/constants/index.ts +2 -2
  92. package/src/enums/requestEnum.ts +25 -25
  93. package/src/expression/ExpressionRunner.ts +28 -28
  94. package/src/expression/TestExpression.ts +510 -510
  95. package/src/expression/core/Delegate.ts +116 -116
  96. package/src/expression/core/Expression.ts +1359 -1359
  97. package/src/expression/core/Program.ts +985 -985
  98. package/src/expression/core/Token.ts +29 -29
  99. package/src/expression/enums/ExpressionType.ts +81 -81
  100. package/src/expression/enums/TokenType.ts +11 -11
  101. package/src/expression/exception/BreakWayException.ts +2 -2
  102. package/src/expression/exception/ContinueWayException.ts +2 -2
  103. package/src/expression/exception/ExpressionException.ts +29 -29
  104. package/src/expression/exception/ReturnWayException.ts +14 -14
  105. package/src/expression/exception/ServiceException.ts +22 -22
  106. package/src/expression/instances/JSONArray.ts +52 -52
  107. package/src/expression/instances/JSONObject.ts +118 -118
  108. package/src/expression/instances/LogicConsole.ts +31 -31
  109. package/src/font-style/font.css +4 -4
  110. package/src/hooks/useBoolean.ts +26 -0
  111. package/src/hooks/useCommon.ts +9 -9
  112. package/src/hooks/useLogin.ts +97 -97
  113. package/src/icons/svg/check-in.svg +32 -32
  114. package/src/icons/svg/dark.svg +4 -4
  115. package/src/icons/svg/github.svg +4 -4
  116. package/src/icons/svg/light.svg +4 -4
  117. package/src/icons/svg/link.svg +4 -4
  118. package/src/icons/svgo.yml +22 -22
  119. package/src/layout/GridView/index.vue +16 -16
  120. package/src/layout/PageLayout.vue +9 -9
  121. package/src/layout/SingleLayout.vue +9 -9
  122. package/src/locales/en-US.json +128 -128
  123. package/src/locales/zh-CN.json +128 -128
  124. package/src/logic/LogicRunner.ts +67 -67
  125. package/src/logic/TestLogic.ts +13 -13
  126. package/src/logic/plugins/common/DateTools.ts +35 -35
  127. package/src/logic/plugins/common/VueTools.ts +30 -30
  128. package/src/logic/plugins/index.ts +7 -7
  129. package/src/main.ts +44 -44
  130. package/src/plugins/AppData.ts +38 -38
  131. package/src/plugins/GetLoginInfoService.ts +10 -10
  132. package/src/plugins/collectIcons.ts +10 -0
  133. package/src/plugins/index.ts +11 -11
  134. package/src/router/README.md +8 -8
  135. package/src/router/guards.ts +59 -59
  136. package/src/router/index.ts +35 -35
  137. package/src/router/invoiceRoutes.ts +33 -33
  138. package/src/router/routes.ts +341 -171
  139. package/src/router/types.ts +7 -7
  140. package/src/services/api/Login.ts +6 -6
  141. package/src/services/api/common.ts +109 -109
  142. package/src/services/api/index.ts +7 -7
  143. package/src/services/api/manage.ts +8 -8
  144. package/src/services/api/search.ts +16 -16
  145. package/src/services/api/user.ts +17 -17
  146. package/src/services/restTools.ts +56 -56
  147. package/src/services/v3Api.ts +147 -147
  148. package/src/stores/index.ts +11 -11
  149. package/src/stores/modules/counter.ts +19 -19
  150. package/src/stores/modules/routeCache.ts +23 -23
  151. package/src/stores/modules/setting.ts +76 -76
  152. package/src/stores/modules/user.ts +235 -235
  153. package/src/stores/mutation-type.ts +7 -7
  154. package/src/styles/app.less +36 -36
  155. package/src/styles/login.less +109 -109
  156. package/src/styles/var.less +16 -16
  157. package/src/types/env.d.ts +16 -16
  158. package/src/types/settings.ts +1 -1
  159. package/src/types/vue-router.d.ts +9 -9
  160. package/src/utils/Storage.ts +124 -124
  161. package/src/utils/authority-utils.ts +84 -84
  162. package/src/utils/common.ts +41 -41
  163. package/src/utils/crypto.ts +39 -39
  164. package/src/utils/dataUtil.ts +42 -42
  165. package/src/utils/dictUtil.ts +52 -52
  166. package/src/utils/http/index.ts +199 -199
  167. package/src/utils/i18n.ts +72 -72
  168. package/src/utils/indexedDB.ts +195 -195
  169. package/src/utils/inline-px-to-vw.ts +28 -28
  170. package/src/utils/mobileUtil.ts +34 -34
  171. package/src/utils/progress.ts +19 -19
  172. package/src/utils/queryFormDefaultRangePicker.ts +57 -57
  173. package/src/utils/routerUtil.ts +271 -271
  174. package/src/utils/runEvalFunction.ts +13 -13
  175. package/src/utils/secureStorage.ts +71 -71
  176. package/src/utils/set-page-title.ts +5 -5
  177. package/src/utils/validate.ts +6 -6
  178. package/src/utils/wechatUtil.ts +9 -9
  179. package/src/views/chat/index.vue +153 -153
  180. package/src/views/common/LoadError.vue +63 -63
  181. package/src/views/common/NotFound.vue +67 -67
  182. package/src/views/component/EvaluateRecordView/index.vue +40 -40
  183. package/src/views/component/IconifyView/index.vue +504 -507
  184. package/src/views/component/UserDetailView/UserDetailPage.vue +77 -0
  185. package/src/views/component/UserDetailView/index.vue +224 -0
  186. package/src/views/component/XCellDetailView/index.vue +217 -217
  187. package/src/views/component/XCellListView/index.vue +108 -147
  188. package/src/views/component/XFormAppraiseView/index.vue +174 -174
  189. package/src/views/component/XFormGroupView/index.vue +78 -91
  190. package/src/views/component/XFormView/index.vue +27 -107
  191. package/src/views/component/XOlMapView/XLocationPicker/index.vue +118 -118
  192. package/src/views/component/XOlMapView/index.vue +434 -434
  193. package/src/views/component/XOlMapView/testData.ts +64 -64
  194. package/src/views/component/XReportFormIframeView/index.vue +47 -47
  195. package/src/views/component/XReportFormView/index.vue +13 -13
  196. package/src/views/component/XReportGridView/index.vue +17 -17
  197. package/src/views/component/XRequestView/index.vue +234 -234
  198. package/src/views/component/XSignatureView/index.vue +50 -50
  199. package/src/views/component/index.vue +181 -177
  200. package/src/views/component/menu.vue +117 -117
  201. package/src/views/component/notice.vue +46 -46
  202. package/src/views/component/topNav.vue +36 -36
  203. package/src/views/invoiceShow/index.vue +61 -61
  204. package/src/views/user/login/ForgetPasswordForm.vue +94 -94
  205. package/src/views/user/login/LoginForm.vue +346 -346
  206. package/src/views/user/login/LoginTitle.vue +76 -76
  207. package/src/views/user/login/LoginWave.vue +109 -109
  208. package/src/views/user/login/index.vue +22 -22
  209. package/src/views/user/my/comm/ModifyPassword.vue +346 -346
  210. package/src/views/user/my/index.vue +340 -340
  211. package/src/views/userRecords/AbnormalAlarmRecords.vue +21 -0
  212. package/src/views/userRecords/CardReplacementRecords.vue +21 -0
  213. package/src/views/userRecords/ChangeRecords.vue +19 -0
  214. package/src/views/userRecords/CommandViewRecords.vue +20 -0
  215. package/src/views/userRecords/GasCompensationRecords.vue +20 -0
  216. package/src/views/userRecords/InstrumentCollectionRecords.vue +21 -0
  217. package/src/views/userRecords/MeterRecords.vue +20 -0
  218. package/src/views/userRecords/OperateRecords.vue +51 -0
  219. package/src/views/userRecords/OtherChargeRecords.vue +19 -0
  220. package/src/views/userRecords/PaymentRecords.vue +28 -0
  221. package/src/views/userRecords/PriceAdjustmentRecords.vue +19 -0
  222. package/src/views/userRecords/ReplacementRecords.vue +19 -0
  223. package/src/views/userRecords/SafetyRecords.vue +19 -0
  224. package/src/views/userRecords/TransactionRecords.vue +21 -0
  225. package/src/views/userRecords/TransferRecords.vue +19 -0
  226. package/src/views/userRecords/operateRecordDetail/index.vue +316 -0
  227. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AddUserDetail.vue +124 -0
  228. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AdvanceDeliveryDetail.vue +88 -0
  229. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AutoAccountsCancelDetail.vue +205 -0
  230. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AutoAccountsDetail.vue +192 -0
  231. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BankDkDetail.vue +192 -0
  232. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BankPayDetail.vue +192 -0
  233. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BlacklistDetail.vue +153 -0
  234. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CancellationDetail.vue +101 -0
  235. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardMeterCenterCancelDetail.vue +127 -0
  236. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardMeterCenterDetail.vue +153 -0
  237. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardOverUserDetail.vue +153 -0
  238. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ChangeMeterCancelDetail.vue +166 -0
  239. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ChangeMeterDetail.vue +205 -0
  240. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/DisableManageDetail.vue +127 -0
  241. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/EnableManageDetail.vue +114 -0
  242. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/FaZheChangeDetail.vue +124 -0
  243. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/FeeDeductionDetail.vue +153 -0
  244. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/GasPriceChangeDetail.vue +126 -0
  245. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/InputtorChangeDetail.vue +126 -0
  246. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotMeterCenterCancelDetail.vue +114 -0
  247. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotMeterCenterDetail.vue +127 -0
  248. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotOpenDetail.vue +88 -0
  249. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineCardDetail.vue +101 -0
  250. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineMeterCenterCancelDetail.vue +218 -0
  251. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineMeterCenterDetail.vue +153 -0
  252. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OffGasAddGasDetail.vue +140 -0
  253. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OtherChargeCancelDetail.vue +127 -0
  254. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OtherChargeDetail.vue +114 -0
  255. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OverUserChangeDetail.vue +127 -0
  256. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReBillDetail.vue +127 -0
  257. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/RefundDetail.vue +114 -0
  258. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReplaceCardManageCancelDetail.vue +127 -0
  259. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReplaceCardManageDetail.vue +114 -0
  260. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/SaleCardGasDetail.vue +140 -0
  261. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/TransferManageCancelDetail.vue +152 -0
  262. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/TransferManageDetail.vue +178 -0
  263. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/UserChangeDetail.vue +123 -0
  264. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/WechatPayDetail.vue +192 -0
  265. package/src/views/userRecords/types.ts +66 -0
  266. package/tsconfig.json +39 -39
  267. package/uno.config.ts +82 -78
  268. package/vite.config.ts +118 -118
@@ -1,1075 +1,1075 @@
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
- BackTop as VanBackTop,
12
- Button as VanButton,
13
- Col as VanCol,
14
- Icon as VanIcon,
15
- List as VanList,
16
- Popover as VanPopover,
17
- PullRefresh as VanPullRefresh,
18
- Row as VanRow,
19
- Search as VanSearch,
20
- Space as VanSpace,
21
- Tag as VanTag,
22
- } from 'vant'
23
- import { computed, defineEmits, defineProps, getCurrentInstance, onBeforeMount, ref, useSlots, watch } from 'vue'
24
- import { useRouter } from 'vue-router'
25
-
26
- const {
27
- configName = '',
28
- fixQueryForm = null,
29
- idKey = 'o_id',
30
- serviceName,
31
- scanOptions,
32
- customAdd = false,
33
- customEdit = false,
34
- hideAllActions = false,
35
- } = defineProps<{
36
- configName?: string
37
- fixQueryForm?: object
38
- idKey?: string
39
- serviceName?: string
40
- scanOptions?: {
41
- show?: boolean // 是否显示扫码按钮
42
- type?: string | string[] // 显示类型:可以是单个类型或类型数组 'nfc' / ['scan', 'nfc']
43
- defaultMode?: string // 默认模式
44
- }
45
- // 是否自定义新增、编辑按钮
46
- customAdd?: boolean
47
- customEdit?: boolean
48
- // 是否隐藏所有操作按钮
49
- hideAllActions?: boolean
50
- }>()
51
-
52
- const emit = defineEmits<{
53
- (e: 'toDetail', item: any): void
54
- (e: 'update', item: any): void
55
- (e: 'deleteRow', item: any): void
56
- (e: 'add'): void
57
- (e: string, item: any): void
58
- (e: 'updateCondition', params: any): void
59
- }>()
60
-
61
- const userState = useUserStore().getLogin()
62
-
63
- const router = useRouter()
64
-
65
- const orderVal = ref(undefined)
66
-
67
- const sortordVal = ref(undefined)
68
-
69
- // 配置内容
70
- const configContent = ref({})
71
-
72
- // 表单加载后是否立即执行查询
73
- const isInitQuery = ref(false)
74
-
75
- // 主列
76
- const mainColumns = ref([])
77
-
78
- // 副标题列
79
- const subTitleColumns = ref([])
80
-
81
- // 详情列
82
- const detailColumns = ref([])
83
-
84
- // 标签列
85
- const tagList = ref([])
86
-
87
- // 标题按钮列
88
- const btnList = ref([])
89
-
90
- // 底部列
91
- const footColumns = ref([])
92
-
93
- // 所有的复杂操作列
94
- const allActions = ref([])
95
-
96
- // 复杂操作列前三项
97
- const mainActions = ref([])
98
-
99
- // 复杂操作列其余项
100
- const otherActions = ref([])
101
-
102
- // 数据集
103
- const list = ref([])
104
-
105
- // 每个 Popover 的显示状态
106
- const showPopover = ref<boolean[]>(list.value.map(() => false))
107
-
108
- // 排序集
109
- const orderList = ref([])
110
-
111
- // 表单查询数组
112
- const formQueryList = ref([])
113
-
114
- // 总数据
115
- const totalCount = ref(0)
116
-
117
- // 当前页数
118
- const pageNo = ref(1)
119
-
120
- // 每页数量
121
- const pageSize = 20
122
-
123
- const searchValue = ref('')
124
-
125
- // 数据加载状态
126
- const loading = ref(false)
127
- const refreshing = ref(false)
128
- const finished = ref(false)
129
- const isError = ref(false)
130
- const finishedText = ref('加载完成')
131
- // 避免查询多次
132
- const isLastPage = ref(false)
133
-
134
- // 条件参数(查询框)
135
- const conditionParams = ref(undefined)
136
-
137
- // 主要按钮的状态
138
- const buttonState = ref(undefined)
139
-
140
- // 新增or查询的表单配置
141
- const groupFormItems = ref({})
142
- const title = ref('')
143
-
144
- // 按钮权限
145
- const buttonPermissions = ref([])
146
-
147
- // 默认参数
148
- const defaultParams = {}
149
-
150
- const slots = useSlots()
151
-
152
- // 当前组件实例(不推荐使用,可能会在后续的版本更迭中调整,暂时用来绑定函数的上下文)
153
- const currInst = getCurrentInstance()
154
-
155
- // 列表底部的文字显示
156
- function finishedBottomText() {
157
- 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))))))
158
- return '已加载全部内容,如需新增请点击右上角的 + 号'
159
- else
160
- return '已加载全部内容'
161
- }
162
-
163
- onBeforeMount(() => {
164
- initComponent()
165
- })
166
-
167
- // 组件初始化
168
- function initComponent() {
169
- getConfigByName(configName, (result) => {
170
- groupFormItems.value = result
171
- title.value = result?.title
172
- const isQuery = result.createdQuery
173
- for (let i = 0; i < result.columnJson.length; i++) {
174
- const item = result.columnJson[i]
175
- item.span = item.flexSpan
176
- if (item.slotType === 'badge')
177
- item.dictName = item.slotKeyMap
178
-
179
- if (item.mobileColumnType === 'mobile_header_column') {
180
- mainColumns.value.push(item)
181
- }
182
- else if (item.mobileColumnType === 'mobile_subtitle_column') {
183
- subTitleColumns.value.push(item)
184
- }
185
- else if (item.mobileColumnType === 'mobile_details_column') {
186
- detailColumns.value.push(item)
187
- }
188
- else if (item.mobileColumnType === 'mobile_footer_column') {
189
- footColumns.value.push(item)
190
- }
191
- else if (item.mobileColumnType === 'mobile_tag_column') {
192
- tagList.value.push(item)
193
- }
194
- else if (item.slotType === 'action' && item.actionArr) {
195
- for (let j = 0; j < item.actionArr.length; j++) {
196
- allActions.value.push({
197
- text: item.actionArr[j].text,
198
- func: item.actionArr[j].func,
199
- customFunction: item.actionArr[j].customFunction,
200
- })
201
- }
202
- }
203
-
204
- if (item.btnIcon)
205
- btnList.value.push(item)
206
-
207
- if (result.showSortIcon && item.sortable) {
208
- orderList.value.push({
209
- title: item.title,
210
- value: item.dataIndex,
211
- })
212
- }
213
- }
214
- configContent.value = result
215
- // 扁平化 type=group 的 groupItems,保持顺序
216
- const flatFormJson = []
217
- result.formJson.forEach((item) => {
218
- if (item.type === 'group' && Array.isArray(item.groupItems)) {
219
- item.groupItems.forEach((groupItem) => {
220
- if (!groupItem.isOnlyAddOrEdit) {
221
- flatFormJson.push(groupItem)
222
- }
223
- })
224
- }
225
- else {
226
- if (!item.isOnlyAddOrEdit && item.type !== 'group') {
227
- flatFormJson.push(item)
228
- }
229
- }
230
- })
231
- formQueryList.value = flatFormJson
232
- console.log('formQueryList', formQueryList.value)
233
- if (result.buttonState) {
234
- buttonState.value = result.buttonState
235
- buttonPermissions.value = result.buttonPermissions
236
- 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))))))
237
- allActions.value.push({ text: '修改', func: 'updateRow' })
238
- 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))))))
239
- allActions.value.push({ text: '删除', func: 'deleteRow' })
240
- }
241
- splitArrayAt(allActions.value, 3)
242
-
243
- // 初始化条件参数(从表单默认值中获取)
244
- initConditionParams(result.formJson, isQuery)
245
- }, serviceName)
246
- }
247
-
248
- // 初始化条件参数
249
- function initConditionParams(formItems, isQuery) {
250
- let hasDefaults: boolean
251
-
252
- // 从表单配置中获取所有默认值
253
- formItems.forEach((item) => {
254
- if (item.model) {
255
- // 根据查询模式获取对应的默认值
256
- if (item.queryFormDefault !== undefined && item.queryFormDefault !== null) {
257
- if (item.type === 'rangePicker' && item.queryType === 'BETWEEN') {
258
- defaultParams[item.model] = getRangeByType(item.queryFormDefault, false)
259
- }
260
- else if (['treeSelect', 'select', 'checkbox'].includes(item.type) && ['curOrgId', 'curDepId', 'curUserId'].includes(item.queryFormDefault)) {
261
- if (item.queryFormDefault === 'curOrgId') {
262
- defaultParams[item.model] = item.type === 'select' ? userState.f.resources.orgid : [userState.f.resources.orgid]
263
- }
264
- if (item.queryFormDefault === 'curDepId') {
265
- defaultParams[item.model] = item.type === 'select' ? userState.f.resources.depids : [userState.f.resources.depids]
266
- }
267
- if (item.queryFormDefault === 'curUserId') {
268
- defaultParams[item.model] = item.type === 'select' ? userState.f.resources.id : [userState.f.resources.id]
269
- }
270
- }
271
- else {
272
- defaultParams[item.model] = item.queryFormDefault
273
- }
274
- }
275
- }
276
- })
277
- // eslint-disable-next-line prefer-const
278
- hasDefaults = true
279
-
280
- // 如果有默认值,则设置到条件参数中并立即执行查询
281
- isInitQuery.value = isQuery
282
- if (hasDefaults && isInitQuery.value) {
283
- // 延迟执行第一次查询,确保组件完全加载
284
- setTimeout(() => {
285
- loading.value = true
286
- onLoad(defaultParams)
287
- }, 100)
288
- }
289
- }
290
-
291
- // 刷新数据
292
- function onRefresh() {
293
- isError.value = false
294
- setTimeout(() => {
295
- // 重新加载数据
296
- // 将 loading 设置为 true,表示处于加载状态
297
- refreshing.value = true
298
- finishedText.value = '加载完成'
299
- finished.value = false
300
- loading.value = true
301
- onLoad(defaultParams)
302
- }, 100)
303
- }
304
-
305
- // 加载数据
306
- function onLoad(defaultParams = {}) {
307
- if (refreshing.value) {
308
- list.value = []
309
- pageNo.value = 1
310
- isLastPage.value = false
311
- }
312
- if (!isLastPage.value) {
313
- let searchVal = searchValue.value
314
- if (searchVal === '')
315
- searchVal = undefined
316
-
317
- // 确保conditionParams不是undefined
318
- if (conditionParams.value === undefined)
319
- conditionParams.value = {}
320
- const mergedParams = mergeParams(defaultParams, conditionParams.value)
321
- // 输出查询条件,便于调试
322
- console.log('查询条件:', {
323
- pageNo: pageNo.value,
324
- pageSize,
325
- conditionParams: {
326
- $queryValue: searchVal,
327
- ...fixQueryForm,
328
- ...mergedParams,
329
- },
330
- })
331
-
332
- query({
333
- queryParamsName: configName,
334
- pageNo: pageNo.value,
335
- pageSize,
336
- conditionParams: {
337
- $queryValue: searchVal,
338
- ...fixQueryForm,
339
- ...mergedParams,
340
- },
341
- sortField: orderVal?.value,
342
- sortOrder: sortordVal?.value,
343
- }, serviceName).then((res: any) => {
344
- totalCount.value = res.totalCount
345
- if (res.data.length === 0)
346
- isLastPage.value = true
347
-
348
- for (const item of res.data)
349
- list.value.push(item)
350
- if (list.value.length >= res.totalCount)
351
- finished.value = true
352
- else
353
- pageNo.value = pageNo.value + 1
354
- }).catch(() => {
355
- finishedText.value = ''
356
- finished.value = true
357
- isError.value = true
358
- }).finally(() => {
359
- // 加载状态结束
360
- loading.value = false
361
- refreshing.value = false
362
- })
363
- }
364
- }
365
-
366
- // 合并参数
367
- function mergeParams(defaultParams: object, overrideParams: object) {
368
- const result = { ...defaultParams }
369
- for (const [key, value] of Object.entries(overrideParams)) {
370
- // 只有当overrideParams中的值不是undefined或空字符串时才覆盖
371
- if (value !== undefined) {
372
- result[key] = value
373
- }
374
- }
375
- return result
376
- }
377
-
378
- // 区分主要操作列与其他操作列
379
- function splitArrayAt<T>(array: T[], index: number) {
380
- mainActions.value = array.slice(0, index)
381
- otherActions.value = array.slice(index)
382
- }
383
-
384
- // 新增:动态获取按钮分组
385
- function getActionGroups(item: any, index: number) {
386
- // 先过滤出当前行可见的按钮
387
- const visibleActions = allActions.value.filter(action =>
388
- evaluateCustomFunction(action.customFunction, item, index),
389
- )
390
- // 前3个为主按钮,其余为更多按钮,保持逆序
391
- return {
392
- main: visibleActions.slice(0, 3).reverse(),
393
- more: visibleActions.slice(3).reverse(),
394
- }
395
- }
396
-
397
- watch(() => searchValue.value, (newVal) => {
398
- if (newVal === '')
399
- onRefresh()
400
- })
401
-
402
- // 配置中心->表单项展示函数
403
- function handleFunctionStyle(funcString, record) {
404
- if (!funcString) {
405
- return {}
406
- }
407
-
408
- try {
409
- // 同步执行函数
410
- const obj = executeStrFunctionByContext(currInst, funcString, [record])
411
- // 如果返回的是对象,则直接返回
412
- if (obj && typeof obj === 'object') {
413
- return obj
414
- }
415
- // 其他情况返回空对象
416
- return {}
417
- }
418
- catch (error) {
419
- console.error('Error in handleFunctionStyle:', error)
420
- return {}
421
- }
422
- }
423
-
424
- // 逆序排列主要按钮
425
- const reversedMainActions = computed(() => {
426
- return [...mainActions.value].reverse()
427
- })
428
-
429
- // 设置 Popover 的事件
430
- function onSelectMenu(item: any, event: any) {
431
- if (event.text === '删除') {
432
- showConfirmDialog({
433
- title: '删除',
434
- message:
435
- '请确认是否删除!!!',
436
- }).then(() => {
437
- emit(event.func, item)
438
- }).catch(() => {
439
- })
440
- }
441
- else if (event.text === '修改') {
442
- if (customEdit) {
443
- emit('update', item)
444
- }
445
- else {
446
- // 默认行为 - 导航到XForm页面
447
- router.push({
448
- name: 'XForm',
449
- // params: { id: item },
450
- query: {
451
- groupFormItems: JSON.stringify(groupFormItems.value),
452
- serviceName,
453
- formData: JSON.stringify(item),
454
- mode: '修改',
455
- },
456
- })
457
- }
458
- }
459
- else {
460
- emit(event.func, item)
461
- }
462
- }
463
-
464
- // 抛出新增按钮的事件
465
- function addOption() {
466
- if (customAdd) {
467
- emit('add')
468
- }
469
- else {
470
- // 默认行为 - 导航到XForm页面
471
- router.push({
472
- name: 'XForm',
473
- query: {
474
- groupFormItems: JSON.stringify(groupFormItems.value),
475
- serviceName,
476
- formData: JSON.stringify({}),
477
- mode: '新增',
478
- },
479
- })
480
- }
481
- }
482
-
483
- // 按钮权限信息筛选
484
- function filterButtonPermissions(btn) {
485
- return buttonPermissions.value.find(item => item.btnName === btn)
486
- }
487
-
488
- // 处理按钮点击
489
- function handleButtonClick(btn, item) {
490
- emit(`${btn.btnIcon}`, item)
491
- }
492
-
493
- // 处理自定义函数
494
- function evaluateCustomFunction(funcString: string | undefined, record: any, index: number): boolean {
495
- try {
496
- // 如果 customFunction 不存在,返回 true 表示正常显示
497
- if (!funcString || funcString === '')
498
- return true
499
-
500
- // 匹配参数名、函数体
501
- const innerFuncRegex = /function\s*\((\w+)\s*,\s*(\w+)\)\s*\{([\s\S]*)\}/
502
- const matches = funcString.match(innerFuncRegex)
503
-
504
- if (!matches)
505
- return true
506
-
507
- const [, param1, param2, functionBody] = matches
508
-
509
- // eslint-disable-next-line no-new-func
510
- const func = new Function(param1, param2, functionBody)
511
- return func(record, index)
512
- }
513
- catch (error) {
514
- console.error('Error evaluating custom function:', error)
515
- return true
516
- }
517
- }
518
-
519
- /**
520
- * 函数描述: 传入自定义条件进行查询(传入字段必须在琉璃中配置生成查询项才会生效)
521
- * @param {string} params - 查询条件map 例: { os_id:1 }
522
- * // 小提示:此处传入的条件会覆盖掉fixQueryForm(固定查询条件参数)
523
- */
524
- function updateConditionAndRefresh(params: any) {
525
- if (params) {
526
- // 遍历参数,更新对应的表单值
527
- Object.entries(params).forEach(([key, value]) => {
528
- // 找到对应的表单项
529
- conditionParams.value[key] = value
530
- })
531
- }
532
-
533
- // 触发刷新
534
- onRefresh()
535
- }
536
-
537
- // 暴露方法给父组件
538
- defineExpose({
539
- updateConditionAndRefresh,
540
- onRefresh,
541
- })
542
- </script>
543
-
544
- <template>
545
- <div id="XCellList">
546
- <VanRow class="filter-condition">
547
- <!-- 左侧动态插槽区域 -->
548
- <template v-for="(_, name) in slots" :key="name">
549
- <template v-if="typeof name === 'string' && name.startsWith('search-left-')">
550
- <div class="filter-icon-box">
551
- <slot :name="name" />
552
- </div>
553
- </template>
554
- </template>
555
- <VanCol class="search-col">
556
- <VanSearch
557
- v-model="searchValue"
558
- class="title-search"
559
- clearable
560
- placeholder="综合查询框..."
561
- shape="round"
562
- @search="onRefresh"
563
- />
564
- </VanCol>
565
- <!-- 新增按钮,放在查询框后、查询条件下拉按钮前 -->
566
- <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">
567
- <VanButton
568
- icon="add"
569
- type="primary"
570
- size="small"
571
- class="add-action-btn"
572
- @click="addOption"
573
- />
574
- </VanCol>
575
- <!-- 右侧动态插槽区域 -->
576
- <template v-for="(_, name) in slots" :key="name">
577
- <template v-if="typeof name === 'string' && name.startsWith('search-right-')">
578
- <div class="filter-icon-box">
579
- <slot :name="name" />
580
- </div>
581
- </template>
582
- </template>
583
- <VanCol class="search-icon">
584
- <XCellListFilter
585
- v-model:sortord-val="sortordVal"
586
- v-model:order-val="orderVal"
587
- v-model:condition-params="conditionParams"
588
- :fix-query-form="fixQueryForm"
589
- :service-name="serviceName"
590
- :order-list="orderList"
591
- :form-query="formQueryList"
592
- :button-state="buttonState"
593
- :button-permissions="buttonPermissions"
594
- :scan-options="scanOptions"
595
- @on-refresh="onRefresh"
596
- />
597
- </VanCol>
598
- </VanRow>
599
- <slot name="search-after" />
600
- <div class="main">
601
- <VanPullRefresh v-model="refreshing" :success-text="finishedText" head-height="70" @refresh="onRefresh">
602
- <template v-if="!isError">
603
- <VanList
604
- v-model:loading="loading"
605
- class="list_main"
606
- :finished="finished"
607
- :finished-text="finishedBottomText()"
608
- :immediate-check="isInitQuery"
609
- @load="onLoad"
610
- >
611
- <div v-for="(item, index) in list" :key="`card_${index}`" class="card_item_main">
612
- <VanRow gutter="20" class="card_item_header" align="center" @click="emit('toDetail', item)">
613
- <VanCol :span="24">
614
- <div class="title-row">
615
- <div v-for="(column) in mainColumns" :key="`main_${column.dataIndex}`" class="main-title">
616
- <p
617
- class="card_item_title"
618
- :style="handleFunctionStyle(column.styleFunctionForValue, item)"
619
- >
620
- {{ item[column.dataIndex] ?? '--' }}
621
- </p>
622
- </div>
623
- <div v-for="(column) in subTitleColumns" :key="`subtitle_${column.dataIndex}`" class="sub-title">
624
- <p
625
- class="card_item_subtitle"
626
- :style="handleFunctionStyle(column.styleFunctionForValue, item)"
627
- >
628
- <XBadge
629
- :style="handleFunctionStyle(column.styleFunctionForValue, item)"
630
- :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
631
- :service-name="serviceName"
632
- />
633
- </p>
634
- </div>
635
- <div v-if="!hideAllActions" class="action-buttons">
636
- <VanButton
637
- v-for="btn in btnList"
638
- :key="btn.dataIndex"
639
- class="action-button"
640
- :icon="btn.btnIcon"
641
- size="small"
642
- @click.stop="handleButtonClick(btn, item)"
643
- />
644
- </div>
645
- </div>
646
- </VanCol>
647
- </VanRow>
648
- <VanRow gutter="20" class="card_item_details" @click="emit('toDetail', item)">
649
- <VanCol v-for="column of detailColumns" :key="`details_${column.dataIndex}`" :span="column.span">
650
- <p>
651
- {{ (column.showLabel === undefined || column.showLabel) ? `${column.title}: ` : '' }}
652
- <XBadge
653
- :style="handleFunctionStyle(column.styleFunctionForValue, item)"
654
- :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
655
- :service-name="serviceName"
656
- />
657
- </p>
658
- </VanCol>
659
- </VanRow>
660
- <VanRow v-if="tagList.length > 0" gutter="20" class="tag-row" @click="emit('toDetail', item)">
661
- <VanCol :span="24">
662
- <div class="tag-container">
663
- <div class="tag-wrapper">
664
- <template
665
- v-for="column of tagList"
666
- :key="`tag_${column.dataIndex}`"
667
- >
668
- <VanTag
669
- :text-color="column.tagColor"
670
- :color="column.tagBorderColor"
671
- size="large"
672
- class="tag-item"
673
- >
674
- <div class="tag-content">
675
- <VanIcon v-if="column.tagIcon" :name="column.tagIcon" class="tag-icon" />
676
- <span v-if="column.showLabel === undefined || column.showLabel" class="tag-title">{{ `${column.title}: ` }}</span>
677
- <XBadge
678
- :dict-name="column.dictName"
679
- :dict-value="item[column.dataIndex]"
680
- :service-name="serviceName"
681
- />
682
- </div>
683
- </VanTag>
684
- </template>
685
- </div>
686
- </div>
687
- </VanCol>
688
- </VanRow>
689
- <VanRow
690
- v-if="footColumns && footColumns.length > 0"
691
- gutter="20"
692
- class="card_item_footer"
693
- @click="emit('toDetail', item)"
694
- >
695
- <VanCol v-for="column of footColumns" :key="`foot_${column.dataIndex}`" :span="12">
696
- <p>
697
- <span :style="handleFunctionStyle(column.styleFunctionForTitle, item)">
698
- {{ (column.showLabel === undefined || column.showLabel) ? `${column.title}: ` : '' }}
699
- </span>
700
- <XBadge
701
- :style="handleFunctionStyle(column.styleFunctionForValue, item)"
702
- :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
703
- :service-name="serviceName"
704
- />
705
- </p>
706
- </VanCol>
707
- </VanRow>
708
- <!-- 添加详情插槽 -->
709
- <slot name="item-detail" :item="item" />
710
- <VanRow v-if="!hideAllActions && allActions.length > 0" gutter="20" class="card_item_bottom">
711
- <VanCol span="4">
712
- <VanPopover
713
- v-if="getActionGroups(item, index).more.length"
714
- v-model:show="showPopover[index]"
715
- placement="bottom-start"
716
- theme="dark"
717
- overlay
718
- :actions="getActionGroups(item, index).more"
719
- @select="onSelectMenu(item, $event)"
720
- >
721
- <template #reference>
722
- <div class="more-button">
723
- <span>⋯</span>
724
- </div>
725
- </template>
726
- </VanPopover>
727
- </VanCol>
728
- <VanCol span="20">
729
- <VanRow justify="end">
730
- <VanSpace>
731
- <VanButton
732
- v-for="button in getActionGroups(item, index).main"
733
- :key="button.func"
734
- type="primary"
735
- size="normal"
736
- class="action-btn"
737
- @click="onSelectMenu(item, button)"
738
- >
739
- {{ button.text }}
740
- </VanButton>
741
- </VanSpace>
742
- </VanRow>
743
- </VanCol>
744
- </VanRow>
745
- </div>
746
- </VanList>
747
- </template>
748
- <template v-else>
749
- <LoadError />
750
- </template>
751
- </VanPullRefresh>
752
- <VanBackTop />
753
- </div>
754
- </div>
755
- </template>
756
-
757
- <style scoped lang="less">
758
- #XCellList {
759
- height: calc(100vh - var(--van-nav-bar-height) - 20px);
760
- display: flex;
761
- flex-direction: column;
762
- --van-search-padding: 3px;
763
- --van-dropdown-menu-title-padding: 3px;
764
- --van-text-color-2: rgb(75, 85, 99);
765
- --van-button-normal-font-size: 13px;
766
- .main {
767
- flex: 1;
768
- min-height: 0;
769
- overflow-y: auto;
770
- background-color: var(--van-background);
771
- padding: var(--van-padding-base) var(--van-padding-sm);
772
-
773
- p {
774
- white-space: nowrap;
775
- overflow: hidden;
776
- text-overflow: ellipsis;
777
- margin: 0;
778
- }
779
-
780
- .card_item_main {
781
- background-color: var(--van-background-2);
782
- border-radius: var(--van-radius-lg);
783
- margin: 0 0 var(--van-padding-xs) 0;
784
- padding: var(--van-padding-sm);
785
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
786
- transition: all 0.3s ease;
787
- border: 1px solid rgba(0, 0, 0, 0.04);
788
-
789
- &:active {
790
- transform: scale(0.98);
791
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02);
792
- }
793
-
794
- .card_item_header {
795
- margin-bottom: var(--van-padding-base);
796
-
797
- .title-row {
798
- display: flex;
799
- align-items: center;
800
- margin-bottom: 2px;
801
- width: 100%;
802
-
803
- .main-title {
804
- display: inline-flex;
805
- align-items: center;
806
- .card_item_title {
807
- font-size: var(--van-font-size-lg);
808
- font-weight: 700;
809
- color: var(--van-text-color);
810
- margin: 0;
811
- }
812
- }
813
-
814
- .sub-title {
815
- display: inline-flex;
816
- align-items: center;
817
- margin-left: 8px;
818
- .card_item_subtitle {
819
- font-size: var(--van-font-size-md);
820
- color: var(--van-text-color-2);
821
- }
822
- }
823
-
824
- .action-buttons {
825
- display: flex;
826
- align-items: center;
827
- margin-left: auto;
828
- .action-button {
829
- margin-left: 6px;
830
- width: 32px;
831
- height: 32px;
832
- padding: 0;
833
- border: none;
834
- color: var(--van-primary-color);
835
- background-color: rgba(25, 137, 250, 0.1);
836
- border-radius: 6px;
837
- font-size: var(--van-font-size-lg);
838
- display: flex;
839
- align-items: center;
840
- justify-content: center;
841
- transition: all 0.2s ease;
842
- &:active {
843
- opacity: 0.7;
844
- background-color: rgba(25, 137, 250, 0.2);
845
- transform: scale(0.95);
846
- }
847
- }
848
- }
849
- }
850
- }
851
-
852
- .tag-row {
853
- margin-bottom: var(--van-padding-base);
854
- }
855
-
856
- .tag-container {
857
- width: 100%;
858
- .tag-wrapper {
859
- display: flex;
860
- flex-wrap: wrap;
861
- padding: 0 !important;
862
- align-items: center;
863
- width: 100%;
864
- margin: 0 -4px;
865
-
866
- .tag-item {
867
- width: auto;
868
- font-size: var(--van-font-size-md);
869
- margin: 4px 4px;
870
- :deep(.van-tag) {
871
- width: fit-content;
872
- display: inline-flex;
873
- align-items: center;
874
- padding: 2px 8px;
875
- border-radius: 4px;
876
- }
877
- .tag-content {
878
- display: flex;
879
- align-items: center;
880
- //white-space: nowrap;
881
- }
882
- .tag-icon {
883
- margin-right: 4px;
884
- font-size: 14px;
885
- display: inline-flex;
886
- align-items: center;
887
- }
888
- .tag-title {
889
- font-weight: normal;
890
- }
891
- }
892
- }
893
- }
894
-
895
- .card_item_details {
896
- font-size: var(--van-font-size-md);
897
- color: var(--van-text-color-2);
898
- padding: 4px 0;
899
-
900
- .van-col {
901
- margin-bottom: 4px;
902
- p {
903
- display: flex;
904
- align-items: center;
905
- gap: 4px;
906
-
907
- span {
908
- flex: 1;
909
- min-width: 0;
910
- overflow: hidden;
911
- text-overflow: ellipsis;
912
- white-space: nowrap;
913
- }
914
-
915
- :deep(.van-badge) {
916
- flex-shrink: 0;
917
- }
918
- }
919
- }
920
- }
921
-
922
- .card_item_footer {
923
- font-size: var(--van-font-size-md);
924
- color: var(--van-text-color-2);
925
- padding: 4px 0;
926
- .van-col:last-child {
927
- text-align: right;
928
- }
929
- .van-col:first-child {
930
- text-align: left;
931
- }
932
- }
933
-
934
- .card_item_bottom {
935
- margin-top: 8px;
936
- padding-top: 10px;
937
- border-top: 1px solid rgba(0, 0, 0, 0.06);
938
-
939
- .more-button {
940
- width: 28px;
941
- height: 28px;
942
- display: flex;
943
- align-items: center;
944
- justify-content: center;
945
- color: var(--van-text-color-2);
946
- cursor: pointer;
947
- font-size: var(--van-font-size-lg);
948
- background-color: var(--van-background);
949
- border-radius: 6px;
950
- transition: all 0.2s ease;
951
- border: 1px solid rgba(0, 0, 0, 0.06);
952
- span {
953
- line-height: 1;
954
- margin-top: -2px;
955
- }
956
- &:active {
957
- opacity: 0.7;
958
- background-color: var(--van-background-2);
959
- transform: scale(0.95);
960
- }
961
- }
962
-
963
- .action-btn {
964
- --van-button-primary-border-color: #1890ff;
965
- --van-button-primary-background: #1890ff;
966
- min-width: 76px;
967
- height: 40px;
968
- border-radius: 10px;
969
- padding: 0 14px;
970
- // font-size: var(--van-font-size-md);
971
- transition: all 0.2s ease;
972
- &:active {
973
- transform: scale(0.95);
974
- }
975
- }
976
- }
977
- }
978
- }
979
-
980
- :deep(.van-search__field) {
981
- padding: 0 !important;
982
- height: 100%;
983
- }
984
-
985
- .filter-condition {
986
- display: flex;
987
- align-items: center;
988
- padding: 8px 12px;
989
- background-color: #fff;
990
- gap: 1px;
991
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02);
992
- position: sticky;
993
- top: 0;
994
- z-index: 10;
995
- .add-col {
996
- display: flex;
997
- align-items: center;
998
- margin: 0 4px;
999
- .add-action-btn {
1000
- width: 40px;
1001
- height: 40px;
1002
- border-radius: 10px;
1003
- background-color: var(--van-background);
1004
- color: #323233;
1005
- border: 1px solid rgba(0, 0, 0, 0.06);
1006
- display: flex;
1007
- align-items: center;
1008
- justify-content: center;
1009
- font-size: 16px;
1010
- padding: 0;
1011
- transition: all 0.2s;
1012
- &:hover,
1013
- &:active {
1014
- opacity: 0.85;
1015
- background-color: rgba(25, 137, 250, 0.08);
1016
- border-color: var(--van-primary-color);
1017
- color: var(--van-primary-color);
1018
- }
1019
- }
1020
- }
1021
- :deep(.van-search) {
1022
- width: 100%;
1023
- padding: var(--van-search-padding);
1024
- background-color: transparent;
1025
- }
1026
- :deep(.van-search__content) {
1027
- border-radius: 8px;
1028
- background-color: var(--van-background);
1029
- padding: 4px 12px;
1030
- border: 1px solid rgba(0, 0, 0, 0.01);
1031
- height: 40px;
1032
- }
1033
- :deep(.van-field__left-icon) {
1034
- color: var(--van-text-color-2);
1035
- }
1036
- :deep(.van-cell) {
1037
- background-color: transparent;
1038
- }
1039
- :deep(.van-field__control::placeholder) {
1040
- color: var(--van-text-color-2);
1041
- font-size: 14px;
1042
- }
1043
- .van-col {
1044
- display: flex;
1045
- align-items: center;
1046
- &.search-col {
1047
- flex: 1;
1048
- min-width: 0;
1049
- }
1050
- &.search-icon {
1051
- flex-shrink: 0;
1052
- padding: 0;
1053
- }
1054
- }
1055
- .filter-icon-box {
1056
- display: flex;
1057
- align-items: center;
1058
- justify-content: center;
1059
- width: 36px;
1060
- height: 36px;
1061
- border-radius: 8px;
1062
- background-color: var(--van-background);
1063
- cursor: pointer;
1064
- position: relative;
1065
- z-index: 1;
1066
- border: 1px solid rgba(0, 0, 0, 0.06);
1067
- transition: all 0.2s ease;
1068
- &:active {
1069
- opacity: 0.7;
1070
- transform: scale(0.95);
1071
- }
1072
- }
1073
- }
1074
- }
1075
- </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
+ BackTop as VanBackTop,
12
+ Button as VanButton,
13
+ Col as VanCol,
14
+ Icon as VanIcon,
15
+ List as VanList,
16
+ Popover as VanPopover,
17
+ PullRefresh as VanPullRefresh,
18
+ Row as VanRow,
19
+ Search as VanSearch,
20
+ Space as VanSpace,
21
+ Tag as VanTag,
22
+ } from 'vant'
23
+ import { computed, defineEmits, defineProps, getCurrentInstance, onBeforeMount, ref, useSlots, watch } from 'vue'
24
+ import { useRouter } from 'vue-router'
25
+
26
+ const {
27
+ configName = '',
28
+ fixQueryForm = null,
29
+ idKey = 'o_id',
30
+ serviceName,
31
+ scanOptions,
32
+ customAdd = false,
33
+ customEdit = false,
34
+ hideAllActions = false,
35
+ } = defineProps<{
36
+ configName?: string
37
+ fixQueryForm?: object
38
+ idKey?: string
39
+ serviceName?: string
40
+ scanOptions?: {
41
+ show?: boolean // 是否显示扫码按钮
42
+ type?: string | string[] // 显示类型:可以是单个类型或类型数组 'nfc' / ['scan', 'nfc']
43
+ defaultMode?: string // 默认模式
44
+ }
45
+ // 是否自定义新增、编辑按钮
46
+ customAdd?: boolean
47
+ customEdit?: boolean
48
+ // 是否隐藏所有操作按钮
49
+ hideAllActions?: boolean
50
+ }>()
51
+
52
+ const emit = defineEmits<{
53
+ (e: 'toDetail', item: any): void
54
+ (e: 'update', item: any): void
55
+ (e: 'deleteRow', item: any): void
56
+ (e: 'add'): void
57
+ (e: string, item: any): void
58
+ (e: 'updateCondition', params: any): void
59
+ }>()
60
+
61
+ const userState = useUserStore().getLogin()
62
+
63
+ const router = useRouter()
64
+
65
+ const orderVal = ref(undefined)
66
+
67
+ const sortordVal = ref(undefined)
68
+
69
+ // 配置内容
70
+ const configContent = ref({})
71
+
72
+ // 表单加载后是否立即执行查询
73
+ const isInitQuery = ref(false)
74
+
75
+ // 主列
76
+ const mainColumns = ref([])
77
+
78
+ // 副标题列
79
+ const subTitleColumns = ref([])
80
+
81
+ // 详情列
82
+ const detailColumns = ref([])
83
+
84
+ // 标签列
85
+ const tagList = ref([])
86
+
87
+ // 标题按钮列
88
+ const btnList = ref([])
89
+
90
+ // 底部列
91
+ const footColumns = ref([])
92
+
93
+ // 所有的复杂操作列
94
+ const allActions = ref([])
95
+
96
+ // 复杂操作列前三项
97
+ const mainActions = ref([])
98
+
99
+ // 复杂操作列其余项
100
+ const otherActions = ref([])
101
+
102
+ // 数据集
103
+ const list = ref([])
104
+
105
+ // 每个 Popover 的显示状态
106
+ const showPopover = ref<boolean[]>(list.value.map(() => false))
107
+
108
+ // 排序集
109
+ const orderList = ref([])
110
+
111
+ // 表单查询数组
112
+ const formQueryList = ref([])
113
+
114
+ // 总数据
115
+ const totalCount = ref(0)
116
+
117
+ // 当前页数
118
+ const pageNo = ref(1)
119
+
120
+ // 每页数量
121
+ const pageSize = 20
122
+
123
+ const searchValue = ref('')
124
+
125
+ // 数据加载状态
126
+ const loading = ref(false)
127
+ const refreshing = ref(false)
128
+ const finished = ref(false)
129
+ const isError = ref(false)
130
+ const finishedText = ref('加载完成')
131
+ // 避免查询多次
132
+ const isLastPage = ref(false)
133
+
134
+ // 条件参数(查询框)
135
+ const conditionParams = ref(undefined)
136
+
137
+ // 主要按钮的状态
138
+ const buttonState = ref(undefined)
139
+
140
+ // 新增or查询的表单配置
141
+ const groupFormItems = ref({})
142
+ const title = ref('')
143
+
144
+ // 按钮权限
145
+ const buttonPermissions = ref([])
146
+
147
+ // 默认参数
148
+ const defaultParams = {}
149
+
150
+ const slots = useSlots()
151
+
152
+ // 当前组件实例(不推荐使用,可能会在后续的版本更迭中调整,暂时用来绑定函数的上下文)
153
+ const currInst = getCurrentInstance()
154
+
155
+ // 列表底部的文字显示
156
+ function finishedBottomText() {
157
+ 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))))))
158
+ return '已加载全部内容,如需新增请点击右上角的 + 号'
159
+ else
160
+ return '已加载全部内容'
161
+ }
162
+
163
+ onBeforeMount(() => {
164
+ initComponent()
165
+ })
166
+
167
+ // 组件初始化
168
+ function initComponent() {
169
+ getConfigByName(configName, (result) => {
170
+ groupFormItems.value = result
171
+ title.value = result?.title
172
+ const isQuery = result.createdQuery
173
+ for (let i = 0; i < result.columnJson.length; i++) {
174
+ const item = result.columnJson[i]
175
+ item.span = item.flexSpan
176
+ if (item.slotType === 'badge')
177
+ item.dictName = item.slotKeyMap
178
+
179
+ if (item.mobileColumnType === 'mobile_header_column') {
180
+ mainColumns.value.push(item)
181
+ }
182
+ else if (item.mobileColumnType === 'mobile_subtitle_column') {
183
+ subTitleColumns.value.push(item)
184
+ }
185
+ else if (item.mobileColumnType === 'mobile_details_column') {
186
+ detailColumns.value.push(item)
187
+ }
188
+ else if (item.mobileColumnType === 'mobile_footer_column') {
189
+ footColumns.value.push(item)
190
+ }
191
+ else if (item.mobileColumnType === 'mobile_tag_column') {
192
+ tagList.value.push(item)
193
+ }
194
+ else if (item.slotType === 'action' && item.actionArr) {
195
+ for (let j = 0; j < item.actionArr.length; j++) {
196
+ allActions.value.push({
197
+ text: item.actionArr[j].text,
198
+ func: item.actionArr[j].func,
199
+ customFunction: item.actionArr[j].customFunction,
200
+ })
201
+ }
202
+ }
203
+
204
+ if (item.btnIcon)
205
+ btnList.value.push(item)
206
+
207
+ if (result.showSortIcon && item.sortable) {
208
+ orderList.value.push({
209
+ title: item.title,
210
+ value: item.dataIndex,
211
+ })
212
+ }
213
+ }
214
+ configContent.value = result
215
+ // 扁平化 type=group 的 groupItems,保持顺序
216
+ const flatFormJson = []
217
+ result.formJson.forEach((item) => {
218
+ if (item.type === 'group' && Array.isArray(item.groupItems)) {
219
+ item.groupItems.forEach((groupItem) => {
220
+ if (!groupItem.isOnlyAddOrEdit) {
221
+ flatFormJson.push(groupItem)
222
+ }
223
+ })
224
+ }
225
+ else {
226
+ if (!item.isOnlyAddOrEdit && item.type !== 'group') {
227
+ flatFormJson.push(item)
228
+ }
229
+ }
230
+ })
231
+ formQueryList.value = flatFormJson
232
+ console.log('formQueryList', formQueryList.value)
233
+ if (result.buttonState) {
234
+ buttonState.value = result.buttonState
235
+ buttonPermissions.value = result.buttonPermissions
236
+ 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))))))
237
+ allActions.value.push({ text: '修改', func: 'updateRow' })
238
+ 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))))))
239
+ allActions.value.push({ text: '删除', func: 'deleteRow' })
240
+ }
241
+ splitArrayAt(allActions.value, 3)
242
+
243
+ // 初始化条件参数(从表单默认值中获取)
244
+ initConditionParams(result.formJson, isQuery)
245
+ }, serviceName)
246
+ }
247
+
248
+ // 初始化条件参数
249
+ function initConditionParams(formItems, isQuery) {
250
+ let hasDefaults: boolean
251
+
252
+ // 从表单配置中获取所有默认值
253
+ formItems.forEach((item) => {
254
+ if (item.model) {
255
+ // 根据查询模式获取对应的默认值
256
+ if (item.queryFormDefault !== undefined && item.queryFormDefault !== null) {
257
+ if (item.type === 'rangePicker' && item.queryType === 'BETWEEN') {
258
+ defaultParams[item.model] = getRangeByType(item.queryFormDefault, false)
259
+ }
260
+ else if (['treeSelect', 'select', 'checkbox'].includes(item.type) && ['curOrgId', 'curDepId', 'curUserId'].includes(item.queryFormDefault)) {
261
+ if (item.queryFormDefault === 'curOrgId') {
262
+ defaultParams[item.model] = item.type === 'select' ? userState.f.resources.orgid : [userState.f.resources.orgid]
263
+ }
264
+ if (item.queryFormDefault === 'curDepId') {
265
+ defaultParams[item.model] = item.type === 'select' ? userState.f.resources.depids : [userState.f.resources.depids]
266
+ }
267
+ if (item.queryFormDefault === 'curUserId') {
268
+ defaultParams[item.model] = item.type === 'select' ? userState.f.resources.id : [userState.f.resources.id]
269
+ }
270
+ }
271
+ else {
272
+ defaultParams[item.model] = item.queryFormDefault
273
+ }
274
+ }
275
+ }
276
+ })
277
+ // eslint-disable-next-line prefer-const
278
+ hasDefaults = true
279
+
280
+ // 如果有默认值,则设置到条件参数中并立即执行查询
281
+ isInitQuery.value = isQuery
282
+ if (hasDefaults && isInitQuery.value) {
283
+ // 延迟执行第一次查询,确保组件完全加载
284
+ setTimeout(() => {
285
+ loading.value = true
286
+ onLoad(defaultParams)
287
+ }, 100)
288
+ }
289
+ }
290
+
291
+ // 刷新数据
292
+ function onRefresh() {
293
+ isError.value = false
294
+ setTimeout(() => {
295
+ // 重新加载数据
296
+ // 将 loading 设置为 true,表示处于加载状态
297
+ refreshing.value = true
298
+ finishedText.value = '加载完成'
299
+ finished.value = false
300
+ loading.value = true
301
+ onLoad(defaultParams)
302
+ }, 100)
303
+ }
304
+
305
+ // 加载数据
306
+ function onLoad(defaultParams = {}) {
307
+ if (refreshing.value) {
308
+ list.value = []
309
+ pageNo.value = 1
310
+ isLastPage.value = false
311
+ }
312
+ if (!isLastPage.value) {
313
+ let searchVal = searchValue.value
314
+ if (searchVal === '')
315
+ searchVal = undefined
316
+
317
+ // 确保conditionParams不是undefined
318
+ if (conditionParams.value === undefined)
319
+ conditionParams.value = {}
320
+ const mergedParams = mergeParams(defaultParams, conditionParams.value)
321
+ // 输出查询条件,便于调试
322
+ console.log('查询条件:', {
323
+ pageNo: pageNo.value,
324
+ pageSize,
325
+ conditionParams: {
326
+ $queryValue: searchVal,
327
+ ...fixQueryForm,
328
+ ...mergedParams,
329
+ },
330
+ })
331
+
332
+ query({
333
+ queryParamsName: configName,
334
+ pageNo: pageNo.value,
335
+ pageSize,
336
+ conditionParams: {
337
+ $queryValue: searchVal,
338
+ ...fixQueryForm,
339
+ ...mergedParams,
340
+ },
341
+ sortField: orderVal?.value,
342
+ sortOrder: sortordVal?.value,
343
+ }, serviceName).then((res: any) => {
344
+ totalCount.value = res.totalCount
345
+ if (res.data.length === 0)
346
+ isLastPage.value = true
347
+
348
+ for (const item of res.data)
349
+ list.value.push(item)
350
+ if (list.value.length >= res.totalCount)
351
+ finished.value = true
352
+ else
353
+ pageNo.value = pageNo.value + 1
354
+ }).catch(() => {
355
+ finishedText.value = ''
356
+ finished.value = true
357
+ isError.value = true
358
+ }).finally(() => {
359
+ // 加载状态结束
360
+ loading.value = false
361
+ refreshing.value = false
362
+ })
363
+ }
364
+ }
365
+
366
+ // 合并参数
367
+ function mergeParams(defaultParams: object, overrideParams: object) {
368
+ const result = { ...defaultParams }
369
+ for (const [key, value] of Object.entries(overrideParams)) {
370
+ // 只有当overrideParams中的值不是undefined或空字符串时才覆盖
371
+ if (value !== undefined) {
372
+ result[key] = value
373
+ }
374
+ }
375
+ return result
376
+ }
377
+
378
+ // 区分主要操作列与其他操作列
379
+ function splitArrayAt<T>(array: T[], index: number) {
380
+ mainActions.value = array.slice(0, index)
381
+ otherActions.value = array.slice(index)
382
+ }
383
+
384
+ // 新增:动态获取按钮分组
385
+ function getActionGroups(item: any, index: number) {
386
+ // 先过滤出当前行可见的按钮
387
+ const visibleActions = allActions.value.filter(action =>
388
+ evaluateCustomFunction(action.customFunction, item, index),
389
+ )
390
+ // 前3个为主按钮,其余为更多按钮,保持逆序
391
+ return {
392
+ main: visibleActions.slice(0, 3).reverse(),
393
+ more: visibleActions.slice(3).reverse(),
394
+ }
395
+ }
396
+
397
+ watch(() => searchValue.value, (newVal) => {
398
+ if (newVal === '')
399
+ onRefresh()
400
+ })
401
+
402
+ // 配置中心->表单项展示函数
403
+ function handleFunctionStyle(funcString, record) {
404
+ if (!funcString) {
405
+ return {}
406
+ }
407
+
408
+ try {
409
+ // 同步执行函数
410
+ const obj = executeStrFunctionByContext(currInst, funcString, [record])
411
+ // 如果返回的是对象,则直接返回
412
+ if (obj && typeof obj === 'object') {
413
+ return obj
414
+ }
415
+ // 其他情况返回空对象
416
+ return {}
417
+ }
418
+ catch (error) {
419
+ console.error('Error in handleFunctionStyle:', error)
420
+ return {}
421
+ }
422
+ }
423
+
424
+ // 逆序排列主要按钮
425
+ const reversedMainActions = computed(() => {
426
+ return [...mainActions.value].reverse()
427
+ })
428
+
429
+ // 设置 Popover 的事件
430
+ function onSelectMenu(item: any, event: any) {
431
+ if (event.text === '删除') {
432
+ showConfirmDialog({
433
+ title: '删除',
434
+ message:
435
+ '请确认是否删除!!!',
436
+ }).then(() => {
437
+ emit(event.func, item)
438
+ }).catch(() => {
439
+ })
440
+ }
441
+ else if (event.text === '修改') {
442
+ if (customEdit) {
443
+ emit('update', item)
444
+ }
445
+ else {
446
+ // 默认行为 - 导航到XForm页面
447
+ router.push({
448
+ name: 'XForm',
449
+ // params: { id: item },
450
+ query: {
451
+ groupFormItems: JSON.stringify(groupFormItems.value),
452
+ serviceName,
453
+ formData: JSON.stringify(item),
454
+ mode: '修改',
455
+ },
456
+ })
457
+ }
458
+ }
459
+ else {
460
+ emit(event.func, item)
461
+ }
462
+ }
463
+
464
+ // 抛出新增按钮的事件
465
+ function addOption() {
466
+ if (customAdd) {
467
+ emit('add')
468
+ }
469
+ else {
470
+ // 默认行为 - 导航到XForm页面
471
+ router.push({
472
+ name: 'XForm',
473
+ query: {
474
+ groupFormItems: JSON.stringify(groupFormItems.value),
475
+ serviceName,
476
+ formData: JSON.stringify({}),
477
+ mode: '新增',
478
+ },
479
+ })
480
+ }
481
+ }
482
+
483
+ // 按钮权限信息筛选
484
+ function filterButtonPermissions(btn) {
485
+ return buttonPermissions.value.find(item => item.btnName === btn)
486
+ }
487
+
488
+ // 处理按钮点击
489
+ function handleButtonClick(btn, item) {
490
+ emit(`${btn.btnIcon}`, item)
491
+ }
492
+
493
+ // 处理自定义函数
494
+ function evaluateCustomFunction(funcString: string | undefined, record: any, index: number): boolean {
495
+ try {
496
+ // 如果 customFunction 不存在,返回 true 表示正常显示
497
+ if (!funcString || funcString === '')
498
+ return true
499
+
500
+ // 匹配参数名、函数体
501
+ const innerFuncRegex = /function\s*\((\w+)\s*,\s*(\w+)\)\s*\{([\s\S]*)\}/
502
+ const matches = funcString.match(innerFuncRegex)
503
+
504
+ if (!matches)
505
+ return true
506
+
507
+ const [, param1, param2, functionBody] = matches
508
+
509
+ // eslint-disable-next-line no-new-func
510
+ const func = new Function(param1, param2, functionBody)
511
+ return func(record, index)
512
+ }
513
+ catch (error) {
514
+ console.error('Error evaluating custom function:', error)
515
+ return true
516
+ }
517
+ }
518
+
519
+ /**
520
+ * 函数描述: 传入自定义条件进行查询(传入字段必须在琉璃中配置生成查询项才会生效)
521
+ * @param {string} params - 查询条件map 例: { os_id:1 }
522
+ * // 小提示:此处传入的条件会覆盖掉fixQueryForm(固定查询条件参数)
523
+ */
524
+ function updateConditionAndRefresh(params: any) {
525
+ if (params) {
526
+ // 遍历参数,更新对应的表单值
527
+ Object.entries(params).forEach(([key, value]) => {
528
+ // 找到对应的表单项
529
+ conditionParams.value[key] = value
530
+ })
531
+ }
532
+
533
+ // 触发刷新
534
+ onRefresh()
535
+ }
536
+
537
+ // 暴露方法给父组件
538
+ defineExpose({
539
+ updateConditionAndRefresh,
540
+ onRefresh,
541
+ })
542
+ </script>
543
+
544
+ <template>
545
+ <div id="XCellList">
546
+ <VanRow class="filter-condition">
547
+ <!-- 左侧动态插槽区域 -->
548
+ <template v-for="(_, name) in slots" :key="name">
549
+ <template v-if="typeof name === 'string' && name.startsWith('search-left-')">
550
+ <div class="filter-icon-box">
551
+ <slot :name="name" />
552
+ </div>
553
+ </template>
554
+ </template>
555
+ <VanCol class="search-col">
556
+ <VanSearch
557
+ v-model="searchValue"
558
+ class="title-search"
559
+ clearable
560
+ placeholder="综合查询框..."
561
+ shape="round"
562
+ @search="onRefresh"
563
+ />
564
+ </VanCol>
565
+ <!-- 新增按钮,放在查询框后、查询条件下拉按钮前 -->
566
+ <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">
567
+ <VanButton
568
+ icon="add"
569
+ type="primary"
570
+ size="small"
571
+ class="add-action-btn"
572
+ @click="addOption"
573
+ />
574
+ </VanCol>
575
+ <!-- 右侧动态插槽区域 -->
576
+ <template v-for="(_, name) in slots" :key="name">
577
+ <template v-if="typeof name === 'string' && name.startsWith('search-right-')">
578
+ <div class="filter-icon-box">
579
+ <slot :name="name" />
580
+ </div>
581
+ </template>
582
+ </template>
583
+ <VanCol class="search-icon">
584
+ <XCellListFilter
585
+ v-model:sortord-val="sortordVal"
586
+ v-model:order-val="orderVal"
587
+ v-model:condition-params="conditionParams"
588
+ :fix-query-form="fixQueryForm"
589
+ :service-name="serviceName"
590
+ :order-list="orderList"
591
+ :form-query="formQueryList"
592
+ :button-state="buttonState"
593
+ :button-permissions="buttonPermissions"
594
+ :scan-options="scanOptions"
595
+ @on-refresh="onRefresh"
596
+ />
597
+ </VanCol>
598
+ </VanRow>
599
+ <slot name="search-after" />
600
+ <div class="main">
601
+ <VanPullRefresh v-model="refreshing" :success-text="finishedText" head-height="70" @refresh="onRefresh">
602
+ <template v-if="!isError">
603
+ <VanList
604
+ v-model:loading="loading"
605
+ class="list_main"
606
+ :finished="finished"
607
+ :finished-text="finishedBottomText()"
608
+ :immediate-check="isInitQuery"
609
+ @load="onLoad"
610
+ >
611
+ <div v-for="(item, index) in list" :key="`card_${index}`" class="card_item_main">
612
+ <VanRow gutter="20" class="card_item_header" align="center" @click="emit('toDetail', item)">
613
+ <VanCol :span="24">
614
+ <div class="title-row">
615
+ <div v-for="(column) in mainColumns" :key="`main_${column.dataIndex}`" class="main-title">
616
+ <p
617
+ class="card_item_title"
618
+ :style="handleFunctionStyle(column.styleFunctionForValue, item)"
619
+ >
620
+ {{ item[column.dataIndex] ?? '--' }}
621
+ </p>
622
+ </div>
623
+ <div v-for="(column) in subTitleColumns" :key="`subtitle_${column.dataIndex}`" class="sub-title">
624
+ <p
625
+ class="card_item_subtitle"
626
+ :style="handleFunctionStyle(column.styleFunctionForValue, item)"
627
+ >
628
+ <XBadge
629
+ :style="handleFunctionStyle(column.styleFunctionForValue, item)"
630
+ :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
631
+ :service-name="serviceName"
632
+ />
633
+ </p>
634
+ </div>
635
+ <div v-if="!hideAllActions" class="action-buttons">
636
+ <VanButton
637
+ v-for="btn in btnList"
638
+ :key="btn.dataIndex"
639
+ class="action-button"
640
+ :icon="btn.btnIcon"
641
+ size="small"
642
+ @click.stop="handleButtonClick(btn, item)"
643
+ />
644
+ </div>
645
+ </div>
646
+ </VanCol>
647
+ </VanRow>
648
+ <VanRow gutter="20" class="card_item_details" @click="emit('toDetail', item)">
649
+ <VanCol v-for="column of detailColumns" :key="`details_${column.dataIndex}`" :span="column.span">
650
+ <p>
651
+ {{ (column.showLabel === undefined || column.showLabel) ? `${column.title}: ` : '' }}
652
+ <XBadge
653
+ :style="handleFunctionStyle(column.styleFunctionForValue, item)"
654
+ :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
655
+ :service-name="serviceName"
656
+ />
657
+ </p>
658
+ </VanCol>
659
+ </VanRow>
660
+ <VanRow v-if="tagList.length > 0" gutter="20" class="tag-row" @click="emit('toDetail', item)">
661
+ <VanCol :span="24">
662
+ <div class="tag-container">
663
+ <div class="tag-wrapper">
664
+ <template
665
+ v-for="column of tagList"
666
+ :key="`tag_${column.dataIndex}`"
667
+ >
668
+ <VanTag
669
+ :text-color="column.tagColor"
670
+ :color="column.tagBorderColor"
671
+ size="large"
672
+ class="tag-item"
673
+ >
674
+ <div class="tag-content">
675
+ <VanIcon v-if="column.tagIcon" :name="column.tagIcon" class="tag-icon" />
676
+ <span v-if="column.showLabel === undefined || column.showLabel" class="tag-title">{{ `${column.title}: ` }}</span>
677
+ <XBadge
678
+ :dict-name="column.dictName"
679
+ :dict-value="item[column.dataIndex]"
680
+ :service-name="serviceName"
681
+ />
682
+ </div>
683
+ </VanTag>
684
+ </template>
685
+ </div>
686
+ </div>
687
+ </VanCol>
688
+ </VanRow>
689
+ <VanRow
690
+ v-if="footColumns && footColumns.length > 0"
691
+ gutter="20"
692
+ class="card_item_footer"
693
+ @click="emit('toDetail', item)"
694
+ >
695
+ <VanCol v-for="column of footColumns" :key="`foot_${column.dataIndex}`" :span="12">
696
+ <p>
697
+ <span :style="handleFunctionStyle(column.styleFunctionForTitle, item)">
698
+ {{ (column.showLabel === undefined || column.showLabel) ? `${column.title}: ` : '' }}
699
+ </span>
700
+ <XBadge
701
+ :style="handleFunctionStyle(column.styleFunctionForValue, item)"
702
+ :dict-name="column.dictName" :dict-value="item[column.dataIndex]"
703
+ :service-name="serviceName"
704
+ />
705
+ </p>
706
+ </VanCol>
707
+ </VanRow>
708
+ <!-- 添加详情插槽 -->
709
+ <slot name="item-detail" :item="item" />
710
+ <VanRow v-if="!hideAllActions && allActions.length > 0" gutter="20" class="card_item_bottom">
711
+ <VanCol span="4">
712
+ <VanPopover
713
+ v-if="getActionGroups(item, index).more.length"
714
+ v-model:show="showPopover[index]"
715
+ placement="bottom-start"
716
+ theme="dark"
717
+ overlay
718
+ :actions="getActionGroups(item, index).more"
719
+ @select="onSelectMenu(item, $event)"
720
+ >
721
+ <template #reference>
722
+ <div class="more-button">
723
+ <span>⋯</span>
724
+ </div>
725
+ </template>
726
+ </VanPopover>
727
+ </VanCol>
728
+ <VanCol span="20">
729
+ <VanRow justify="end">
730
+ <VanSpace>
731
+ <VanButton
732
+ v-for="button in getActionGroups(item, index).main"
733
+ :key="button.func"
734
+ type="primary"
735
+ size="normal"
736
+ class="action-btn"
737
+ @click="onSelectMenu(item, button)"
738
+ >
739
+ {{ button.text }}
740
+ </VanButton>
741
+ </VanSpace>
742
+ </VanRow>
743
+ </VanCol>
744
+ </VanRow>
745
+ </div>
746
+ </VanList>
747
+ </template>
748
+ <template v-else>
749
+ <LoadError />
750
+ </template>
751
+ </VanPullRefresh>
752
+ <VanBackTop />
753
+ </div>
754
+ </div>
755
+ </template>
756
+
757
+ <style scoped lang="less">
758
+ #XCellList {
759
+ height: calc(100vh - var(--van-nav-bar-height) - 20px);
760
+ display: flex;
761
+ flex-direction: column;
762
+ --van-search-padding: 3px;
763
+ --van-dropdown-menu-title-padding: 3px;
764
+ --van-text-color-2: rgb(75, 85, 99);
765
+ --van-button-normal-font-size: 13px;
766
+ .main {
767
+ flex: 1;
768
+ min-height: 0;
769
+ overflow-y: auto;
770
+ background-color: var(--van-background);
771
+ padding: var(--van-padding-base) var(--van-padding-sm);
772
+
773
+ p {
774
+ white-space: nowrap;
775
+ overflow: hidden;
776
+ text-overflow: ellipsis;
777
+ margin: 0;
778
+ }
779
+
780
+ .card_item_main {
781
+ background-color: var(--van-background-2);
782
+ border-radius: var(--van-radius-lg);
783
+ margin: 0 0 var(--van-padding-xs) 0;
784
+ padding: var(--van-padding-sm);
785
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
786
+ transition: all 0.3s ease;
787
+ border: 1px solid rgba(0, 0, 0, 0.04);
788
+
789
+ &:active {
790
+ transform: scale(0.98);
791
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02);
792
+ }
793
+
794
+ .card_item_header {
795
+ margin-bottom: var(--van-padding-base);
796
+
797
+ .title-row {
798
+ display: flex;
799
+ align-items: center;
800
+ margin-bottom: 2px;
801
+ width: 100%;
802
+
803
+ .main-title {
804
+ display: inline-flex;
805
+ align-items: center;
806
+ .card_item_title {
807
+ font-size: var(--van-font-size-lg);
808
+ font-weight: 700;
809
+ color: var(--van-text-color);
810
+ margin: 0;
811
+ }
812
+ }
813
+
814
+ .sub-title {
815
+ display: inline-flex;
816
+ align-items: center;
817
+ margin-left: 8px;
818
+ .card_item_subtitle {
819
+ font-size: var(--van-font-size-md);
820
+ color: var(--van-text-color-2);
821
+ }
822
+ }
823
+
824
+ .action-buttons {
825
+ display: flex;
826
+ align-items: center;
827
+ margin-left: auto;
828
+ .action-button {
829
+ margin-left: 6px;
830
+ width: 32px;
831
+ height: 32px;
832
+ padding: 0;
833
+ border: none;
834
+ color: var(--van-primary-color);
835
+ background-color: rgba(25, 137, 250, 0.1);
836
+ border-radius: 6px;
837
+ font-size: var(--van-font-size-lg);
838
+ display: flex;
839
+ align-items: center;
840
+ justify-content: center;
841
+ transition: all 0.2s ease;
842
+ &:active {
843
+ opacity: 0.7;
844
+ background-color: rgba(25, 137, 250, 0.2);
845
+ transform: scale(0.95);
846
+ }
847
+ }
848
+ }
849
+ }
850
+ }
851
+
852
+ .tag-row {
853
+ margin-bottom: var(--van-padding-base);
854
+ }
855
+
856
+ .tag-container {
857
+ width: 100%;
858
+ .tag-wrapper {
859
+ display: flex;
860
+ flex-wrap: wrap;
861
+ padding: 0 !important;
862
+ align-items: center;
863
+ width: 100%;
864
+ margin: 0 -4px;
865
+
866
+ .tag-item {
867
+ width: auto;
868
+ font-size: var(--van-font-size-md);
869
+ margin: 4px 4px;
870
+ :deep(.van-tag) {
871
+ width: fit-content;
872
+ display: inline-flex;
873
+ align-items: center;
874
+ padding: 2px 8px;
875
+ border-radius: 4px;
876
+ }
877
+ .tag-content {
878
+ display: flex;
879
+ align-items: center;
880
+ //white-space: nowrap;
881
+ }
882
+ .tag-icon {
883
+ margin-right: 4px;
884
+ font-size: 14px;
885
+ display: inline-flex;
886
+ align-items: center;
887
+ }
888
+ .tag-title {
889
+ font-weight: normal;
890
+ }
891
+ }
892
+ }
893
+ }
894
+
895
+ .card_item_details {
896
+ font-size: var(--van-font-size-md);
897
+ color: var(--van-text-color-2);
898
+ padding: 4px 0;
899
+
900
+ .van-col {
901
+ margin-bottom: 4px;
902
+ p {
903
+ display: flex;
904
+ align-items: center;
905
+ gap: 4px;
906
+
907
+ span {
908
+ flex: 1;
909
+ min-width: 0;
910
+ overflow: hidden;
911
+ text-overflow: ellipsis;
912
+ white-space: nowrap;
913
+ }
914
+
915
+ :deep(.van-badge) {
916
+ flex-shrink: 0;
917
+ }
918
+ }
919
+ }
920
+ }
921
+
922
+ .card_item_footer {
923
+ font-size: var(--van-font-size-md);
924
+ color: var(--van-text-color-2);
925
+ padding: 4px 0;
926
+ .van-col:last-child {
927
+ text-align: right;
928
+ }
929
+ .van-col:first-child {
930
+ text-align: left;
931
+ }
932
+ }
933
+
934
+ .card_item_bottom {
935
+ margin-top: 8px;
936
+ padding-top: 10px;
937
+ border-top: 1px solid rgba(0, 0, 0, 0.06);
938
+
939
+ .more-button {
940
+ width: 28px;
941
+ height: 28px;
942
+ display: flex;
943
+ align-items: center;
944
+ justify-content: center;
945
+ color: var(--van-text-color-2);
946
+ cursor: pointer;
947
+ font-size: var(--van-font-size-lg);
948
+ background-color: var(--van-background);
949
+ border-radius: 6px;
950
+ transition: all 0.2s ease;
951
+ border: 1px solid rgba(0, 0, 0, 0.06);
952
+ span {
953
+ line-height: 1;
954
+ margin-top: -2px;
955
+ }
956
+ &:active {
957
+ opacity: 0.7;
958
+ background-color: var(--van-background-2);
959
+ transform: scale(0.95);
960
+ }
961
+ }
962
+
963
+ .action-btn {
964
+ --van-button-primary-border-color: #1890ff;
965
+ --van-button-primary-background: #1890ff;
966
+ min-width: 76px;
967
+ height: 40px;
968
+ border-radius: 10px;
969
+ padding: 0 14px;
970
+ // font-size: var(--van-font-size-md);
971
+ transition: all 0.2s ease;
972
+ &:active {
973
+ transform: scale(0.95);
974
+ }
975
+ }
976
+ }
977
+ }
978
+ }
979
+
980
+ :deep(.van-search__field) {
981
+ padding: 0 !important;
982
+ height: 100%;
983
+ }
984
+
985
+ .filter-condition {
986
+ display: flex;
987
+ align-items: center;
988
+ padding: 8px 12px;
989
+ background-color: #fff;
990
+ gap: 1px;
991
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02);
992
+ position: sticky;
993
+ top: 0;
994
+ z-index: 10;
995
+ .add-col {
996
+ display: flex;
997
+ align-items: center;
998
+ margin: 0 4px;
999
+ .add-action-btn {
1000
+ width: 40px;
1001
+ height: 40px;
1002
+ border-radius: 10px;
1003
+ background-color: var(--van-background);
1004
+ color: #323233;
1005
+ border: 1px solid rgba(0, 0, 0, 0.06);
1006
+ display: flex;
1007
+ align-items: center;
1008
+ justify-content: center;
1009
+ font-size: 16px;
1010
+ padding: 0;
1011
+ transition: all 0.2s;
1012
+ &:hover,
1013
+ &:active {
1014
+ opacity: 0.85;
1015
+ background-color: rgba(25, 137, 250, 0.08);
1016
+ border-color: var(--van-primary-color);
1017
+ color: var(--van-primary-color);
1018
+ }
1019
+ }
1020
+ }
1021
+ :deep(.van-search) {
1022
+ width: 100%;
1023
+ padding: var(--van-search-padding);
1024
+ background-color: transparent;
1025
+ }
1026
+ :deep(.van-search__content) {
1027
+ border-radius: 8px;
1028
+ background-color: var(--van-background);
1029
+ padding: 4px 12px;
1030
+ border: 1px solid rgba(0, 0, 0, 0.01);
1031
+ height: 40px;
1032
+ }
1033
+ :deep(.van-field__left-icon) {
1034
+ color: var(--van-text-color-2);
1035
+ }
1036
+ :deep(.van-cell) {
1037
+ background-color: transparent;
1038
+ }
1039
+ :deep(.van-field__control::placeholder) {
1040
+ color: var(--van-text-color-2);
1041
+ font-size: 14px;
1042
+ }
1043
+ .van-col {
1044
+ display: flex;
1045
+ align-items: center;
1046
+ &.search-col {
1047
+ flex: 1;
1048
+ min-width: 0;
1049
+ }
1050
+ &.search-icon {
1051
+ flex-shrink: 0;
1052
+ padding: 0;
1053
+ }
1054
+ }
1055
+ .filter-icon-box {
1056
+ display: flex;
1057
+ align-items: center;
1058
+ justify-content: center;
1059
+ width: 36px;
1060
+ height: 36px;
1061
+ border-radius: 8px;
1062
+ background-color: var(--van-background);
1063
+ cursor: pointer;
1064
+ position: relative;
1065
+ z-index: 1;
1066
+ border: 1px solid rgba(0, 0, 0, 0.06);
1067
+ transition: all 0.2s ease;
1068
+ &:active {
1069
+ opacity: 0.7;
1070
+ transform: scale(0.95);
1071
+ }
1072
+ }
1073
+ }
1074
+ }
1075
+ </style>