af-mobile-client-vue3 1.3.16 → 1.3.17

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 (273) hide show
  1. package/.cursorrules +60 -60
  2. package/.editorconfig +9 -9
  3. package/.env +10 -10
  4. package/.env.development +1 -1
  5. package/.env.production +1 -1
  6. package/.node-version +1 -1
  7. package/.vscode/extensions.json +12 -12
  8. package/.vscode/settings.json +66 -66
  9. package/CLAUDE.md +189 -189
  10. package/README.md +182 -182
  11. package/af-example-mobile-vue-web.iml +9 -9
  12. package/build/vite/index.ts +98 -98
  13. package/build/vite/optimize.ts +34 -34
  14. package/build/vite/vconsole.ts +47 -47
  15. package/commitlint.config.ts +32 -32
  16. package/compress.js +36 -36
  17. package/eslint.config.ts +30 -30
  18. package/index.html +23 -23
  19. package/mock/data.ts +20 -20
  20. package/mock/index.ts +7 -7
  21. package/mock/modules/prose.mock.ts +13 -13
  22. package/mock/modules/user.mock.ts +152 -152
  23. package/mock/util.ts +19 -19
  24. package/netlify.toml +12 -12
  25. package/package.json +114 -114
  26. package/postcss.config.ts +27 -27
  27. package/public/favicon.svg +4 -4
  28. package/public/safari-pinned-tab.svg +4 -4
  29. package/scripts/verifyCommit.js +19 -19
  30. package/src/App.vue +79 -79
  31. package/src/api/mock/index.ts +30 -30
  32. package/src/api/user/index.ts +40 -40
  33. package/src/assets/img/user/login/background-shadow-1.svg +20 -20
  34. package/src/assets/img/user/login/logo-background.svg +20 -20
  35. package/src/bootstrap.ts +26 -26
  36. package/src/components/core/BeautifulLoading/index.vue +52 -52
  37. package/src/components/core/ImageUploader/index.vue +244 -244
  38. package/src/components/core/NavBar/index.vue +53 -53
  39. package/src/components/core/Tabbar/index.vue +32 -32
  40. package/src/components/core/Uploader/index.vue +124 -124
  41. package/src/components/core/XGridDropOption/index.vue +154 -154
  42. package/src/components/core/XMultiSelect/index.vue +183 -183
  43. package/src/components/core/XSelect/index.vue +149 -149
  44. package/src/components/data/CardContainer/CardContainer.vue +118 -118
  45. package/src/components/data/CardContainer/CardHeader.vue +99 -99
  46. package/src/components/data/InfoDisplay/index.vue +132 -132
  47. package/src/components/data/UserDetail/api.ts +24 -24
  48. package/src/components/data/UserDetail/index.vue +620 -620
  49. package/src/components/data/UserDetail/recordEntries.ts +159 -159
  50. package/src/components/data/UserDetail/types.ts +26 -26
  51. package/src/components/data/XBadge/index.vue +82 -82
  52. package/src/components/data/XCellDetail/index.vue +105 -105
  53. package/src/components/data/XCellList/XCellList.md +313 -313
  54. package/src/components/data/XCellList/index.vue +1110 -1075
  55. package/src/components/data/XCellListFilter/QrScanner/index.vue +207 -207
  56. package/src/components/data/XCellListFilter/QrScanner/startScanAnimation.ts +53 -53
  57. package/src/components/data/XCellListFilter/VpnRecognition/index.vue +119 -119
  58. package/src/components/data/XCellListFilter/index.vue +705 -705
  59. package/src/components/data/XForm/index.vue +659 -659
  60. package/src/components/data/XFormGroup/doc/DeviceForm.vue +122 -122
  61. package/src/components/data/XFormGroup/doc/FormGroupDemo.vue +56 -56
  62. package/src/components/data/XFormGroup/doc/README.md +286 -286
  63. package/src/components/data/XFormGroup/doc/UserForm.vue +102 -102
  64. package/src/components/data/XFormGroup/index.vue +240 -240
  65. package/src/components/data/XFormItem/index.vue +1310 -1310
  66. package/src/components/data/XOlMap/README.md +227 -227
  67. package/src/components/data/XOlMap/XLocationPicker/index.vue +226 -226
  68. package/src/components/data/XOlMap/index.vue +1490 -1490
  69. package/src/components/data/XOlMap/types.ts +149 -149
  70. package/src/components/data/XOlMap/utils/wgs84ToGcj02.js +154 -154
  71. package/src/components/data/XReportForm/DateTimeSecondsPicker.vue +208 -208
  72. package/src/components/data/XReportForm/XReportFormJsonRender.vue +220 -220
  73. package/src/components/data/XReportForm/index.vue +1393 -1393
  74. package/src/components/data/XReportGrid/XAddReport/XAddReport.vue +198 -198
  75. package/src/components/data/XReportGrid/XAddReport/index.js +3 -3
  76. package/src/components/data/XReportGrid/XAddReport/index.md +53 -53
  77. package/src/components/data/XReportGrid/XAddReport/index.ts +10 -10
  78. package/src/components/data/XReportGrid/XReport.vue +960 -960
  79. package/src/components/data/XReportGrid/XReportDemo.vue +33 -33
  80. package/src/components/data/XReportGrid/XReportDesign.vue +597 -597
  81. package/src/components/data/XReportGrid/XReportDrawer/XReportDrawer.vue +148 -148
  82. package/src/components/data/XReportGrid/XReportDrawer/index.js +3 -3
  83. package/src/components/data/XReportGrid/XReportDrawer/index.ts +10 -10
  84. package/src/components/data/XReportGrid/XReportJsonRender.vue +399 -399
  85. package/src/components/data/XReportGrid/XReportTrGroup.vue +592 -592
  86. package/src/components/data/XReportGrid/index.md +46 -46
  87. package/src/components/data/XReportGrid/print.js +184 -184
  88. package/src/components/data/XSignature/index.vue +284 -284
  89. package/src/components/data/XTag/index.vue +10 -10
  90. package/src/components/layout/NormalDataLayout/index.vue +69 -69
  91. package/src/components/layout/TabBarLayout/index.vue +40 -40
  92. package/src/composables/dark.ts +5 -5
  93. package/src/config/routes.ts +9 -9
  94. package/src/constants/index.ts +2 -2
  95. package/src/enums/requestEnum.ts +25 -25
  96. package/src/expression/ExpressionRunner.ts +28 -28
  97. package/src/expression/TestExpression.ts +510 -510
  98. package/src/expression/core/Delegate.ts +116 -116
  99. package/src/expression/core/Expression.ts +1359 -1359
  100. package/src/expression/core/Program.ts +985 -985
  101. package/src/expression/core/Token.ts +29 -29
  102. package/src/expression/enums/ExpressionType.ts +81 -81
  103. package/src/expression/enums/TokenType.ts +11 -11
  104. package/src/expression/exception/BreakWayException.ts +2 -2
  105. package/src/expression/exception/ContinueWayException.ts +2 -2
  106. package/src/expression/exception/ExpressionException.ts +29 -29
  107. package/src/expression/exception/ReturnWayException.ts +14 -14
  108. package/src/expression/exception/ServiceException.ts +22 -22
  109. package/src/expression/instances/JSONArray.ts +52 -52
  110. package/src/expression/instances/JSONObject.ts +118 -118
  111. package/src/expression/instances/LogicConsole.ts +31 -31
  112. package/src/font-style/font.css +4 -4
  113. package/src/hooks/useBoolean.ts +26 -26
  114. package/src/hooks/useCommon.ts +9 -9
  115. package/src/hooks/useLoading.ts +16 -16
  116. package/src/hooks/useLogin.ts +97 -97
  117. package/src/icons/svg/check-in.svg +32 -32
  118. package/src/icons/svg/dark.svg +4 -4
  119. package/src/icons/svg/github.svg +4 -4
  120. package/src/icons/svg/light.svg +4 -4
  121. package/src/icons/svg/link.svg +4 -4
  122. package/src/icons/svgo.yml +22 -22
  123. package/src/layout/GridView/index.vue +16 -16
  124. package/src/layout/PageLayout.vue +9 -9
  125. package/src/layout/SingleLayout.vue +9 -9
  126. package/src/locales/en-US.json +128 -128
  127. package/src/locales/zh-CN.json +128 -128
  128. package/src/logic/LogicRunner.ts +67 -67
  129. package/src/logic/TestLogic.ts +13 -13
  130. package/src/logic/plugins/common/DateTools.ts +35 -35
  131. package/src/logic/plugins/common/VueTools.ts +30 -30
  132. package/src/logic/plugins/index.ts +7 -7
  133. package/src/main.ts +44 -44
  134. package/src/plugins/AppData.ts +38 -38
  135. package/src/plugins/GetLoginInfoService.ts +10 -10
  136. package/src/plugins/collectIcons.ts +10 -10
  137. package/src/plugins/index.ts +11 -11
  138. package/src/router/README.md +8 -8
  139. package/src/router/guards.ts +59 -59
  140. package/src/router/index.ts +35 -35
  141. package/src/router/invoiceRoutes.ts +33 -33
  142. package/src/router/routes.ts +347 -341
  143. package/src/router/types.ts +7 -7
  144. package/src/services/api/Login.ts +6 -6
  145. package/src/services/api/common.ts +109 -109
  146. package/src/services/api/index.ts +7 -7
  147. package/src/services/api/manage.ts +8 -8
  148. package/src/services/api/search.ts +16 -16
  149. package/src/services/api/user.ts +17 -17
  150. package/src/services/restTools.ts +56 -56
  151. package/src/services/v3Api.ts +147 -147
  152. package/src/stores/index.ts +11 -11
  153. package/src/stores/modules/counter.ts +19 -19
  154. package/src/stores/modules/routeCache.ts +23 -23
  155. package/src/stores/modules/setting.ts +76 -76
  156. package/src/stores/modules/user.ts +235 -235
  157. package/src/stores/mutation-type.ts +7 -7
  158. package/src/styles/app.less +36 -36
  159. package/src/styles/login.less +109 -109
  160. package/src/styles/var.less +25 -16
  161. package/src/types/env.d.ts +16 -16
  162. package/src/types/settings.ts +1 -1
  163. package/src/types/vue-router.d.ts +9 -9
  164. package/src/utils/Storage.ts +124 -124
  165. package/src/utils/authority-utils.ts +84 -84
  166. package/src/utils/common.ts +41 -41
  167. package/src/utils/crypto.ts +39 -39
  168. package/src/utils/dataUtil.ts +42 -42
  169. package/src/utils/dictUtil.ts +52 -52
  170. package/src/utils/http/index.ts +199 -199
  171. package/src/utils/i18n.ts +72 -72
  172. package/src/utils/indexedDB.ts +195 -195
  173. package/src/utils/inline-px-to-vw.ts +28 -28
  174. package/src/utils/mobileUtil.ts +34 -34
  175. package/src/utils/progress.ts +19 -19
  176. package/src/utils/routerUtil.ts +271 -271
  177. package/src/utils/runEvalFunction.ts +13 -13
  178. package/src/utils/secureStorage.ts +71 -71
  179. package/src/utils/set-page-title.ts +5 -5
  180. package/src/utils/validate.ts +6 -6
  181. package/src/utils/wechatUtil.ts +9 -9
  182. package/src/views/chat/index.vue +153 -153
  183. package/src/views/common/LoadError.vue +63 -63
  184. package/src/views/common/NotFound.vue +67 -67
  185. package/src/views/component/EvaluateRecordView/index.vue +40 -40
  186. package/src/views/component/IconifyView/index.vue +504 -504
  187. package/src/views/component/UserDetailView/UserDetailPage.vue +77 -77
  188. package/src/views/component/UserDetailView/index.vue +234 -234
  189. package/src/views/component/XCellDetailView/index.vue +217 -217
  190. package/src/views/component/XCellListView/index.vue +157 -108
  191. package/src/views/component/XFormAppraiseView/index.vue +174 -174
  192. package/src/views/component/XFormGroupView/index.vue +82 -78
  193. package/src/views/component/XFormGroupView/xformgroup222.vue +97 -0
  194. package/src/views/component/XFormView/index.vue +27 -27
  195. package/src/views/component/XOlMapView/XLocationPicker/index.vue +118 -118
  196. package/src/views/component/XOlMapView/index.vue +434 -434
  197. package/src/views/component/XOlMapView/testData.ts +64 -64
  198. package/src/views/component/XReportFormIframeView/index.vue +47 -47
  199. package/src/views/component/XReportFormView/index.vue +13 -13
  200. package/src/views/component/XReportGridView/index.vue +17 -17
  201. package/src/views/component/XRequestView/index.vue +234 -234
  202. package/src/views/component/XSignatureView/index.vue +50 -50
  203. package/src/views/component/index.vue +181 -181
  204. package/src/views/component/menu.vue +117 -117
  205. package/src/views/component/notice.vue +46 -46
  206. package/src/views/component/topNav.vue +36 -36
  207. package/src/views/invoiceShow/index.vue +61 -61
  208. package/src/views/user/login/ForgetPasswordForm.vue +94 -94
  209. package/src/views/user/login/LoginForm.vue +346 -346
  210. package/src/views/user/login/LoginTitle.vue +76 -76
  211. package/src/views/user/login/LoginWave.vue +109 -109
  212. package/src/views/user/login/index.vue +22 -22
  213. package/src/views/user/my/comm/ModifyPassword.vue +346 -346
  214. package/src/views/user/my/index.vue +340 -340
  215. package/src/views/userRecords/AbnormalAlarmRecords.vue +21 -21
  216. package/src/views/userRecords/CardReplacementRecords.vue +21 -21
  217. package/src/views/userRecords/ChangeRecords.vue +19 -19
  218. package/src/views/userRecords/CommandViewRecords.vue +20 -20
  219. package/src/views/userRecords/GasCompensationRecords.vue +20 -20
  220. package/src/views/userRecords/InstrumentCollectionRecords.vue +21 -21
  221. package/src/views/userRecords/MeterRecords.vue +20 -20
  222. package/src/views/userRecords/OperateRecords.vue +51 -51
  223. package/src/views/userRecords/OtherChargeRecords.vue +19 -19
  224. package/src/views/userRecords/PaymentRecords.vue +28 -28
  225. package/src/views/userRecords/PriceAdjustmentRecords.vue +19 -19
  226. package/src/views/userRecords/ReplacementRecords.vue +19 -19
  227. package/src/views/userRecords/SafetyRecords.vue +19 -19
  228. package/src/views/userRecords/TransactionRecords.vue +21 -21
  229. package/src/views/userRecords/TransferRecords.vue +19 -19
  230. package/src/views/userRecords/operateRecordDetail/index.vue +316 -316
  231. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AddUserDetail.vue +124 -124
  232. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AdvanceDeliveryDetail.vue +88 -88
  233. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AutoAccountsCancelDetail.vue +205 -205
  234. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AutoAccountsDetail.vue +192 -192
  235. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BankDkDetail.vue +192 -192
  236. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BankPayDetail.vue +192 -192
  237. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BlacklistDetail.vue +153 -153
  238. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CancellationDetail.vue +101 -101
  239. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardMeterCenterCancelDetail.vue +127 -127
  240. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardMeterCenterDetail.vue +153 -153
  241. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardOverUserDetail.vue +153 -153
  242. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ChangeMeterCancelDetail.vue +166 -166
  243. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ChangeMeterDetail.vue +205 -205
  244. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/DisableManageDetail.vue +127 -127
  245. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/EnableManageDetail.vue +114 -114
  246. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/FaZheChangeDetail.vue +124 -124
  247. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/FeeDeductionDetail.vue +153 -153
  248. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/GasPriceChangeDetail.vue +126 -126
  249. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/InputtorChangeDetail.vue +126 -126
  250. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotMeterCenterCancelDetail.vue +114 -114
  251. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotMeterCenterDetail.vue +127 -127
  252. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotOpenDetail.vue +88 -88
  253. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineCardDetail.vue +101 -101
  254. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineMeterCenterCancelDetail.vue +218 -218
  255. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineMeterCenterDetail.vue +153 -153
  256. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OffGasAddGasDetail.vue +140 -140
  257. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OtherChargeCancelDetail.vue +127 -127
  258. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OtherChargeDetail.vue +114 -114
  259. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OverUserChangeDetail.vue +127 -127
  260. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReBillDetail.vue +127 -127
  261. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/RefundDetail.vue +114 -114
  262. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReplaceCardManageCancelDetail.vue +127 -127
  263. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReplaceCardManageDetail.vue +114 -114
  264. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/SaleCardGasDetail.vue +140 -140
  265. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/TransferManageCancelDetail.vue +152 -152
  266. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/TransferManageDetail.vue +178 -178
  267. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/UserChangeDetail.vue +123 -123
  268. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/WechatPayDetail.vue +192 -192
  269. package/src/views/userRecords/types.ts +66 -66
  270. package/tsconfig.json +39 -39
  271. package/uno.config.ts +82 -82
  272. package/vite.config.ts +118 -118
  273. package/.claude/settings.local.json +0 -8
@@ -1,1075 +1,1110 @@
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
+ :actions="getActionGroups(item, index).more"
717
+ @select="onSelectMenu(item, $event)"
718
+ >
719
+ <template #reference>
720
+ <div class="more-button">
721
+ <span>⋯</span>
722
+ </div>
723
+ </template>
724
+ </VanPopover>
725
+ </VanCol>
726
+ <VanCol span="20">
727
+ <VanRow justify="end">
728
+ <VanSpace>
729
+ <VanButton
730
+ v-for="button in getActionGroups(item, index).main"
731
+ :key="button.func"
732
+ type="primary"
733
+ size="normal"
734
+ class="action-btn"
735
+ @click="onSelectMenu(item, button)"
736
+ >
737
+ {{ button.text }}
738
+ </VanButton>
739
+ </VanSpace>
740
+ </VanRow>
741
+ </VanCol>
742
+ </VanRow>
743
+ </div>
744
+ </VanList>
745
+ </template>
746
+ <template v-else>
747
+ <LoadError />
748
+ </template>
749
+ </VanPullRefresh>
750
+ <VanBackTop />
751
+ </div>
752
+ </div>
753
+ </template>
754
+
755
+ <style scoped lang="less">
756
+ #XCellList {
757
+ height: calc(100vh - var(--van-nav-bar-height) - 20px);
758
+ display: flex;
759
+ flex-direction: column;
760
+ --van-search-padding: 3px;
761
+ --van-dropdown-menu-title-padding: 3px;
762
+ --van-text-color-2: rgb(75, 85, 99);
763
+ --van-button-normal-font-size: 13px;
764
+ .main {
765
+ flex: 1;
766
+ min-height: 0;
767
+ overflow-y: auto;
768
+ background-color: var(--van-background);
769
+ padding: var(--van-padding-base) var(--van-padding-sm);
770
+
771
+ p {
772
+ white-space: nowrap;
773
+ overflow: hidden;
774
+ text-overflow: ellipsis;
775
+ margin: 0;
776
+ }
777
+
778
+ .card_item_main {
779
+ background-color: var(--van-background-2);
780
+ border-radius: var(--van-radius-lg);
781
+ margin: 0 0 var(--van-padding-xs) 0;
782
+ padding: var(--van-padding-sm);
783
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
784
+ transition: all 0.3s ease;
785
+ border: 1px solid rgba(0, 0, 0, 0.04);
786
+
787
+ &:active {
788
+ transform: scale(0.98);
789
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02);
790
+ }
791
+
792
+ .card_item_header {
793
+ margin-bottom: var(--van-padding-base);
794
+
795
+ .title-row {
796
+ display: flex;
797
+ align-items: center;
798
+ margin-bottom: 2px;
799
+ width: 100%;
800
+
801
+ .main-title {
802
+ display: inline-flex;
803
+ align-items: center;
804
+ .card_item_title {
805
+ font-size: var(--van-font-size-lg);
806
+ font-weight: 700;
807
+ color: var(--van-text-color);
808
+ margin: 0;
809
+ }
810
+ }
811
+
812
+ .sub-title {
813
+ display: inline-flex;
814
+ align-items: center;
815
+ margin-left: 8px;
816
+ .card_item_subtitle {
817
+ font-size: var(--van-font-size-md);
818
+ color: var(--van-text-color-2);
819
+ }
820
+ }
821
+
822
+ .action-buttons {
823
+ display: flex;
824
+ align-items: center;
825
+ margin-left: auto;
826
+ .action-button {
827
+ margin-left: 6px;
828
+ width: 32px;
829
+ height: 32px;
830
+ padding: 0;
831
+ border: none;
832
+ color: var(--van-primary-color);
833
+ background-color: rgba(25, 137, 250, 0.1);
834
+ border-radius: 6px;
835
+ font-size: var(--van-font-size-lg);
836
+ display: flex;
837
+ align-items: center;
838
+ justify-content: center;
839
+ transition: all 0.2s ease;
840
+ &:active {
841
+ opacity: 0.7;
842
+ background-color: rgba(25, 137, 250, 0.2);
843
+ transform: scale(0.95);
844
+ }
845
+ }
846
+ }
847
+ }
848
+ }
849
+
850
+ .tag-row {
851
+ margin-bottom: var(--van-padding-base);
852
+ }
853
+
854
+ .tag-container {
855
+ width: 100%;
856
+ .tag-wrapper {
857
+ display: flex;
858
+ flex-wrap: wrap;
859
+ padding: 0 !important;
860
+ align-items: center;
861
+ width: 100%;
862
+ margin: 0 -4px;
863
+
864
+ .tag-item {
865
+ width: auto;
866
+ font-size: var(--van-font-size-md);
867
+ margin: 4px 4px;
868
+ :deep(.van-tag) {
869
+ width: fit-content;
870
+ display: inline-flex;
871
+ align-items: center;
872
+ padding: 2px 8px;
873
+ border-radius: 4px;
874
+ }
875
+ .tag-content {
876
+ display: flex;
877
+ align-items: center;
878
+ //white-space: nowrap;
879
+ }
880
+ .tag-icon {
881
+ margin-right: 4px;
882
+ font-size: 14px;
883
+ display: inline-flex;
884
+ align-items: center;
885
+ }
886
+ .tag-title {
887
+ font-weight: normal;
888
+ }
889
+ }
890
+ }
891
+ }
892
+
893
+ .card_item_details {
894
+ font-size: var(--van-font-size-md);
895
+ color: var(--van-text-color-2);
896
+ padding: 4px 0;
897
+
898
+ .van-col {
899
+ margin-bottom: 4px;
900
+ p {
901
+ display: flex;
902
+ align-items: center;
903
+ gap: 4px;
904
+
905
+ span {
906
+ flex: 1;
907
+ min-width: 0;
908
+ overflow: hidden;
909
+ text-overflow: ellipsis;
910
+ white-space: nowrap;
911
+ }
912
+
913
+ :deep(.van-badge) {
914
+ flex-shrink: 0;
915
+ }
916
+ }
917
+ }
918
+ }
919
+
920
+ .card_item_footer {
921
+ font-size: var(--van-font-size-md);
922
+ color: var(--van-text-color-2);
923
+ padding: 4px 0;
924
+ .van-col:last-child {
925
+ text-align: right;
926
+ }
927
+ .van-col:first-child {
928
+ text-align: left;
929
+ }
930
+ }
931
+
932
+ .card_item_bottom {
933
+ margin-top: 8px;
934
+ padding-top: 10px;
935
+ border-top: 1px solid rgba(0, 0, 0, 0.06);
936
+
937
+ .more-button {
938
+ width: 37px;
939
+ height: 37px;
940
+ display: flex;
941
+ align-items: center;
942
+ justify-content: center;
943
+ color: var(--van-text-color-2);
944
+ cursor: pointer;
945
+ font-size: var(--van-font-size-lg);
946
+ background-color: var(--van-background);
947
+ border-radius: 6px;
948
+ transition: all 0.2s ease;
949
+ border: 1px solid rgba(0, 0, 0, 0.06);
950
+ margin: 2px 0 0 0;
951
+ span {
952
+ line-height: 1;
953
+ margin-top: -2px;
954
+ }
955
+ &:active {
956
+ opacity: 0.7;
957
+ background-color: var(--van-background-2);
958
+ transform: scale(0.95);
959
+ }
960
+ }
961
+
962
+ .action-btn {
963
+ --van-button-primary-border-color: #1890ff;
964
+ --van-button-primary-background: #1890ff;
965
+ min-width: 76px;
966
+ height: 40px;
967
+ border-radius: 10px;
968
+ padding: 0 14px;
969
+ // font-size: var(--van-font-size-md);
970
+ transition: all 0.2s ease;
971
+ &:active {
972
+ transform: scale(0.95);
973
+ }
974
+ }
975
+ }
976
+ }
977
+ }
978
+
979
+ :deep(.van-search__field) {
980
+ padding: 0 !important;
981
+ height: 100%;
982
+ }
983
+
984
+ :deep(.van-popup),
985
+ :deep(.van-popover) {
986
+ border: none !important;
987
+ outline: none !important;
988
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
989
+ background-color: #ffffff !important;
990
+
991
+ &::before,
992
+ &::after {
993
+ border: none !important;
994
+ outline: none !important;
995
+ }
996
+
997
+ .van-popover__content {
998
+ background-color: #ffffff !important;
999
+ border: none !important;
1000
+ outline: none !important;
1001
+ border-radius: 8px !important;
1002
+ }
1003
+
1004
+ .van-popover__arrow,
1005
+ .van-popover_arrow {
1006
+ background-color: #ffffff !important;
1007
+ border: none !important;
1008
+ outline: none !important;
1009
+ }
1010
+
1011
+ .van-popover__action {
1012
+ color: #333 !important;
1013
+
1014
+ &:hover {
1015
+ background-color: #f5f5f5 !important;
1016
+ }
1017
+ }
1018
+ }
1019
+
1020
+ .filter-condition {
1021
+ display: flex;
1022
+ align-items: center;
1023
+ padding: 8px 12px;
1024
+ background-color: #fff;
1025
+ gap: 1px;
1026
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.02);
1027
+ position: sticky;
1028
+ top: 0;
1029
+ z-index: 10;
1030
+ .add-col {
1031
+ display: flex;
1032
+ align-items: center;
1033
+ margin: 0 4px;
1034
+ .add-action-btn {
1035
+ width: 40px;
1036
+ height: 40px;
1037
+ border-radius: 10px;
1038
+ background-color: var(--van-background);
1039
+ color: #323233;
1040
+ border: 1px solid rgba(0, 0, 0, 0.06);
1041
+ display: flex;
1042
+ align-items: center;
1043
+ justify-content: center;
1044
+ font-size: 16px;
1045
+ padding: 0;
1046
+ transition: all 0.2s;
1047
+ &:hover,
1048
+ &:active {
1049
+ opacity: 0.85;
1050
+ background-color: rgba(25, 137, 250, 0.08);
1051
+ border-color: var(--van-primary-color);
1052
+ color: var(--van-primary-color);
1053
+ }
1054
+ }
1055
+ }
1056
+ :deep(.van-search) {
1057
+ width: 100%;
1058
+ padding: var(--van-search-padding);
1059
+ background-color: transparent;
1060
+ }
1061
+ :deep(.van-search__content) {
1062
+ border-radius: 8px;
1063
+ background-color: var(--van-background);
1064
+ padding: 4px 12px;
1065
+ border: 1px solid rgba(0, 0, 0, 0.01);
1066
+ height: 40px;
1067
+ }
1068
+ :deep(.van-field__left-icon) {
1069
+ color: var(--van-text-color-2);
1070
+ }
1071
+ :deep(.van-cell) {
1072
+ background-color: transparent;
1073
+ }
1074
+ :deep(.van-field__control::placeholder) {
1075
+ color: var(--van-text-color-2);
1076
+ font-size: 14px;
1077
+ }
1078
+ .van-col {
1079
+ display: flex;
1080
+ align-items: center;
1081
+ &.search-col {
1082
+ flex: 1;
1083
+ min-width: 0;
1084
+ }
1085
+ &.search-icon {
1086
+ flex-shrink: 0;
1087
+ padding: 0;
1088
+ }
1089
+ }
1090
+ .filter-icon-box {
1091
+ display: flex;
1092
+ align-items: center;
1093
+ justify-content: center;
1094
+ width: 36px;
1095
+ height: 36px;
1096
+ border-radius: 8px;
1097
+ background-color: var(--van-background);
1098
+ cursor: pointer;
1099
+ position: relative;
1100
+ z-index: 1;
1101
+ border: 1px solid rgba(0, 0, 0, 0.06);
1102
+ transition: all 0.2s ease;
1103
+ &:active {
1104
+ opacity: 0.7;
1105
+ transform: scale(0.95);
1106
+ }
1107
+ }
1108
+ }
1109
+ }
1110
+ </style>