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,1310 +1,1310 @@
1
- <script setup lang="ts">
2
- import type { FieldType } from 'vant'
3
- import type { Numeric } from 'vant/es/utils'
4
- import ImageUploader from '@af-mobile-client-vue3/components/core/ImageUploader/index.vue'
5
- import XGridDropOption from '@af-mobile-client-vue3/components/core/XGridDropOption/index.vue'
6
- import XMultiSelect from '@af-mobile-client-vue3/components/core/XMultiSelect/index.vue'
7
- import XSelect from '@af-mobile-client-vue3/components/core/XSelect/index.vue'
8
- import XLocationPicker from '@af-mobile-client-vue3/components/data/XOlMap/XLocationPicker/index.vue'
9
- import { getConfigByNameAsync, runLogic } from '@af-mobile-client-vue3/services/api/common'
10
- import { post } from '@af-mobile-client-vue3/services/restTools'
11
- import { searchToListOption, searchToOption } from '@af-mobile-client-vue3/services/v3Api'
12
- import { useUserStore } from '@af-mobile-client-vue3/stores/modules/user'
13
- import { getDict } from '@af-mobile-client-vue3/utils/dictUtil'
14
- import { executeStrFunctionByContext } from '@af-mobile-client-vue3/utils/runEvalFunction'
15
- import { areaList } from '@vant/area-data'
16
- import dayjs from 'dayjs/esm/index'
17
- import { debounce } from 'lodash-es'
18
- import {
19
- Area as VanArea,
20
- Button as VanButton,
21
- Calendar as VanCalendar,
22
- Cascader as VanCascader,
23
- Checkbox as VanCheckbox,
24
- CheckboxGroup as vanCheckboxGroup,
25
- DatePicker as VanDatePicker,
26
- Field as VanField,
27
- Picker as VanPicker,
28
- PickerGroup as VanPickerGroup,
29
- Popup as VanPopup,
30
- Radio as VanRadio,
31
- RadioGroup as VanRadioGroup,
32
- Rate as VanRate,
33
- Slider as VanSlider,
34
- Stepper as VanStepper,
35
- Switch as VanSwitch,
36
- TimePicker as VanTimePicker,
37
- } from 'vant'
38
- import { computed, defineEmits, defineModel, defineProps, getCurrentInstance, onBeforeMount, ref, watch } from 'vue'
39
-
40
- const props = defineProps({
41
- attr: {
42
- type: Object,
43
- },
44
- form: {
45
- type: Object,
46
- },
47
- datePickerFilter: {
48
- type: Function,
49
- default: () => true,
50
- },
51
- datePickerFormatter: {
52
- type: Function,
53
- default: (type, val) => val,
54
- },
55
- mode: {
56
- type: String,
57
- default: '查询',
58
- },
59
- serviceName: {
60
- type: String,
61
- default: undefined,
62
- },
63
- // 调用logic获取数据源的追加参数
64
- getDataParams: {
65
- type: Object,
66
- default: undefined,
67
- },
68
- disabled: {
69
- type: Boolean,
70
- default: false,
71
- },
72
- rules: {
73
- type: Object,
74
- default: () => {},
75
- },
76
- // 用 defineModel 替代 modelValue/emit
77
- modelValue: {
78
- type: [String, Number, Boolean, Array, Object],
79
- default: undefined,
80
- },
81
- showLabel: {
82
- type: Boolean,
83
- default: true,
84
- },
85
- // radio/checkbox/select/mul-select 选项数据结构
86
- columnsField: {
87
- type: Object,
88
- default: () => {
89
- return { text: 'label', value: 'value' }
90
- },
91
- },
92
-
93
- })
94
-
95
- const emits = defineEmits(['setForm', 'xFormItemEmitFunc'])
96
-
97
- // 用 defineModel 替代 modelValue/emit
98
- const modelData = defineModel<string | number | boolean | any[] | Record<string, any>>()
99
-
100
- // 获取字典
101
- interface OptionItem {
102
- label: string
103
- value: any
104
- children?: OptionItem[]
105
- }
106
-
107
- // 判断并初始化防抖函数
108
- let debouncedUserLinkFunc: (() => void) | null = null
109
- let debouncedDepLinkFunc: (() => void) | null = null
110
- let debouncedUpdateOptions: (() => void) | null = null
111
-
112
- const { attr, form, mode, serviceName, getDataParams, columnsField } = props
113
- const calendarShow = ref(false)
114
- const option = ref([])
115
- const pickerValue = ref(undefined)
116
- const timePickerValue = ref(undefined)
117
- const area = ref<any>(undefined)
118
- const showPicker = ref(false)
119
- const showDatePicker = ref(false)
120
- const showTimePicker = ref(false)
121
- const showArea = ref(false)
122
- const errorMessage = ref('')
123
- const showTreeSelect = ref(false)
124
- const treeValue = ref('')
125
-
126
- // 登录信息 (可以在配置的动态函数中使用 this.setupState 获取到当前组件内的全部函数和变量 例:this.setupState.userState)
127
- const userState = useUserStore().getLogin()
128
- const currUser = computed(() => userState.f.resources.id)
129
- const userInfo = computed(() => ({
130
- orgId: userState.f.resources.orgid,
131
- userId: userState.f.resources.id,
132
- }))
133
-
134
- // 是否展示当前项
135
- const showItem = ref(true)
136
-
137
- // 当前组件实例(不推荐使用,可能会在后续的版本更迭中调整,暂时用来绑定函数的上下文)
138
- const currInst = getCurrentInstance()
139
-
140
- // 配置中心->表单项变更触发函数
141
- async function dataChangeFunc() {
142
- if (attr.dataChangeFunc) {
143
- await executeStrFunctionByContext(currInst, attr.dataChangeFunc, [props.form, (formData: any) => emits('setForm', formData), attr, null, mode])
144
- }
145
- }
146
- const dataChangeFuncdebounce = debounce(dataChangeFunc, 300)
147
- // 配置中心->表单项展示函数
148
- async function showFormItemFunc() {
149
- if (attr.showFormItemFunc) {
150
- const obj = await executeStrFunctionByContext(currInst, attr.showFormItemFunc, [form, attr, null, mode])
151
- // 判断是 bool 还是 obj 兼容
152
- if (typeof obj === 'boolean') {
153
- showItem.value = obj
154
- }
155
- else if (obj && typeof obj === 'object') {
156
- // obj 是一个对象,并且不是数组
157
- showItem.value = obj?.show
158
- }
159
- }
160
- }
161
- const showFormItemFuncdebounce = debounce(showFormItemFunc, 300)
162
- /**
163
- * 检测是否传入了有效的值
164
- * @returns any
165
- */
166
- function checkModel(val = props.modelValue) {
167
- if (val === null || val === undefined || val === '')
168
- return false
169
- if (Array.isArray(val))
170
- return val.length > 0
171
- return true
172
- }
173
- /**
174
- * 获取表单项的默认值
175
- * @returns any
176
- */
177
- function getDefaultValue() {
178
- const val = props.modelValue
179
-
180
- // 如果有有效值,直接返回
181
- if (checkModel(val)) {
182
- return val
183
- }
184
-
185
- // 根据类型获取默认值
186
- const getDefaultByType = () => {
187
- const def = mode !== '查询' ? attr.formDefault : attr.queryFormDefault
188
-
189
- if (['treeSelect', 'select', 'checkbox'].includes(attr.type) && ['curOrgId', 'curDepId', 'curUserId'].includes(def)) {
190
- if (def === 'curOrgId') {
191
- if (attr.type === 'treeSelect') {
192
- treeValue.value = userState.f.resources.orgs
193
- }
194
- return attr.type === 'select' ? userState.f.resources.orgid : [userState.f.resources.orgid]
195
- }
196
- if (def === 'curDepId') {
197
- if (attr.type === 'treeSelect') {
198
- treeValue.value = userState.f.resources.deps
199
- }
200
- return attr.type === 'select' ? userState.f.resources.depids : [userState.f.resources.depids]
201
- }
202
- if (def === 'curUserId') {
203
- if (attr.type === 'treeSelect') {
204
- treeValue.value = userState.f.resources.name
205
- }
206
- return attr.type === 'select' ? userState.f.resources.id : [userState.f.resources.id]
207
- }
208
- }
209
-
210
- // 数组类型默认值
211
- const arrayTypes = ['uploader', 'checkbox', 'file', 'area', 'image', 'treeSelect']
212
- if (arrayTypes.includes(attr.type)) {
213
- return def !== undefined ? def : []
214
- }
215
-
216
- // 特殊类型默认值
217
- const specialDefaults = {
218
- switch: false,
219
- stepper: 1,
220
- addressSearch: val,
221
- }
222
-
223
- if (specialDefaults[attr.type] !== undefined) {
224
- return specialDefaults[attr.type]
225
- }
226
-
227
- // 日期时间类型
228
- const dateTypes = ['rangePicker', 'yearPicker', 'monthPicker', 'yearRangePicker', 'monthRangePicker', 'datePicker', 'timePicker']
229
- if (dateTypes.includes(attr.type)) {
230
- return getDateRange({
231
- type: attr.type,
232
- formDefault: attr.formDefault ?? '',
233
- queryFormDefault: attr.queryFormDefault ?? '',
234
- queryType: attr.queryType,
235
- queryValueFormat: attr.queryValueFormat,
236
- name: attr.name ?? '',
237
- }) ?? []
238
- }
239
-
240
- // 其他类型(字符串、数字等)
241
- return def ?? ''
242
- }
243
-
244
- return getDefaultByType()
245
- }
246
-
247
- // 初始化日期组件初始值,组件自定义格式显示值和实际值(日期相关初始化都在此函数中操作)
248
- function getDateRange({
249
- type,
250
- formDefault: defaultValue,
251
- queryFormDefault,
252
- queryType,
253
- queryValueFormat: defaultFormat,
254
- name,
255
- }: {
256
- type: string
257
- formDefault: string
258
- queryFormDefault: string
259
- queryType?: string
260
- queryValueFormat?: string
261
- name: string
262
- }): string | [string, string] | undefined {
263
- const formatMap: Record<string, string> = {
264
- yearPicker: 'YYYY',
265
- yearRangePicker: 'YYYY',
266
- monthPicker: 'YYYY-MM',
267
- monthRangePicker: 'YYYY-MM',
268
- datePicker: 'YYYY-MM-DD HH:mm:ss',
269
- rangePicker: 'YYYY-MM-DD HH:mm:ss',
270
- }
271
- if (mode) {
272
- const format = defaultFormat || formatMap[type]
273
- const val = mode === '查询' ? queryFormDefault : defaultValue
274
- let start: string, end: string
275
- switch (val) {
276
- case 'curYear':
277
- start = dayjs().startOf('year').format(format)
278
- end = dayjs().endOf('year').format(format)
279
- break
280
- case 'curMonth':
281
- start = dayjs().startOf('month').format(format)
282
- end = dayjs().endOf('month').format(format)
283
- break
284
- case 'curDay':
285
- start = dayjs().startOf('day').format(format)
286
- end = dayjs().endOf('day').format(format)
287
- break
288
- case 'curTime':
289
- start = dayjs().format(format)
290
- end = dayjs().format(format)
291
- break
292
- default:
293
- return undefined
294
- }
295
- if (['monthPicker', 'yearPicker', 'datePicker'].includes(type)) {
296
- if (mode !== '查询') {
297
- if (queryType === 'BETWEEN') {
298
- return [start, end]
299
- }
300
- if (name.includes('开始') || name.includes('起始')) {
301
- return start
302
- }
303
- else {
304
- return end
305
- }
306
- }
307
- else {
308
- return start
309
- }
310
- }
311
- // rangePicker组件表单显示的值
312
- if (mode === '查询' && type === 'rangePicker') {
313
- pickerValue.value = `${start} ~ ${end}`
314
- }
315
- return mode !== '查询' ? start : [start, end]
316
- }
317
- else {
318
- return undefined
319
- }
320
- }
321
-
322
- // 监听 props.form 的变化
323
- watch(
324
- () => props.form,
325
- (newVal, oldVal) => {
326
- // 如果是从函数获取 options
327
- if (props.attr.keyName && (props.attr.keyName.toString().includes('async ') || props.attr.keyName.toString().includes('function'))) {
328
- debouncedUpdateOptions()
329
- }
330
- if (props.attr.showFormItemFunc) {
331
- showFormItemFuncdebounce()
332
- }
333
- },
334
- { deep: true },
335
- )
336
-
337
- // 监听 modelData 的变化,调用 dataChangeFunc
338
- watch(
339
- () => modelData.value,
340
- (newVal, oldVal) => {
341
- // 避免初始化时的调用
342
- if (newVal !== oldVal) {
343
- dataChangeFuncdebounce()
344
- }
345
- },
346
- { deep: true },
347
- )
348
-
349
- // 监听 option.value 的变化
350
- watch(
351
- () => option.value,
352
- (newOption) => {
353
- if (attr.type === 'treeSelect' && newOption && newOption.length > 0) {
354
- // 你可以在这里调用 findOptionInTree 函数来查找默认值对应的 label
355
- const result = findOptionInTree(option.value, modelData.value)
356
- if (attr.type === 'treeSelect' && result)
357
- treeValue.value = result.label
358
- }
359
- },
360
- )
361
-
362
- function updateFile(files, _index) {
363
- modelData.value = files
364
- }
365
-
366
- // 表单校验的类型校验
367
- function formTypeCheck(attr, value) {
368
- if (mode === '查询')
369
- return
370
- if (!attr.rule || !attr.rule.required || attr.rule.required === 'false')
371
- return
372
- switch (attr.rule.type) {
373
- case 'string':
374
- if (value.length === 0) {
375
- errorMessage.value = `请输入${attr.name}`
376
- return
377
- }
378
- break
379
- case 'number':
380
- if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(value)) {
381
- errorMessage.value = `${attr.name}必须为数字`
382
- return
383
- }
384
- break
385
- case 'boolean':
386
- case 'array':
387
- case 'regexp':
388
- if (value) {
389
- errorMessage.value = ''
390
- return
391
- }
392
- break
393
- case 'integer':
394
- if (!/^-?\d+$/.test(value)) {
395
- errorMessage.value = `${attr.name}必须为整数`
396
- return
397
- }
398
- break
399
- case 'float':
400
- if (!/^-?\d+\.\d+$/.test(value)) {
401
- errorMessage.value = `${attr.name}必须为小数`
402
- return
403
- }
404
- break
405
- case 'email':
406
- if (!/^[\w.%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i.test(value)) {
407
- errorMessage.value = `请输入正确的邮箱地址`
408
- return
409
- }
410
- break
411
- case 'idNumber':
412
- if (!/^(?:\d{15}|\d{17}[0-9X])$/.test(value)) {
413
- errorMessage.value = `请输入正确的身份证号码`
414
- return
415
- }
416
- break
417
- case 'userPhone':
418
- if (!/^1[3-9]\d{9}$/.test(value)) {
419
- errorMessage.value = `请输入正确的手机号码`
420
- return
421
- }
422
- break
423
- case 'landlineNumber':
424
- if (!/^0\d{2,3}[-\s]?\d{7,8}$/.test(value)) {
425
- errorMessage.value = `请输入正确的座机号码`
426
- return
427
- }
428
- break
429
- case 'greaterThanZero':
430
- if (!/^(?:[1-9]\d*(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(value)) {
431
- errorMessage.value = `请输入一个大于0的数字`
432
- return
433
- }
434
- break
435
- case 'greaterThanOrEqualZero':
436
- if (!/^(?:\d+|\d*\.\d+)$/.test(value)) {
437
- errorMessage.value = `请输入一个大于等于0的数字`
438
- return
439
- }
440
- break
441
- case 'stringLength':
442
- if (!(value.length >= attr.rule.minLen && value.length < attr.rule.maxLen)) {
443
- errorMessage.value = `长度必须在${attr.rule.minLen}~${attr.rule.maxLen}之间`
444
- return
445
- }
446
- break
447
- case 'customJs':
448
- // eslint-disable-next-line no-case-declarations
449
- const funcStr = attr.rule.customValidatorFunc
450
- // 输入的数据是否符合条件
451
- // eslint-disable-next-line no-case-declarations
452
- const status = ref(true)
453
- // 提取函数体部分
454
- // eslint-disable-next-line no-case-declarations
455
- const funcBodyMatch = funcStr.match(/function\s*\(.*?\)\s*\{([\s\S]*)\}/)
456
- if (!funcBodyMatch)
457
- throw new Error('自定义校验函数不合法')
458
- // eslint-disable-next-line no-case-declarations
459
- const funcBody = funcBodyMatch[1].trim() // 提取函数体
460
- // 使用 new Function 创建函数
461
- // eslint-disable-next-line no-new-func,no-case-declarations
462
- const customValidatorFunc = new Function('rule', 'value', 'callback', 'form', 'attr', 'util', funcBody)
463
- // 定义 callback 函数
464
- // eslint-disable-next-line no-case-declarations
465
- const callback = (error) => {
466
- if (error) {
467
- errorMessage.value = `${error}`
468
- status.value = false // 表示有错误发生
469
- }
470
- }
471
- // 调用自定义校验函数
472
- customValidatorFunc(
473
- attr.rule,
474
- value,
475
- callback,
476
- form,
477
- attr,
478
- {}, // util 对象(可以根据需要传递)
479
- )
480
- if (!status.value)
481
- return
482
- break
483
- default:
484
- errorMessage.value = ''
485
- break
486
- }
487
- errorMessage.value = ''
488
- }
489
-
490
- onBeforeMount(() => {
491
- init()
492
- modelData.value = getDefaultValue()
493
- showFormItemFunc()
494
- dataChangeFunc()
495
- if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString().endsWith(']联动人员'))
496
- debouncedUserLinkFunc = debounce(() => updateResOptions('人员'), 200)
497
-
498
- if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString().endsWith(']联动部门'))
499
- debouncedDepLinkFunc = debounce(() => updateResOptions('部门'), 200)
500
-
501
- if (attr.keyName && (attr?.keyName?.toString().indexOf('async ') !== -1 || attr?.keyName?.toString()?.indexOf('function') !== -1)) {
502
- debouncedUpdateOptions = debounce(updateOptions, 200)
503
- }
504
- })
505
- // 是否展示表单左侧label文字
506
- const labelData = computed(() => {
507
- return props.showLabel ? attr.name : null
508
- })
509
- // 是否展示表单左侧label文字
510
- const labelAlign = computed(() => {
511
- return attr.labelAlign ? attr.labelAlign : 'left'
512
- })
513
- // 是否只读
514
- const readonly = computed(() => {
515
- return attr.addOrEdit === 'readonly' || mode === '预览'
516
- })
517
- // 提示内容
518
- const placeholder = computed(() => {
519
- if (attr.addOrEdit === 'readonly')
520
- return '暂无内容 ~ '
521
- else
522
- return attr.placeholder ? attr.placeholder : `请选择${attr.name}`
523
- })
524
-
525
- const formatDate = date => `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
526
-
527
- function onCalendarConfirm(values) {
528
- modelData.value = [formatDate(values[0]), formatDate(values[1])]
529
- pickerValue.value = `${formatDate(values[0])} ~ ${formatDate(values[1])}`
530
- calendarShow.value = false
531
- }
532
-
533
- // js 函数作为数据源
534
- async function updateOptions() {
535
- if (attr.keyName && (attr.keyName.toString().includes('async ') || attr.keyName.toString().includes('function '))) {
536
- option.value = await executeStrFunctionByContext(this, attr.keyName, [props.form, runLogic, props.mode, getConfigByNameAsync, post])
537
- }
538
- }
539
-
540
- function init() {
541
- if (attr.keyName && typeof attr.keyName === 'string') {
542
- if (attr.keyName && attr.keyName.includes('logic@')) {
543
- getData({}, (res) => {
544
- option.value = res
545
- initRadioValue()
546
- })
547
- }
548
- else if (attr.keyName && attr.keyName.includes('config@')) {
549
- const configName = attr.keyName.substring(7)
550
- getDict(configName, (result) => {
551
- if (result)
552
- option.value = result
553
- }, serviceName)
554
- }
555
- else if (attr.keyName && attr.keyName.includes('search@')) {
556
- let source = attr.keyName.substring(7)
557
- const userid = currUser.value
558
- let roleName = 'roleName'
559
- if (source.startsWith('根据角色[') && source.endsWith(']获取人员')) {
560
- const startIndex = source.indexOf('[') + 1
561
- const endIndex = source.indexOf(']', startIndex)
562
- roleName = source.substring(startIndex, endIndex)
563
- source = '根据角色获取人员'
564
- }
565
- const searchData = { source, userid, roleName }
566
- if (source.startsWith('根据表单项[') && source.endsWith(']联动人员'))
567
- updateResOptions('人员')
568
- else if (source.startsWith('根据表单项[') && source.endsWith(']联动部门'))
569
- updateResOptions('部门')
570
- else if (attr.type === 'select' || attr.type === 'checkbox')
571
- searchToListOption(searchData, res => getDataCallback(res))
572
- else
573
- searchToOption(searchData, res => getDataCallback(res))
574
- }
575
- else if (attr.keyName.toString().includes('async ') || attr.keyName.toString().includes('function ')) {
576
- updateOptions()
577
- }
578
- else {
579
- initRadioValue()
580
- }
581
- }
582
- }
583
-
584
- function getDataCallback(res) {
585
- option.value = res.map(item => ({
586
- ...item,
587
- value: item.label, // 将value设置为label的值
588
- }))
589
- if (attr.type === 'radio')
590
- initRadioValue()
591
- }
592
-
593
- async function updateResOptions(type) {
594
- if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString()?.endsWith(`]联动${type}`)) {
595
- const searchData = { source: `获取${type}`, userid: currUser.value }
596
- const startIndex = attr.keyName.indexOf('[') + 1
597
- const endIndex = attr.keyName.indexOf(']', startIndex)
598
- const formModel = attr.keyName.substring(startIndex, endIndex).replace('.', '_')
599
- const formModelData = form[formModel]
600
- if (formModel?.length && formModelData?.length) {
601
- await searchToListOption(searchData, (res) => {
602
- getDataCallback(res.filter((h) => {
603
- return formModelData['0'] === h.f_organization_id || formModelData['0'] === h.f_department_id || formModelData['0'] === h.parentid
604
- // if (formModel.indexOf('org') > -1) {
605
- // return formModelData?.includes(h.orgid || h.f_organization_id || h.parentid)
606
- // } else {
607
- // return formModelData?.includes(h?.parentid)
608
- // }
609
- }))
610
- })
611
- }
612
- }
613
- }
614
-
615
- function initRadioValue() {
616
- if ((mode === '新增' || mode === '修改') && attr.type === 'radio' && !props.modelValue) {
617
- if (attr.keys && attr.keys.length > 0)
618
- modelData.value = attr.keys[0].value
619
- else if (option.value && option.value.length > 0)
620
- modelData.value = option.value[0].value
621
- }
622
- }
623
-
624
- function getData(value, callback) {
625
- if (value !== '') {
626
- const logicName = attr.keyName
627
- const logic = logicName.substring(6)
628
- // 调用logic前设置参数
629
- if (getDataParams && getDataParams[attr.model])
630
- Object.assign(value, getDataParams[attr.model])
631
- Object.assign(value, userInfo.value)
632
- runLogic(logic, value, serviceName).then((res) => {
633
- callback(res)
634
- })
635
- }
636
- }
637
-
638
- // 已废弃 不进行维护
639
- function onPickerConfirm({ selectedOptions }) {
640
- showPicker.value = false
641
- modelData.value = selectedOptions[0].text
642
- }
643
-
644
- // 日期时间选择数据
645
- const dateTimePickerValue = ref<any>({})
646
- function showDataTimePicker() {
647
- if (props.modelValue && typeof props.modelValue === 'string') {
648
- // 拆分日期和时间
649
- const [dateStr, timeStr] = props.modelValue.split(' ')
650
- // 拆分日期部分
651
- const date = dateStr.split('-')
652
- // 拆分时间部分
653
- const time = timeStr.split(':')
654
- // 赋值给 dateTimePickerValue
655
- dateTimePickerValue.value = {
656
- date,
657
- time,
658
- }
659
- }
660
- else {
661
- dateTimePickerValue.value = {
662
- date: formatDate(new Date()).split('-'),
663
- time: ['00', '00', '00'],
664
- }
665
- }
666
- showDatePicker.value = true
667
- }
668
-
669
- function onDatePickerConfirm({ selectedValues }) {
670
- showDatePicker.value = false
671
- modelData.value = selectedValues.join('-')
672
- }
673
-
674
- // 已废弃 不进行维护
675
- function onTimePickerConfirm({ selectedValues }) {
676
- showTimePicker.value = false
677
- timePickerValue.value = selectedValues.join(':')
678
- modelData.value = timePickerValue.value
679
- }
680
-
681
- // 没人用到本次先不动。后续需要看这个组件需要怎么使用
682
- function onAreaConfirm({ selectedOptions }) {
683
- area.value = `${selectedOptions[0].text}-${selectedOptions[1].text}-${selectedOptions[2].text}`
684
- showArea.value = false
685
- modelData.value = [{
686
- province: selectedOptions[0].text,
687
- city: selectedOptions[1].text,
688
- district: selectedOptions[2].text,
689
- }]
690
- }
691
-
692
- function onDateTimePickerConfirm() {
693
- showDatePicker.value = false
694
- const dateStr = dateTimePickerValue.value.date.join('-')
695
- const timeStr = dateTimePickerValue.value.time.join(':')
696
- modelData.value = `${dateStr} ${timeStr}`
697
- }
698
-
699
- function onPickerCancel() {
700
- showDatePicker.value = false
701
- }
702
-
703
- const showAddressPicker = ref(false)
704
- const addressValue = ref('')
705
-
706
- // XLocationPicker 默认加载的中心点
707
- const defaultMapCenter = computed(() => {
708
- const lonLat = form[`${attr.model}_lon_lat`]
709
- if (lonLat && typeof lonLat === 'string' && lonLat.includes(',')) {
710
- const [lon, lat] = lonLat.split(',').map(Number)
711
- if (!Number.isNaN(lon) && !Number.isNaN(lat)) {
712
- return [lon, lat] as [number, number]
713
- }
714
- }
715
- return undefined
716
- })
717
-
718
- // 处理地址选择器确认
719
- function handleAddressConfirm(location) {
720
- // 构造新的数据格式
721
- const formData = {
722
- [`${attr.model}_lon_lat`]: `${location.longitude},${location.latitude}`,
723
- [attr.model]: location.address,
724
- }
725
- // 更新表单数据
726
- emits('setForm', formData)
727
- showAddressPicker.value = false
728
- }
729
-
730
- // 重置方法,供父组件调用
731
- function reset() {
732
- modelData.value = null
733
- errorMessage.value = ''
734
- treeValue.value = null
735
- area.value = null
736
- pickerValue.value = null
737
- }
738
-
739
- defineExpose({
740
- reset,
741
- })
742
-
743
- // 数据处理
744
- function cleanEmptyChildren(options, fieldNames = { text: 'label', value: 'value', children: 'children' }) {
745
- if (!Array.isArray(options))
746
- return options
747
-
748
- const childrenKey = fieldNames.children || 'children'
749
-
750
- return options.map((option) => {
751
- // 深拷贝选项,避免修改原始数据
752
- const newOption = { ...option }
753
-
754
- // 如果存在children属性且是空数组
755
- if (newOption[childrenKey] && Array.isArray(newOption[childrenKey]) && newOption[childrenKey].length === 0) {
756
- delete newOption[childrenKey]
757
- }
758
- // 如果存在children属性且非空,则递归处理
759
- else if (newOption[childrenKey] && Array.isArray(newOption[childrenKey])) {
760
- newOption[childrenKey] = cleanEmptyChildren(newOption[childrenKey], fieldNames)
761
- }
762
- return newOption
763
- })
764
- }
765
-
766
- // 级联选择完成事件
767
- function onTreeSelectFinish({ selectedOptions }) {
768
- const index = selectedOptions.length - 1
769
- treeValue.value = selectedOptions[index].label
770
- if (mode === '查询') {
771
- modelData.value = [selectedOptions[index].value]
772
- }
773
- else {
774
- modelData.value = selectedOptions[index].value
775
- }
776
- showTreeSelect.value = false
777
- }
778
-
779
- function emitFunc(func, data) {
780
- emits('xFormItemEmitFunc', func, data, data?.model ? form[data.model] : form)
781
- }
782
-
783
- function findOptionInTree(options, value) {
784
- // 在当前层级查找
785
- const foundItem = options.find(item => item[columnsField.value] === value)
786
- if (foundItem) {
787
- return foundItem
788
- }
789
- // 递归查找子级
790
- for (const item of options) {
791
- if (item.children?.length) {
792
- const foundInChildren = findOptionInTree(item.children, value)
793
- if (foundInChildren)
794
- return foundInChildren
795
- }
796
- }
797
-
798
- return null
799
- }
800
- </script>
801
-
802
- <template>
803
- <div>
804
- <!-- switch开关 -->
805
- <VanField
806
- v-if="attr.type === 'switch' && showItem"
807
- name="switch"
808
- :label="labelData"
809
- :label-align="labelAlign"
810
- :input-align="attr.inputAlign ? attr.inputAlign : 'right'"
811
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
812
- :required="attr.rule.required === 'true'"
813
- >
814
- <template #input>
815
- <VanSwitch v-model="modelData" />
816
- </template>
817
- </VanField>
818
-
819
- <!-- 复选框 -->
820
- <!-- <VanField
821
- v-if="attr.type === 'checkbox'"
822
- name="checkbox"
823
- :label="labelData"
824
- >
825
- <template #input>
826
- <VanCheckbox v-model="modelData" shape="square" />
827
- </template>
828
- </VanField> -->
829
-
830
- <!-- 多选框-checkbox-复选框组 -->
831
- <template v-if="attr.type === 'checkbox' && showItem">
832
- <!-- 勾选 -->
833
- <VanField
834
- v-if="attr.showMode === 'checkbox' && mode !== '查询'"
835
- name="checkboxGroup"
836
- :label="labelData"
837
- :label-align="labelAlign"
838
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
839
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
840
- :required="attr.rule.required === 'true'"
841
- >
842
- <template #input>
843
- <van-checkbox-group v-model="modelData as any[]" direction="horizontal" shape="square" :disabled="readonly">
844
- <VanCheckbox v-for="(item, index) in option" :key="index" style="padding: 2px" :name="item[columnsField.value]" :shape="rules?.[attr.model].shape" :value="item[columnsField.value]">
845
- {{ item[columnsField.text] }}
846
- </VanCheckbox>
847
- </van-checkbox-group>
848
- </template>
849
- </VanField>
850
- <VanField
851
- v-if="attr.showMode === 'checkbox' && mode === '查询'"
852
- name="checkboxGroup"
853
- :label="labelData"
854
- :label-align="labelAlign"
855
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
856
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
857
- >
858
- <template #input>
859
- <XGridDropOption
860
- v-model="(modelData as string[])"
861
- :column-num="labelData ? 3 : 4"
862
- :multiple="true"
863
- :columns="option"
864
- />
865
- </template>
866
- </VanField>
867
- <!-- 下拉 -->
868
- <XMultiSelect
869
- v-if="attr.showMode === 'select' && mode === '查询'"
870
- v-model="modelData"
871
- :label="labelData"
872
- :readonly="readonly"
873
- :placeholder="placeholder"
874
- :columns="option"
875
- :option="attr.option ? attr.option : columnsField"
876
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
877
- :required="attr.rule.required === 'true'"
878
- />
879
- </template>
880
-
881
- <!-- 单选框 -->
882
- <VanField
883
- v-if="attr.type === 'radio' && mode !== '查询' && showItem"
884
- name="radio"
885
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
886
- :label="labelData"
887
- :label-align="labelAlign"
888
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
889
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
890
- :required="attr.rule.required === 'true'"
891
- >
892
- <template #input>
893
- <VanRadioGroup v-model="modelData" direction="horizontal" :disabled="readonly">
894
- <VanRadio v-for="(item, index) in option" :key="index" style="padding: 2px" :name="item[columnsField.value]" :value="item[columnsField.value]">
895
- {{ item[columnsField.text] }}
896
- </VanRadio>
897
- </VanRadioGroup>
898
- </template>
899
- </VanField>
900
-
901
- <!-- 单选框-查询 -->
902
- <VanField
903
- v-if="attr.type === 'radio' && mode === '查询' && showItem"
904
- name="radio"
905
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
906
- :label="labelData"
907
- :label-align="labelAlign"
908
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
909
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
910
- >
911
- <template #input>
912
- <XGridDropOption
913
- v-model="(modelData as string)"
914
- :column-num="labelData ? 3 : 4"
915
- :columns="option"
916
- />
917
- </template>
918
- </VanField>
919
-
920
- <!-- 步进器 -->
921
- <VanField
922
- v-if="attr.type === 'stepper' && showItem"
923
- name="stepper"
924
- :label="labelData"
925
- :label-align="labelAlign"
926
- :input-align="attr.inputAlign ? attr.inputAlign : 'center'"
927
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
928
- :required="attr.rule.required === 'true'"
929
- >
930
- <template #input>
931
- <VanStepper v-model="modelData as any" :disabled="readonly" />
932
- </template>
933
- </VanField>
934
-
935
- <!-- 评分 -->
936
- <VanField
937
- v-if="attr.type === 'rate' && showItem"
938
- name="rate"
939
- :label="labelData"
940
- :label-align="labelAlign"
941
- :input-align="attr.inputAlign ? attr.inputAlign : 'center'"
942
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
943
- :required="attr.rule.required === 'true'"
944
- >
945
- <template #input>
946
- <VanRate v-model="modelData as number" :size="25" :readonly="readonly" void-color="#eee" void-icon="star" color="#ffd21e" />
947
- </template>
948
- </VanField>
949
-
950
- <!-- 滑块 -->
951
- <VanField
952
- v-if="attr.type === 'slider' && showItem"
953
- name="slider"
954
- :label="labelData"
955
- :label-align="labelAlign"
956
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
957
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
958
- :required="attr.rule.required === 'true'"
959
- >
960
- <template #input>
961
- <VanSlider v-model="modelData as number" :readonly="readonly" />
962
- </template>
963
- </VanField>
964
-
965
- <!-- 文件上传 -->
966
- <!-- 图片上传, 手机端拍照 -->
967
- <VanField
968
- v-if="(attr.type === 'image' || attr.type === 'file') && showItem"
969
- name="image"
970
- :label="labelData"
971
- :label-align="labelAlign"
972
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
973
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
974
- :required="attr.rule.required === 'true'"
975
- >
976
- <template #input>
977
- <ImageUploader
978
- upload-mode="server"
979
- :image-list="(modelData as any[])"
980
- authority="admin"
981
- :attr="attr"
982
- :mode="props.mode"
983
- @update-file-list="updateFile"
984
- />
985
- </template>
986
- </VanField>
987
-
988
- <!-- 选择器 琉璃中不存在,不进行维护后续将删除 -->
989
- <VanField
990
- v-if="attr.type === 'picker' && showItem"
991
- v-model="pickerValue"
992
- name="picker"
993
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
994
- :label="labelData"
995
- :label-align="labelAlign"
996
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
997
- readonly
998
- is-link
999
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1000
- @click="readonly ? null : showPicker = true"
1001
- />
1002
- <VanPopup v-model:show="showPicker" round position="bottom" teleport="body" overlay-class="date-picker-overlay">
1003
- <VanPicker
1004
- v-model="(modelData as Numeric[])"
1005
- :title="attr.name"
1006
- :columns="attr.selectKey"
1007
- :readonly="readonly"
1008
- :columns-field-names="attr.customFieldName ? attr.customFieldName : { text: 'text', value: 'value', children: 'children' }"
1009
- :confirm-button-text="attr.confirmButtonText || attr.confirmButtonText === '' ? attr.confirmButtonText : '确认'"
1010
- :cancel-button-text="attr.cancelButtonText || attr.cancelButtonText === '' ? attr.cancelButtonText : '取消'"
1011
- @cancel="showPicker = false"
1012
- @confirm="onPickerConfirm"
1013
- />
1014
- </VanPopup>
1015
-
1016
- <!-- 日历选择-查询 -->
1017
- <VanField
1018
- v-if="attr.type === 'rangePicker' && mode === '查询' && showItem"
1019
- v-model="(pickerValue as string | number)"
1020
- is-link
1021
- readonly
1022
- name="rangePicker"
1023
- :label="labelData"
1024
- :label-align="labelAlign"
1025
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1026
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1027
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1028
- @click="calendarShow = true"
1029
- />
1030
- <VanCalendar
1031
- v-model:show="calendarShow"
1032
- switch-mode="year-month"
1033
- type="range"
1034
- teleport="body"
1035
- overlay-class="date-picker-overlay"
1036
- :show-confirm="attr.showConfirm"
1037
- @confirm="onCalendarConfirm"
1038
- />
1039
-
1040
- <!-- 日期选择-非查询 -->
1041
- <VanField
1042
- v-if="(attr.type === 'datePicker' || attr.type === 'rangePicker') && mode !== '查询' && showItem"
1043
- v-model="(modelData as string | number)"
1044
- name="datePicker"
1045
- :label="labelData"
1046
- :label-align="labelAlign"
1047
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1048
- readonly
1049
- :is-link="true"
1050
- :placeholder="placeholder"
1051
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1052
- :required="attr.rule.required === 'true'"
1053
- @click="readonly ? null : showDataTimePicker()"
1054
- />
1055
- <VanPopup v-model:show="showDatePicker" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1056
- <VanPickerGroup
1057
- :title="attr.name"
1058
- :tabs="['选择日期', '选择时间']"
1059
- next-step-text="下一步"
1060
- :confirm-button-text="attr.confirmButtonText ? attr.confirmButtonText : '确认'"
1061
- :cancel-button-text="attr.cancelButtonText ? attr.cancelButtonText : '取消'"
1062
- @confirm="onDateTimePickerConfirm"
1063
- @cancel="onPickerCancel"
1064
- >
1065
- <VanDatePicker
1066
- v-model="dateTimePickerValue.date"
1067
- :columns-type="attr.dateColumnsType || ['year', 'month', 'day']"
1068
- />
1069
- <VanTimePicker
1070
- v-model="dateTimePickerValue.time"
1071
- :columns-type="attr.timeColumnsType || ['hour', 'minute', 'second']"
1072
- :min-time="attr.minTime ? attr.minTime : '00:00:00'"
1073
- :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
1074
- />
1075
- </VanPickerGroup>
1076
- </VanPopup>
1077
-
1078
- <!-- 日期选择-查询 -->
1079
- <VanField
1080
- v-if="attr.type === 'datePicker' && mode === '查询' && showItem"
1081
- v-model="(modelData as string | number)"
1082
- name="datePicker"
1083
- :label="labelData"
1084
- :label-align="labelAlign"
1085
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1086
- readonly
1087
- :is-link="true"
1088
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1089
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1090
- @click="showDatePicker = true"
1091
- />
1092
- <VanPopup v-model:show="showDatePicker" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1093
- <VanPickerGroup
1094
- :title="attr.name"
1095
- :tabs="['选择日期', '选择时间']"
1096
- next-step-text="下一步"
1097
- :confirm-button-text="attr.confirmButtonText ? attr.confirmButtonText : '确认'"
1098
- :cancel-button-text="attr.cancelButtonText ? attr.cancelButtonText : '取消'"
1099
- @confirm="onDateTimePickerConfirm"
1100
- @cancel="onPickerCancel"
1101
- >
1102
- <VanDatePicker
1103
- v-model="dateTimePickerValue.date"
1104
- :columns-type="attr.dateColumnsType || ['year', 'month', 'day']"
1105
- />
1106
- <VanTimePicker
1107
- v-model="dateTimePickerValue.time"
1108
- :columns-type="attr.timeColumnsType || ['hour', 'minute', 'second']"
1109
- :min-time="attr.minTime ? attr.minTime : '00:00:00'"
1110
- :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
1111
- />
1112
- </VanPickerGroup>
1113
- </VanPopup>
1114
-
1115
- <!-- 时间选择 --该配置未在pc找到不进行维护 后续将删除 -->
1116
- <VanField
1117
- v-if="attr.type === 'timePicker' && showItem"
1118
- v-model="timePickerValue"
1119
- name="timePicker"
1120
- is-link
1121
- readonly
1122
- :placeholder="attr.placeholder"
1123
- :label="labelData"
1124
- :label-align="labelAlign"
1125
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1126
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
1127
- @click="showTimePicker = true"
1128
- />
1129
- <VanPopup v-model:show="showTimePicker" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1130
- <VanTimePicker
1131
- v-model="modelData as string[]"
1132
- :title="attr.name"
1133
- :columns-type="attr.columnsType ? attr.columnsType : ['hour', 'minute', 'second']"
1134
- :min-time="attr.minTime ? attr.minTime : '00:00:00'"
1135
- :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
1136
- :readonly="readonly"
1137
- @cancel="showTimePicker = false"
1138
- @confirm="onTimePickerConfirm"
1139
- />
1140
- </VanPopup>
1141
-
1142
- <!-- 省市区选择 -->
1143
- <VanField
1144
- v-if="(attr.type === 'area' || attr.type === 'citySelect') && showItem"
1145
- v-model="area"
1146
- name="area"
1147
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1148
- is-link
1149
- readonly
1150
- :label="labelData"
1151
- :label-align="labelAlign"
1152
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1153
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
1154
- :required="attr.rule.required === 'true'"
1155
- @click="readonly ? null : showArea = true"
1156
- />
1157
- <VanPopup v-model:show="showArea" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1158
- <VanArea
1159
- v-model="modelData as string" :title="attr.name" :area-list="areaList"
1160
- @confirm="onAreaConfirm"
1161
- @cancel="showArea = false"
1162
- />
1163
- </VanPopup>
1164
-
1165
- <!-- 单选下拉列表 -->
1166
- <XSelect
1167
- v-if="attr.type === 'select' && showItem"
1168
- v-model="modelData"
1169
- :label="labelData"
1170
- :readonly="readonly"
1171
- clearable
1172
- :placeholder="placeholder"
1173
- :columns="option"
1174
- :option="attr.option ? attr.option : columnsField"
1175
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1176
- :required="attr.rule.required === 'true'"
1177
- />
1178
-
1179
- <!-- 文本区域 -->
1180
- <VanField
1181
- v-if="attr.type === 'textarea' && showItem"
1182
- v-model="(modelData as string)"
1183
- rows="3"
1184
- autosize
1185
- :label="labelData"
1186
- :label-align="labelAlign"
1187
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1188
- type="textarea"
1189
- :readonly="readonly"
1190
- :maxlength="attr.maxlength ? attr.maxlength : 200"
1191
- :placeholder="attr.placeholder ? attr.placeholder : `请输入${attr.name}`"
1192
- show-word-limit
1193
- :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
1194
- :required="attr.rule.required === 'true'"
1195
- />
1196
-
1197
- <!-- 文本输入框 -->
1198
- <VanField
1199
- v-if="(attr.type === 'input' || attr.type === 'intervalPicker') && showItem"
1200
- v-model="(modelData as string)"
1201
- style="align-items: center"
1202
- :label="labelData"
1203
- :label-align="labelAlign"
1204
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1205
- :type="attr.type as FieldType"
1206
- :readonly="readonly"
1207
- :disabled="attr.disabled"
1208
- :placeholder="placeholder"
1209
- :error-message="errorMessage"
1210
- :clearable="attr.clearable"
1211
- :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
1212
- :required="attr.rule.required === 'true'"
1213
- @blur="() => formTypeCheck(attr, modelData as string)"
1214
- >
1215
- <template #input>
1216
- <input
1217
- :value="modelData"
1218
- :readonly="readonly"
1219
- class="van-field__control"
1220
- :placeholder="placeholder"
1221
- style="flex: 1; min-width: 0;"
1222
- @input="e => modelData = (e.target as HTMLInputElement).value"
1223
- @blur="() => formTypeCheck(attr, modelData as string)"
1224
- >
1225
- <VanButton
1226
- v-if="attr.inputOnAfterName && attr.inputOnAfterFunc && !attr.inputOnAfterName.includes('|')"
1227
- class="action-btn"
1228
- round
1229
- type="primary"
1230
- size="small"
1231
- @click="emitFunc(attr.inputOnAfterFunc, attr)"
1232
- >
1233
- {{ attr.inputOnAfterName }}
1234
- </VanButton>
1235
- </template>
1236
- </VanField>
1237
-
1238
- <!-- 地址选择器 -->
1239
- <VanField
1240
- v-if="attr.type === 'addressSearch' && showItem"
1241
- v-model="modelData as string"
1242
- name="addressSearch"
1243
- :label="labelData"
1244
- :label-align="labelAlign"
1245
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1246
- readonly
1247
- is-link
1248
- :placeholder="placeholder"
1249
- :rules="[{ required: attr.rule.required === 'true', message: '请选择地址' }]"
1250
- :required="attr.rule.required === 'true'"
1251
- @click="readonly ? null : showAddressPicker = true"
1252
- />
1253
- <VanPopup
1254
- v-model:show="showAddressPicker"
1255
- position="bottom"
1256
- :style="{ height: '80vh' }"
1257
- teleport="body"
1258
- overlay-class="date-picker-overlay"
1259
- >
1260
- <XLocationPicker
1261
- :default-center="defaultMapCenter"
1262
- :service-name="serviceName"
1263
- @confirm="handleAddressConfirm"
1264
- />
1265
- </VanPopup>
1266
-
1267
- <!-- pc的树形选择框————》 手机端采用 Cascader 级联选择 -->
1268
- <VanField
1269
- v-if="attr.type === 'treeSelect' && showItem"
1270
- v-model="treeValue"
1271
- name="treeSelect"
1272
- :label="labelData"
1273
- :label-align="labelAlign"
1274
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1275
- readonly
1276
- is-link
1277
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1278
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1279
- :required="attr.rule.required === 'true'"
1280
- @click="readonly ? null : showTreeSelect = true"
1281
- />
1282
- <VanPopup
1283
- v-model:show="showTreeSelect"
1284
- position="bottom"
1285
- teleport="body"
1286
- overlay-class="date-picker-overlay"
1287
- >
1288
- <VanCascader
1289
- :options="cleanEmptyChildren(option, attr.customFieldName ? attr.customFieldName : { text: 'label', value: 'value', children: 'children' })"
1290
- :field-names="attr.customFieldName ? attr.customFieldName : { text: 'label', value: 'value', children: 'children' }"
1291
- :title="attr.name"
1292
- :closeable="true"
1293
- @close="showTreeSelect = false"
1294
- @finish="onTreeSelectFinish"
1295
- />
1296
- </VanPopup>
1297
- </div>
1298
- </template>
1299
-
1300
- <style scoped>
1301
- .date-picker-overlay {
1302
- background-color: rgba(0, 0, 0, 0.2); /* 设置为半透明的黑色 */
1303
- }
1304
- .action-btn {
1305
- border-radius: 10px;
1306
- margin-left: 8px;
1307
- min-width: 4rem;
1308
- max-width: 6rem;
1309
- }
1310
- </style>
1
+ <script setup lang="ts">
2
+ import type { FieldType } from 'vant'
3
+ import type { Numeric } from 'vant/es/utils'
4
+ import ImageUploader from '@af-mobile-client-vue3/components/core/ImageUploader/index.vue'
5
+ import XGridDropOption from '@af-mobile-client-vue3/components/core/XGridDropOption/index.vue'
6
+ import XMultiSelect from '@af-mobile-client-vue3/components/core/XMultiSelect/index.vue'
7
+ import XSelect from '@af-mobile-client-vue3/components/core/XSelect/index.vue'
8
+ import XLocationPicker from '@af-mobile-client-vue3/components/data/XOlMap/XLocationPicker/index.vue'
9
+ import { getConfigByNameAsync, runLogic } from '@af-mobile-client-vue3/services/api/common'
10
+ import { post } from '@af-mobile-client-vue3/services/restTools'
11
+ import { searchToListOption, searchToOption } from '@af-mobile-client-vue3/services/v3Api'
12
+ import { useUserStore } from '@af-mobile-client-vue3/stores/modules/user'
13
+ import { getDict } from '@af-mobile-client-vue3/utils/dictUtil'
14
+ import { executeStrFunctionByContext } from '@af-mobile-client-vue3/utils/runEvalFunction'
15
+ import { areaList } from '@vant/area-data'
16
+ import dayjs from 'dayjs/esm/index'
17
+ import { debounce } from 'lodash-es'
18
+ import {
19
+ Area as VanArea,
20
+ Button as VanButton,
21
+ Calendar as VanCalendar,
22
+ Cascader as VanCascader,
23
+ Checkbox as VanCheckbox,
24
+ CheckboxGroup as vanCheckboxGroup,
25
+ DatePicker as VanDatePicker,
26
+ Field as VanField,
27
+ Picker as VanPicker,
28
+ PickerGroup as VanPickerGroup,
29
+ Popup as VanPopup,
30
+ Radio as VanRadio,
31
+ RadioGroup as VanRadioGroup,
32
+ Rate as VanRate,
33
+ Slider as VanSlider,
34
+ Stepper as VanStepper,
35
+ Switch as VanSwitch,
36
+ TimePicker as VanTimePicker,
37
+ } from 'vant'
38
+ import { computed, defineEmits, defineModel, defineProps, getCurrentInstance, onBeforeMount, ref, watch } from 'vue'
39
+
40
+ const props = defineProps({
41
+ attr: {
42
+ type: Object,
43
+ },
44
+ form: {
45
+ type: Object,
46
+ },
47
+ datePickerFilter: {
48
+ type: Function,
49
+ default: () => true,
50
+ },
51
+ datePickerFormatter: {
52
+ type: Function,
53
+ default: (type, val) => val,
54
+ },
55
+ mode: {
56
+ type: String,
57
+ default: '查询',
58
+ },
59
+ serviceName: {
60
+ type: String,
61
+ default: undefined,
62
+ },
63
+ // 调用logic获取数据源的追加参数
64
+ getDataParams: {
65
+ type: Object,
66
+ default: undefined,
67
+ },
68
+ disabled: {
69
+ type: Boolean,
70
+ default: false,
71
+ },
72
+ rules: {
73
+ type: Object,
74
+ default: () => {},
75
+ },
76
+ // 用 defineModel 替代 modelValue/emit
77
+ modelValue: {
78
+ type: [String, Number, Boolean, Array, Object],
79
+ default: undefined,
80
+ },
81
+ showLabel: {
82
+ type: Boolean,
83
+ default: true,
84
+ },
85
+ // radio/checkbox/select/mul-select 选项数据结构
86
+ columnsField: {
87
+ type: Object,
88
+ default: () => {
89
+ return { text: 'label', value: 'value' }
90
+ },
91
+ },
92
+
93
+ })
94
+
95
+ const emits = defineEmits(['setForm', 'xFormItemEmitFunc'])
96
+
97
+ // 用 defineModel 替代 modelValue/emit
98
+ const modelData = defineModel<string | number | boolean | any[] | Record<string, any>>()
99
+
100
+ // 获取字典
101
+ interface OptionItem {
102
+ label: string
103
+ value: any
104
+ children?: OptionItem[]
105
+ }
106
+
107
+ // 判断并初始化防抖函数
108
+ let debouncedUserLinkFunc: (() => void) | null = null
109
+ let debouncedDepLinkFunc: (() => void) | null = null
110
+ let debouncedUpdateOptions: (() => void) | null = null
111
+
112
+ const { attr, form, mode, serviceName, getDataParams, columnsField } = props
113
+ const calendarShow = ref(false)
114
+ const option = ref([])
115
+ const pickerValue = ref(undefined)
116
+ const timePickerValue = ref(undefined)
117
+ const area = ref<any>(undefined)
118
+ const showPicker = ref(false)
119
+ const showDatePicker = ref(false)
120
+ const showTimePicker = ref(false)
121
+ const showArea = ref(false)
122
+ const errorMessage = ref('')
123
+ const showTreeSelect = ref(false)
124
+ const treeValue = ref('')
125
+
126
+ // 登录信息 (可以在配置的动态函数中使用 this.setupState 获取到当前组件内的全部函数和变量 例:this.setupState.userState)
127
+ const userState = useUserStore().getLogin()
128
+ const currUser = computed(() => userState.f.resources.id)
129
+ const userInfo = computed(() => ({
130
+ orgId: userState.f.resources.orgid,
131
+ userId: userState.f.resources.id,
132
+ }))
133
+
134
+ // 是否展示当前项
135
+ const showItem = ref(true)
136
+
137
+ // 当前组件实例(不推荐使用,可能会在后续的版本更迭中调整,暂时用来绑定函数的上下文)
138
+ const currInst = getCurrentInstance()
139
+
140
+ // 配置中心->表单项变更触发函数
141
+ async function dataChangeFunc() {
142
+ if (attr.dataChangeFunc) {
143
+ await executeStrFunctionByContext(currInst, attr.dataChangeFunc, [props.form, (formData: any) => emits('setForm', formData), attr, null, mode])
144
+ }
145
+ }
146
+ const dataChangeFuncdebounce = debounce(dataChangeFunc, 300)
147
+ // 配置中心->表单项展示函数
148
+ async function showFormItemFunc() {
149
+ if (attr.showFormItemFunc) {
150
+ const obj = await executeStrFunctionByContext(currInst, attr.showFormItemFunc, [form, attr, null, mode])
151
+ // 判断是 bool 还是 obj 兼容
152
+ if (typeof obj === 'boolean') {
153
+ showItem.value = obj
154
+ }
155
+ else if (obj && typeof obj === 'object') {
156
+ // obj 是一个对象,并且不是数组
157
+ showItem.value = obj?.show
158
+ }
159
+ }
160
+ }
161
+ const showFormItemFuncdebounce = debounce(showFormItemFunc, 300)
162
+ /**
163
+ * 检测是否传入了有效的值
164
+ * @returns any
165
+ */
166
+ function checkModel(val = props.modelValue) {
167
+ if (val === null || val === undefined || val === '')
168
+ return false
169
+ if (Array.isArray(val))
170
+ return val.length > 0
171
+ return true
172
+ }
173
+ /**
174
+ * 获取表单项的默认值
175
+ * @returns any
176
+ */
177
+ function getDefaultValue() {
178
+ const val = props.modelValue
179
+
180
+ // 如果有有效值,直接返回
181
+ if (checkModel(val)) {
182
+ return val
183
+ }
184
+
185
+ // 根据类型获取默认值
186
+ const getDefaultByType = () => {
187
+ const def = mode !== '查询' ? attr.formDefault : attr.queryFormDefault
188
+
189
+ if (['treeSelect', 'select', 'checkbox'].includes(attr.type) && ['curOrgId', 'curDepId', 'curUserId'].includes(def)) {
190
+ if (def === 'curOrgId') {
191
+ if (attr.type === 'treeSelect') {
192
+ treeValue.value = userState.f.resources.orgs
193
+ }
194
+ return attr.type === 'select' ? userState.f.resources.orgid : [userState.f.resources.orgid]
195
+ }
196
+ if (def === 'curDepId') {
197
+ if (attr.type === 'treeSelect') {
198
+ treeValue.value = userState.f.resources.deps
199
+ }
200
+ return attr.type === 'select' ? userState.f.resources.depids : [userState.f.resources.depids]
201
+ }
202
+ if (def === 'curUserId') {
203
+ if (attr.type === 'treeSelect') {
204
+ treeValue.value = userState.f.resources.name
205
+ }
206
+ return attr.type === 'select' ? userState.f.resources.id : [userState.f.resources.id]
207
+ }
208
+ }
209
+
210
+ // 数组类型默认值
211
+ const arrayTypes = ['uploader', 'checkbox', 'file', 'area', 'image', 'treeSelect']
212
+ if (arrayTypes.includes(attr.type)) {
213
+ return def !== undefined ? def : []
214
+ }
215
+
216
+ // 特殊类型默认值
217
+ const specialDefaults = {
218
+ switch: false,
219
+ stepper: 1,
220
+ addressSearch: val,
221
+ }
222
+
223
+ if (specialDefaults[attr.type] !== undefined) {
224
+ return specialDefaults[attr.type]
225
+ }
226
+
227
+ // 日期时间类型
228
+ const dateTypes = ['rangePicker', 'yearPicker', 'monthPicker', 'yearRangePicker', 'monthRangePicker', 'datePicker', 'timePicker']
229
+ if (dateTypes.includes(attr.type)) {
230
+ return getDateRange({
231
+ type: attr.type,
232
+ formDefault: attr.formDefault ?? '',
233
+ queryFormDefault: attr.queryFormDefault ?? '',
234
+ queryType: attr.queryType,
235
+ queryValueFormat: attr.queryValueFormat,
236
+ name: attr.name ?? '',
237
+ }) ?? []
238
+ }
239
+
240
+ // 其他类型(字符串、数字等)
241
+ return def ?? ''
242
+ }
243
+
244
+ return getDefaultByType()
245
+ }
246
+
247
+ // 初始化日期组件初始值,组件自定义格式显示值和实际值(日期相关初始化都在此函数中操作)
248
+ function getDateRange({
249
+ type,
250
+ formDefault: defaultValue,
251
+ queryFormDefault,
252
+ queryType,
253
+ queryValueFormat: defaultFormat,
254
+ name,
255
+ }: {
256
+ type: string
257
+ formDefault: string
258
+ queryFormDefault: string
259
+ queryType?: string
260
+ queryValueFormat?: string
261
+ name: string
262
+ }): string | [string, string] | undefined {
263
+ const formatMap: Record<string, string> = {
264
+ yearPicker: 'YYYY',
265
+ yearRangePicker: 'YYYY',
266
+ monthPicker: 'YYYY-MM',
267
+ monthRangePicker: 'YYYY-MM',
268
+ datePicker: 'YYYY-MM-DD HH:mm:ss',
269
+ rangePicker: 'YYYY-MM-DD HH:mm:ss',
270
+ }
271
+ if (mode) {
272
+ const format = defaultFormat || formatMap[type]
273
+ const val = mode === '查询' ? queryFormDefault : defaultValue
274
+ let start: string, end: string
275
+ switch (val) {
276
+ case 'curYear':
277
+ start = dayjs().startOf('year').format(format)
278
+ end = dayjs().endOf('year').format(format)
279
+ break
280
+ case 'curMonth':
281
+ start = dayjs().startOf('month').format(format)
282
+ end = dayjs().endOf('month').format(format)
283
+ break
284
+ case 'curDay':
285
+ start = dayjs().startOf('day').format(format)
286
+ end = dayjs().endOf('day').format(format)
287
+ break
288
+ case 'curTime':
289
+ start = dayjs().format(format)
290
+ end = dayjs().format(format)
291
+ break
292
+ default:
293
+ return undefined
294
+ }
295
+ if (['monthPicker', 'yearPicker', 'datePicker'].includes(type)) {
296
+ if (mode !== '查询') {
297
+ if (queryType === 'BETWEEN') {
298
+ return [start, end]
299
+ }
300
+ if (name.includes('开始') || name.includes('起始')) {
301
+ return start
302
+ }
303
+ else {
304
+ return end
305
+ }
306
+ }
307
+ else {
308
+ return start
309
+ }
310
+ }
311
+ // rangePicker组件表单显示的值
312
+ if (mode === '查询' && type === 'rangePicker') {
313
+ pickerValue.value = `${start} ~ ${end}`
314
+ }
315
+ return mode !== '查询' ? start : [start, end]
316
+ }
317
+ else {
318
+ return undefined
319
+ }
320
+ }
321
+
322
+ // 监听 props.form 的变化
323
+ watch(
324
+ () => props.form,
325
+ (newVal, oldVal) => {
326
+ // 如果是从函数获取 options
327
+ if (props.attr.keyName && (props.attr.keyName.toString().includes('async ') || props.attr.keyName.toString().includes('function'))) {
328
+ debouncedUpdateOptions()
329
+ }
330
+ if (props.attr.showFormItemFunc) {
331
+ showFormItemFuncdebounce()
332
+ }
333
+ },
334
+ { deep: true },
335
+ )
336
+
337
+ // 监听 modelData 的变化,调用 dataChangeFunc
338
+ watch(
339
+ () => modelData.value,
340
+ (newVal, oldVal) => {
341
+ // 避免初始化时的调用
342
+ if (newVal !== oldVal) {
343
+ dataChangeFuncdebounce()
344
+ }
345
+ },
346
+ { deep: true },
347
+ )
348
+
349
+ // 监听 option.value 的变化
350
+ watch(
351
+ () => option.value,
352
+ (newOption) => {
353
+ if (attr.type === 'treeSelect' && newOption && newOption.length > 0) {
354
+ // 你可以在这里调用 findOptionInTree 函数来查找默认值对应的 label
355
+ const result = findOptionInTree(option.value, modelData.value)
356
+ if (attr.type === 'treeSelect' && result)
357
+ treeValue.value = result.label
358
+ }
359
+ },
360
+ )
361
+
362
+ function updateFile(files, _index) {
363
+ modelData.value = files
364
+ }
365
+
366
+ // 表单校验的类型校验
367
+ function formTypeCheck(attr, value) {
368
+ if (mode === '查询')
369
+ return
370
+ if (!attr.rule || !attr.rule.required || attr.rule.required === 'false')
371
+ return
372
+ switch (attr.rule.type) {
373
+ case 'string':
374
+ if (value.length === 0) {
375
+ errorMessage.value = `请输入${attr.name}`
376
+ return
377
+ }
378
+ break
379
+ case 'number':
380
+ if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(value)) {
381
+ errorMessage.value = `${attr.name}必须为数字`
382
+ return
383
+ }
384
+ break
385
+ case 'boolean':
386
+ case 'array':
387
+ case 'regexp':
388
+ if (value) {
389
+ errorMessage.value = ''
390
+ return
391
+ }
392
+ break
393
+ case 'integer':
394
+ if (!/^-?\d+$/.test(value)) {
395
+ errorMessage.value = `${attr.name}必须为整数`
396
+ return
397
+ }
398
+ break
399
+ case 'float':
400
+ if (!/^-?\d+\.\d+$/.test(value)) {
401
+ errorMessage.value = `${attr.name}必须为小数`
402
+ return
403
+ }
404
+ break
405
+ case 'email':
406
+ if (!/^[\w.%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i.test(value)) {
407
+ errorMessage.value = `请输入正确的邮箱地址`
408
+ return
409
+ }
410
+ break
411
+ case 'idNumber':
412
+ if (!/^(?:\d{15}|\d{17}[0-9X])$/.test(value)) {
413
+ errorMessage.value = `请输入正确的身份证号码`
414
+ return
415
+ }
416
+ break
417
+ case 'userPhone':
418
+ if (!/^1[3-9]\d{9}$/.test(value)) {
419
+ errorMessage.value = `请输入正确的手机号码`
420
+ return
421
+ }
422
+ break
423
+ case 'landlineNumber':
424
+ if (!/^0\d{2,3}[-\s]?\d{7,8}$/.test(value)) {
425
+ errorMessage.value = `请输入正确的座机号码`
426
+ return
427
+ }
428
+ break
429
+ case 'greaterThanZero':
430
+ if (!/^(?:[1-9]\d*(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(value)) {
431
+ errorMessage.value = `请输入一个大于0的数字`
432
+ return
433
+ }
434
+ break
435
+ case 'greaterThanOrEqualZero':
436
+ if (!/^(?:\d+|\d*\.\d+)$/.test(value)) {
437
+ errorMessage.value = `请输入一个大于等于0的数字`
438
+ return
439
+ }
440
+ break
441
+ case 'stringLength':
442
+ if (!(value.length >= attr.rule.minLen && value.length < attr.rule.maxLen)) {
443
+ errorMessage.value = `长度必须在${attr.rule.minLen}~${attr.rule.maxLen}之间`
444
+ return
445
+ }
446
+ break
447
+ case 'customJs':
448
+ // eslint-disable-next-line no-case-declarations
449
+ const funcStr = attr.rule.customValidatorFunc
450
+ // 输入的数据是否符合条件
451
+ // eslint-disable-next-line no-case-declarations
452
+ const status = ref(true)
453
+ // 提取函数体部分
454
+ // eslint-disable-next-line no-case-declarations
455
+ const funcBodyMatch = funcStr.match(/function\s*\(.*?\)\s*\{([\s\S]*)\}/)
456
+ if (!funcBodyMatch)
457
+ throw new Error('自定义校验函数不合法')
458
+ // eslint-disable-next-line no-case-declarations
459
+ const funcBody = funcBodyMatch[1].trim() // 提取函数体
460
+ // 使用 new Function 创建函数
461
+ // eslint-disable-next-line no-new-func,no-case-declarations
462
+ const customValidatorFunc = new Function('rule', 'value', 'callback', 'form', 'attr', 'util', funcBody)
463
+ // 定义 callback 函数
464
+ // eslint-disable-next-line no-case-declarations
465
+ const callback = (error) => {
466
+ if (error) {
467
+ errorMessage.value = `${error}`
468
+ status.value = false // 表示有错误发生
469
+ }
470
+ }
471
+ // 调用自定义校验函数
472
+ customValidatorFunc(
473
+ attr.rule,
474
+ value,
475
+ callback,
476
+ form,
477
+ attr,
478
+ {}, // util 对象(可以根据需要传递)
479
+ )
480
+ if (!status.value)
481
+ return
482
+ break
483
+ default:
484
+ errorMessage.value = ''
485
+ break
486
+ }
487
+ errorMessage.value = ''
488
+ }
489
+
490
+ onBeforeMount(() => {
491
+ init()
492
+ modelData.value = getDefaultValue()
493
+ showFormItemFunc()
494
+ dataChangeFunc()
495
+ if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString().endsWith(']联动人员'))
496
+ debouncedUserLinkFunc = debounce(() => updateResOptions('人员'), 200)
497
+
498
+ if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString().endsWith(']联动部门'))
499
+ debouncedDepLinkFunc = debounce(() => updateResOptions('部门'), 200)
500
+
501
+ if (attr.keyName && (attr?.keyName?.toString().indexOf('async ') !== -1 || attr?.keyName?.toString()?.indexOf('function') !== -1)) {
502
+ debouncedUpdateOptions = debounce(updateOptions, 200)
503
+ }
504
+ })
505
+ // 是否展示表单左侧label文字
506
+ const labelData = computed(() => {
507
+ return props.showLabel ? attr.name : null
508
+ })
509
+ // 是否展示表单左侧label文字
510
+ const labelAlign = computed(() => {
511
+ return attr.labelAlign ? attr.labelAlign : 'left'
512
+ })
513
+ // 是否只读
514
+ const readonly = computed(() => {
515
+ return attr.addOrEdit === 'readonly' || mode === '预览'
516
+ })
517
+ // 提示内容
518
+ const placeholder = computed(() => {
519
+ if (attr.addOrEdit === 'readonly')
520
+ return '暂无内容 ~ '
521
+ else
522
+ return attr.placeholder ? attr.placeholder : `请选择${attr.name}`
523
+ })
524
+
525
+ const formatDate = date => `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
526
+
527
+ function onCalendarConfirm(values) {
528
+ modelData.value = [formatDate(values[0]), formatDate(values[1])]
529
+ pickerValue.value = `${formatDate(values[0])} ~ ${formatDate(values[1])}`
530
+ calendarShow.value = false
531
+ }
532
+
533
+ // js 函数作为数据源
534
+ async function updateOptions() {
535
+ if (attr.keyName && (attr.keyName.toString().includes('async ') || attr.keyName.toString().includes('function '))) {
536
+ option.value = await executeStrFunctionByContext(this, attr.keyName, [props.form, runLogic, props.mode, getConfigByNameAsync, post])
537
+ }
538
+ }
539
+
540
+ function init() {
541
+ if (attr.keyName && typeof attr.keyName === 'string') {
542
+ if (attr.keyName && attr.keyName.includes('logic@')) {
543
+ getData({}, (res) => {
544
+ option.value = res
545
+ initRadioValue()
546
+ })
547
+ }
548
+ else if (attr.keyName && attr.keyName.includes('config@')) {
549
+ const configName = attr.keyName.substring(7)
550
+ getDict(configName, (result) => {
551
+ if (result)
552
+ option.value = result
553
+ }, serviceName)
554
+ }
555
+ else if (attr.keyName && attr.keyName.includes('search@')) {
556
+ let source = attr.keyName.substring(7)
557
+ const userid = currUser.value
558
+ let roleName = 'roleName'
559
+ if (source.startsWith('根据角色[') && source.endsWith(']获取人员')) {
560
+ const startIndex = source.indexOf('[') + 1
561
+ const endIndex = source.indexOf(']', startIndex)
562
+ roleName = source.substring(startIndex, endIndex)
563
+ source = '根据角色获取人员'
564
+ }
565
+ const searchData = { source, userid, roleName }
566
+ if (source.startsWith('根据表单项[') && source.endsWith(']联动人员'))
567
+ updateResOptions('人员')
568
+ else if (source.startsWith('根据表单项[') && source.endsWith(']联动部门'))
569
+ updateResOptions('部门')
570
+ else if (attr.type === 'select' || attr.type === 'checkbox')
571
+ searchToListOption(searchData, res => getDataCallback(res))
572
+ else
573
+ searchToOption(searchData, res => getDataCallback(res))
574
+ }
575
+ else if (attr.keyName.toString().includes('async ') || attr.keyName.toString().includes('function ')) {
576
+ updateOptions()
577
+ }
578
+ else {
579
+ initRadioValue()
580
+ }
581
+ }
582
+ }
583
+
584
+ function getDataCallback(res) {
585
+ option.value = res.map(item => ({
586
+ ...item,
587
+ value: item.label, // 将value设置为label的值
588
+ }))
589
+ if (attr.type === 'radio')
590
+ initRadioValue()
591
+ }
592
+
593
+ async function updateResOptions(type) {
594
+ if (attr?.keyName?.toString()?.startsWith('search@根据表单项[') && attr?.keyName?.toString()?.endsWith(`]联动${type}`)) {
595
+ const searchData = { source: `获取${type}`, userid: currUser.value }
596
+ const startIndex = attr.keyName.indexOf('[') + 1
597
+ const endIndex = attr.keyName.indexOf(']', startIndex)
598
+ const formModel = attr.keyName.substring(startIndex, endIndex).replace('.', '_')
599
+ const formModelData = form[formModel]
600
+ if (formModel?.length && formModelData?.length) {
601
+ await searchToListOption(searchData, (res) => {
602
+ getDataCallback(res.filter((h) => {
603
+ return formModelData['0'] === h.f_organization_id || formModelData['0'] === h.f_department_id || formModelData['0'] === h.parentid
604
+ // if (formModel.indexOf('org') > -1) {
605
+ // return formModelData?.includes(h.orgid || h.f_organization_id || h.parentid)
606
+ // } else {
607
+ // return formModelData?.includes(h?.parentid)
608
+ // }
609
+ }))
610
+ })
611
+ }
612
+ }
613
+ }
614
+
615
+ function initRadioValue() {
616
+ if ((mode === '新增' || mode === '修改') && attr.type === 'radio' && !props.modelValue) {
617
+ if (attr.keys && attr.keys.length > 0)
618
+ modelData.value = attr.keys[0].value
619
+ else if (option.value && option.value.length > 0)
620
+ modelData.value = option.value[0].value
621
+ }
622
+ }
623
+
624
+ function getData(value, callback) {
625
+ if (value !== '') {
626
+ const logicName = attr.keyName
627
+ const logic = logicName.substring(6)
628
+ // 调用logic前设置参数
629
+ if (getDataParams && getDataParams[attr.model])
630
+ Object.assign(value, getDataParams[attr.model])
631
+ Object.assign(value, userInfo.value)
632
+ runLogic(logic, value, serviceName).then((res) => {
633
+ callback(res)
634
+ })
635
+ }
636
+ }
637
+
638
+ // 已废弃 不进行维护
639
+ function onPickerConfirm({ selectedOptions }) {
640
+ showPicker.value = false
641
+ modelData.value = selectedOptions[0].text
642
+ }
643
+
644
+ // 日期时间选择数据
645
+ const dateTimePickerValue = ref<any>({})
646
+ function showDataTimePicker() {
647
+ if (props.modelValue && typeof props.modelValue === 'string') {
648
+ // 拆分日期和时间
649
+ const [dateStr, timeStr] = props.modelValue.split(' ')
650
+ // 拆分日期部分
651
+ const date = dateStr.split('-')
652
+ // 拆分时间部分
653
+ const time = timeStr.split(':')
654
+ // 赋值给 dateTimePickerValue
655
+ dateTimePickerValue.value = {
656
+ date,
657
+ time,
658
+ }
659
+ }
660
+ else {
661
+ dateTimePickerValue.value = {
662
+ date: formatDate(new Date()).split('-'),
663
+ time: ['00', '00', '00'],
664
+ }
665
+ }
666
+ showDatePicker.value = true
667
+ }
668
+
669
+ function onDatePickerConfirm({ selectedValues }) {
670
+ showDatePicker.value = false
671
+ modelData.value = selectedValues.join('-')
672
+ }
673
+
674
+ // 已废弃 不进行维护
675
+ function onTimePickerConfirm({ selectedValues }) {
676
+ showTimePicker.value = false
677
+ timePickerValue.value = selectedValues.join(':')
678
+ modelData.value = timePickerValue.value
679
+ }
680
+
681
+ // 没人用到本次先不动。后续需要看这个组件需要怎么使用
682
+ function onAreaConfirm({ selectedOptions }) {
683
+ area.value = `${selectedOptions[0].text}-${selectedOptions[1].text}-${selectedOptions[2].text}`
684
+ showArea.value = false
685
+ modelData.value = [{
686
+ province: selectedOptions[0].text,
687
+ city: selectedOptions[1].text,
688
+ district: selectedOptions[2].text,
689
+ }]
690
+ }
691
+
692
+ function onDateTimePickerConfirm() {
693
+ showDatePicker.value = false
694
+ const dateStr = dateTimePickerValue.value.date.join('-')
695
+ const timeStr = dateTimePickerValue.value.time.join(':')
696
+ modelData.value = `${dateStr} ${timeStr}`
697
+ }
698
+
699
+ function onPickerCancel() {
700
+ showDatePicker.value = false
701
+ }
702
+
703
+ const showAddressPicker = ref(false)
704
+ const addressValue = ref('')
705
+
706
+ // XLocationPicker 默认加载的中心点
707
+ const defaultMapCenter = computed(() => {
708
+ const lonLat = form[`${attr.model}_lon_lat`]
709
+ if (lonLat && typeof lonLat === 'string' && lonLat.includes(',')) {
710
+ const [lon, lat] = lonLat.split(',').map(Number)
711
+ if (!Number.isNaN(lon) && !Number.isNaN(lat)) {
712
+ return [lon, lat] as [number, number]
713
+ }
714
+ }
715
+ return undefined
716
+ })
717
+
718
+ // 处理地址选择器确认
719
+ function handleAddressConfirm(location) {
720
+ // 构造新的数据格式
721
+ const formData = {
722
+ [`${attr.model}_lon_lat`]: `${location.longitude},${location.latitude}`,
723
+ [attr.model]: location.address,
724
+ }
725
+ // 更新表单数据
726
+ emits('setForm', formData)
727
+ showAddressPicker.value = false
728
+ }
729
+
730
+ // 重置方法,供父组件调用
731
+ function reset() {
732
+ modelData.value = null
733
+ errorMessage.value = ''
734
+ treeValue.value = null
735
+ area.value = null
736
+ pickerValue.value = null
737
+ }
738
+
739
+ defineExpose({
740
+ reset,
741
+ })
742
+
743
+ // 数据处理
744
+ function cleanEmptyChildren(options, fieldNames = { text: 'label', value: 'value', children: 'children' }) {
745
+ if (!Array.isArray(options))
746
+ return options
747
+
748
+ const childrenKey = fieldNames.children || 'children'
749
+
750
+ return options.map((option) => {
751
+ // 深拷贝选项,避免修改原始数据
752
+ const newOption = { ...option }
753
+
754
+ // 如果存在children属性且是空数组
755
+ if (newOption[childrenKey] && Array.isArray(newOption[childrenKey]) && newOption[childrenKey].length === 0) {
756
+ delete newOption[childrenKey]
757
+ }
758
+ // 如果存在children属性且非空,则递归处理
759
+ else if (newOption[childrenKey] && Array.isArray(newOption[childrenKey])) {
760
+ newOption[childrenKey] = cleanEmptyChildren(newOption[childrenKey], fieldNames)
761
+ }
762
+ return newOption
763
+ })
764
+ }
765
+
766
+ // 级联选择完成事件
767
+ function onTreeSelectFinish({ selectedOptions }) {
768
+ const index = selectedOptions.length - 1
769
+ treeValue.value = selectedOptions[index].label
770
+ if (mode === '查询') {
771
+ modelData.value = [selectedOptions[index].value]
772
+ }
773
+ else {
774
+ modelData.value = selectedOptions[index].value
775
+ }
776
+ showTreeSelect.value = false
777
+ }
778
+
779
+ function emitFunc(func, data) {
780
+ emits('xFormItemEmitFunc', func, data, data?.model ? form[data.model] : form)
781
+ }
782
+
783
+ function findOptionInTree(options, value) {
784
+ // 在当前层级查找
785
+ const foundItem = options.find(item => item[columnsField.value] === value)
786
+ if (foundItem) {
787
+ return foundItem
788
+ }
789
+ // 递归查找子级
790
+ for (const item of options) {
791
+ if (item.children?.length) {
792
+ const foundInChildren = findOptionInTree(item.children, value)
793
+ if (foundInChildren)
794
+ return foundInChildren
795
+ }
796
+ }
797
+
798
+ return null
799
+ }
800
+ </script>
801
+
802
+ <template>
803
+ <div>
804
+ <!-- switch开关 -->
805
+ <VanField
806
+ v-if="attr.type === 'switch' && showItem"
807
+ name="switch"
808
+ :label="labelData"
809
+ :label-align="labelAlign"
810
+ :input-align="attr.inputAlign ? attr.inputAlign : 'right'"
811
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
812
+ :required="attr.rule.required === 'true'"
813
+ >
814
+ <template #input>
815
+ <VanSwitch v-model="modelData" />
816
+ </template>
817
+ </VanField>
818
+
819
+ <!-- 复选框 -->
820
+ <!-- <VanField
821
+ v-if="attr.type === 'checkbox'"
822
+ name="checkbox"
823
+ :label="labelData"
824
+ >
825
+ <template #input>
826
+ <VanCheckbox v-model="modelData" shape="square" />
827
+ </template>
828
+ </VanField> -->
829
+
830
+ <!-- 多选框-checkbox-复选框组 -->
831
+ <template v-if="attr.type === 'checkbox' && showItem">
832
+ <!-- 勾选 -->
833
+ <VanField
834
+ v-if="attr.showMode === 'checkbox' && mode !== '查询'"
835
+ name="checkboxGroup"
836
+ :label="labelData"
837
+ :label-align="labelAlign"
838
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
839
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
840
+ :required="attr.rule.required === 'true'"
841
+ >
842
+ <template #input>
843
+ <van-checkbox-group v-model="modelData as any[]" direction="horizontal" shape="square" :disabled="readonly">
844
+ <VanCheckbox v-for="(item, index) in option" :key="index" style="padding: 2px" :name="item[columnsField.value]" :shape="rules?.[attr.model].shape" :value="item[columnsField.value]">
845
+ {{ item[columnsField.text] }}
846
+ </VanCheckbox>
847
+ </van-checkbox-group>
848
+ </template>
849
+ </VanField>
850
+ <VanField
851
+ v-if="attr.showMode === 'checkbox' && mode === '查询'"
852
+ name="checkboxGroup"
853
+ :label="labelData"
854
+ :label-align="labelAlign"
855
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
856
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
857
+ >
858
+ <template #input>
859
+ <XGridDropOption
860
+ v-model="(modelData as string[])"
861
+ :column-num="labelData ? 3 : 4"
862
+ :multiple="true"
863
+ :columns="option"
864
+ />
865
+ </template>
866
+ </VanField>
867
+ <!-- 下拉 -->
868
+ <XMultiSelect
869
+ v-if="attr.showMode === 'select' && mode === '查询'"
870
+ v-model="modelData"
871
+ :label="labelData"
872
+ :readonly="readonly"
873
+ :placeholder="placeholder"
874
+ :columns="option"
875
+ :option="attr.option ? attr.option : columnsField"
876
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
877
+ :required="attr.rule.required === 'true'"
878
+ />
879
+ </template>
880
+
881
+ <!-- 单选框 -->
882
+ <VanField
883
+ v-if="attr.type === 'radio' && mode !== '查询' && showItem"
884
+ name="radio"
885
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
886
+ :label="labelData"
887
+ :label-align="labelAlign"
888
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
889
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
890
+ :required="attr.rule.required === 'true'"
891
+ >
892
+ <template #input>
893
+ <VanRadioGroup v-model="modelData" direction="horizontal" :disabled="readonly">
894
+ <VanRadio v-for="(item, index) in option" :key="index" style="padding: 2px" :name="item[columnsField.value]" :value="item[columnsField.value]">
895
+ {{ item[columnsField.text] }}
896
+ </VanRadio>
897
+ </VanRadioGroup>
898
+ </template>
899
+ </VanField>
900
+
901
+ <!-- 单选框-查询 -->
902
+ <VanField
903
+ v-if="attr.type === 'radio' && mode === '查询' && showItem"
904
+ name="radio"
905
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
906
+ :label="labelData"
907
+ :label-align="labelAlign"
908
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
909
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
910
+ >
911
+ <template #input>
912
+ <XGridDropOption
913
+ v-model="(modelData as string)"
914
+ :column-num="labelData ? 3 : 4"
915
+ :columns="option"
916
+ />
917
+ </template>
918
+ </VanField>
919
+
920
+ <!-- 步进器 -->
921
+ <VanField
922
+ v-if="attr.type === 'stepper' && showItem"
923
+ name="stepper"
924
+ :label="labelData"
925
+ :label-align="labelAlign"
926
+ :input-align="attr.inputAlign ? attr.inputAlign : 'center'"
927
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
928
+ :required="attr.rule.required === 'true'"
929
+ >
930
+ <template #input>
931
+ <VanStepper v-model="modelData as any" :disabled="readonly" />
932
+ </template>
933
+ </VanField>
934
+
935
+ <!-- 评分 -->
936
+ <VanField
937
+ v-if="attr.type === 'rate' && showItem"
938
+ name="rate"
939
+ :label="labelData"
940
+ :label-align="labelAlign"
941
+ :input-align="attr.inputAlign ? attr.inputAlign : 'center'"
942
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
943
+ :required="attr.rule.required === 'true'"
944
+ >
945
+ <template #input>
946
+ <VanRate v-model="modelData as number" :size="25" :readonly="readonly" void-color="#eee" void-icon="star" color="#ffd21e" />
947
+ </template>
948
+ </VanField>
949
+
950
+ <!-- 滑块 -->
951
+ <VanField
952
+ v-if="attr.type === 'slider' && showItem"
953
+ name="slider"
954
+ :label="labelData"
955
+ :label-align="labelAlign"
956
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
957
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
958
+ :required="attr.rule.required === 'true'"
959
+ >
960
+ <template #input>
961
+ <VanSlider v-model="modelData as number" :readonly="readonly" />
962
+ </template>
963
+ </VanField>
964
+
965
+ <!-- 文件上传 -->
966
+ <!-- 图片上传, 手机端拍照 -->
967
+ <VanField
968
+ v-if="(attr.type === 'image' || attr.type === 'file') && showItem"
969
+ name="image"
970
+ :label="labelData"
971
+ :label-align="labelAlign"
972
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
973
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
974
+ :required="attr.rule.required === 'true'"
975
+ >
976
+ <template #input>
977
+ <ImageUploader
978
+ upload-mode="server"
979
+ :image-list="(modelData as any[])"
980
+ authority="admin"
981
+ :attr="attr"
982
+ :mode="props.mode"
983
+ @update-file-list="updateFile"
984
+ />
985
+ </template>
986
+ </VanField>
987
+
988
+ <!-- 选择器 琉璃中不存在,不进行维护后续将删除 -->
989
+ <VanField
990
+ v-if="attr.type === 'picker' && showItem"
991
+ v-model="pickerValue"
992
+ name="picker"
993
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
994
+ :label="labelData"
995
+ :label-align="labelAlign"
996
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
997
+ readonly
998
+ is-link
999
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1000
+ @click="readonly ? null : showPicker = true"
1001
+ />
1002
+ <VanPopup v-model:show="showPicker" round position="bottom" teleport="body" overlay-class="date-picker-overlay">
1003
+ <VanPicker
1004
+ v-model="(modelData as Numeric[])"
1005
+ :title="attr.name"
1006
+ :columns="attr.selectKey"
1007
+ :readonly="readonly"
1008
+ :columns-field-names="attr.customFieldName ? attr.customFieldName : { text: 'text', value: 'value', children: 'children' }"
1009
+ :confirm-button-text="attr.confirmButtonText || attr.confirmButtonText === '' ? attr.confirmButtonText : '确认'"
1010
+ :cancel-button-text="attr.cancelButtonText || attr.cancelButtonText === '' ? attr.cancelButtonText : '取消'"
1011
+ @cancel="showPicker = false"
1012
+ @confirm="onPickerConfirm"
1013
+ />
1014
+ </VanPopup>
1015
+
1016
+ <!-- 日历选择-查询 -->
1017
+ <VanField
1018
+ v-if="attr.type === 'rangePicker' && mode === '查询' && showItem"
1019
+ v-model="(pickerValue as string | number)"
1020
+ is-link
1021
+ readonly
1022
+ name="rangePicker"
1023
+ :label="labelData"
1024
+ :label-align="labelAlign"
1025
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1026
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1027
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1028
+ @click="calendarShow = true"
1029
+ />
1030
+ <VanCalendar
1031
+ v-model:show="calendarShow"
1032
+ switch-mode="year-month"
1033
+ type="range"
1034
+ teleport="body"
1035
+ overlay-class="date-picker-overlay"
1036
+ :show-confirm="attr.showConfirm"
1037
+ @confirm="onCalendarConfirm"
1038
+ />
1039
+
1040
+ <!-- 日期选择-非查询 -->
1041
+ <VanField
1042
+ v-if="(attr.type === 'datePicker' || attr.type === 'rangePicker') && mode !== '查询' && showItem"
1043
+ v-model="(modelData as string | number)"
1044
+ name="datePicker"
1045
+ :label="labelData"
1046
+ :label-align="labelAlign"
1047
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1048
+ readonly
1049
+ :is-link="true"
1050
+ :placeholder="placeholder"
1051
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1052
+ :required="attr.rule.required === 'true'"
1053
+ @click="readonly ? null : showDataTimePicker()"
1054
+ />
1055
+ <VanPopup v-model:show="showDatePicker" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1056
+ <VanPickerGroup
1057
+ :title="attr.name"
1058
+ :tabs="['选择日期', '选择时间']"
1059
+ next-step-text="下一步"
1060
+ :confirm-button-text="attr.confirmButtonText ? attr.confirmButtonText : '确认'"
1061
+ :cancel-button-text="attr.cancelButtonText ? attr.cancelButtonText : '取消'"
1062
+ @confirm="onDateTimePickerConfirm"
1063
+ @cancel="onPickerCancel"
1064
+ >
1065
+ <VanDatePicker
1066
+ v-model="dateTimePickerValue.date"
1067
+ :columns-type="attr.dateColumnsType || ['year', 'month', 'day']"
1068
+ />
1069
+ <VanTimePicker
1070
+ v-model="dateTimePickerValue.time"
1071
+ :columns-type="attr.timeColumnsType || ['hour', 'minute', 'second']"
1072
+ :min-time="attr.minTime ? attr.minTime : '00:00:00'"
1073
+ :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
1074
+ />
1075
+ </VanPickerGroup>
1076
+ </VanPopup>
1077
+
1078
+ <!-- 日期选择-查询 -->
1079
+ <VanField
1080
+ v-if="attr.type === 'datePicker' && mode === '查询' && showItem"
1081
+ v-model="(modelData as string | number)"
1082
+ name="datePicker"
1083
+ :label="labelData"
1084
+ :label-align="labelAlign"
1085
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1086
+ readonly
1087
+ :is-link="true"
1088
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1089
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1090
+ @click="showDatePicker = true"
1091
+ />
1092
+ <VanPopup v-model:show="showDatePicker" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1093
+ <VanPickerGroup
1094
+ :title="attr.name"
1095
+ :tabs="['选择日期', '选择时间']"
1096
+ next-step-text="下一步"
1097
+ :confirm-button-text="attr.confirmButtonText ? attr.confirmButtonText : '确认'"
1098
+ :cancel-button-text="attr.cancelButtonText ? attr.cancelButtonText : '取消'"
1099
+ @confirm="onDateTimePickerConfirm"
1100
+ @cancel="onPickerCancel"
1101
+ >
1102
+ <VanDatePicker
1103
+ v-model="dateTimePickerValue.date"
1104
+ :columns-type="attr.dateColumnsType || ['year', 'month', 'day']"
1105
+ />
1106
+ <VanTimePicker
1107
+ v-model="dateTimePickerValue.time"
1108
+ :columns-type="attr.timeColumnsType || ['hour', 'minute', 'second']"
1109
+ :min-time="attr.minTime ? attr.minTime : '00:00:00'"
1110
+ :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
1111
+ />
1112
+ </VanPickerGroup>
1113
+ </VanPopup>
1114
+
1115
+ <!-- 时间选择 --该配置未在pc找到不进行维护 后续将删除 -->
1116
+ <VanField
1117
+ v-if="attr.type === 'timePicker' && showItem"
1118
+ v-model="timePickerValue"
1119
+ name="timePicker"
1120
+ is-link
1121
+ readonly
1122
+ :placeholder="attr.placeholder"
1123
+ :label="labelData"
1124
+ :label-align="labelAlign"
1125
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1126
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
1127
+ @click="showTimePicker = true"
1128
+ />
1129
+ <VanPopup v-model:show="showTimePicker" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1130
+ <VanTimePicker
1131
+ v-model="modelData as string[]"
1132
+ :title="attr.name"
1133
+ :columns-type="attr.columnsType ? attr.columnsType : ['hour', 'minute', 'second']"
1134
+ :min-time="attr.minTime ? attr.minTime : '00:00:00'"
1135
+ :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
1136
+ :readonly="readonly"
1137
+ @cancel="showTimePicker = false"
1138
+ @confirm="onTimePickerConfirm"
1139
+ />
1140
+ </VanPopup>
1141
+
1142
+ <!-- 省市区选择 -->
1143
+ <VanField
1144
+ v-if="(attr.type === 'area' || attr.type === 'citySelect') && showItem"
1145
+ v-model="area"
1146
+ name="area"
1147
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1148
+ is-link
1149
+ readonly
1150
+ :label="labelData"
1151
+ :label-align="labelAlign"
1152
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1153
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
1154
+ :required="attr.rule.required === 'true'"
1155
+ @click="readonly ? null : showArea = true"
1156
+ />
1157
+ <VanPopup v-model:show="showArea" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1158
+ <VanArea
1159
+ v-model="modelData as string" :title="attr.name" :area-list="areaList"
1160
+ @confirm="onAreaConfirm"
1161
+ @cancel="showArea = false"
1162
+ />
1163
+ </VanPopup>
1164
+
1165
+ <!-- 单选下拉列表 -->
1166
+ <XSelect
1167
+ v-if="attr.type === 'select' && showItem"
1168
+ v-model="modelData"
1169
+ :label="labelData"
1170
+ :readonly="readonly"
1171
+ clearable
1172
+ :placeholder="placeholder"
1173
+ :columns="option"
1174
+ :option="attr.option ? attr.option : columnsField"
1175
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1176
+ :required="attr.rule.required === 'true'"
1177
+ />
1178
+
1179
+ <!-- 文本区域 -->
1180
+ <VanField
1181
+ v-if="attr.type === 'textarea' && showItem"
1182
+ v-model="(modelData as string)"
1183
+ rows="3"
1184
+ autosize
1185
+ :label="labelData"
1186
+ :label-align="labelAlign"
1187
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1188
+ type="textarea"
1189
+ :readonly="readonly"
1190
+ :maxlength="attr.maxlength ? attr.maxlength : 200"
1191
+ :placeholder="attr.placeholder ? attr.placeholder : `请输入${attr.name}`"
1192
+ show-word-limit
1193
+ :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
1194
+ :required="attr.rule.required === 'true'"
1195
+ />
1196
+
1197
+ <!-- 文本输入框 -->
1198
+ <VanField
1199
+ v-if="(attr.type === 'input' || attr.type === 'intervalPicker') && showItem"
1200
+ v-model="(modelData as string)"
1201
+ style="align-items: center"
1202
+ :label="labelData"
1203
+ :label-align="labelAlign"
1204
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1205
+ :type="attr.type as FieldType"
1206
+ :readonly="readonly"
1207
+ :disabled="attr.disabled"
1208
+ :placeholder="placeholder"
1209
+ :error-message="errorMessage"
1210
+ :clearable="attr.clearable"
1211
+ :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
1212
+ :required="attr.rule.required === 'true'"
1213
+ @blur="() => formTypeCheck(attr, modelData as string)"
1214
+ >
1215
+ <template #input>
1216
+ <input
1217
+ :value="modelData"
1218
+ :readonly="readonly"
1219
+ class="van-field__control"
1220
+ :placeholder="placeholder"
1221
+ style="flex: 1; min-width: 0;"
1222
+ @input="e => modelData = (e.target as HTMLInputElement).value"
1223
+ @blur="() => formTypeCheck(attr, modelData as string)"
1224
+ >
1225
+ <VanButton
1226
+ v-if="attr.inputOnAfterName && attr.inputOnAfterFunc && !attr.inputOnAfterName.includes('|')"
1227
+ class="action-btn"
1228
+ round
1229
+ type="primary"
1230
+ size="small"
1231
+ @click="emitFunc(attr.inputOnAfterFunc, attr)"
1232
+ >
1233
+ {{ attr.inputOnAfterName }}
1234
+ </VanButton>
1235
+ </template>
1236
+ </VanField>
1237
+
1238
+ <!-- 地址选择器 -->
1239
+ <VanField
1240
+ v-if="attr.type === 'addressSearch' && showItem"
1241
+ v-model="modelData as string"
1242
+ name="addressSearch"
1243
+ :label="labelData"
1244
+ :label-align="labelAlign"
1245
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1246
+ readonly
1247
+ is-link
1248
+ :placeholder="placeholder"
1249
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择地址' }]"
1250
+ :required="attr.rule.required === 'true'"
1251
+ @click="readonly ? null : showAddressPicker = true"
1252
+ />
1253
+ <VanPopup
1254
+ v-model:show="showAddressPicker"
1255
+ position="bottom"
1256
+ :style="{ height: '80vh' }"
1257
+ teleport="body"
1258
+ overlay-class="date-picker-overlay"
1259
+ >
1260
+ <XLocationPicker
1261
+ :default-center="defaultMapCenter"
1262
+ :service-name="serviceName"
1263
+ @confirm="handleAddressConfirm"
1264
+ />
1265
+ </VanPopup>
1266
+
1267
+ <!-- pc的树形选择框————》 手机端采用 Cascader 级联选择 -->
1268
+ <VanField
1269
+ v-if="attr.type === 'treeSelect' && showItem"
1270
+ v-model="treeValue"
1271
+ name="treeSelect"
1272
+ :label="labelData"
1273
+ :label-align="labelAlign"
1274
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1275
+ readonly
1276
+ is-link
1277
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1278
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1279
+ :required="attr.rule.required === 'true'"
1280
+ @click="readonly ? null : showTreeSelect = true"
1281
+ />
1282
+ <VanPopup
1283
+ v-model:show="showTreeSelect"
1284
+ position="bottom"
1285
+ teleport="body"
1286
+ overlay-class="date-picker-overlay"
1287
+ >
1288
+ <VanCascader
1289
+ :options="cleanEmptyChildren(option, attr.customFieldName ? attr.customFieldName : { text: 'label', value: 'value', children: 'children' })"
1290
+ :field-names="attr.customFieldName ? attr.customFieldName : { text: 'label', value: 'value', children: 'children' }"
1291
+ :title="attr.name"
1292
+ :closeable="true"
1293
+ @close="showTreeSelect = false"
1294
+ @finish="onTreeSelectFinish"
1295
+ />
1296
+ </VanPopup>
1297
+ </div>
1298
+ </template>
1299
+
1300
+ <style scoped>
1301
+ .date-picker-overlay {
1302
+ background-color: rgba(0, 0, 0, 0.2); /* 设置为半透明的黑色 */
1303
+ }
1304
+ .action-btn {
1305
+ border-radius: 10px;
1306
+ margin-left: 8px;
1307
+ min-width: 4rem;
1308
+ max-width: 6rem;
1309
+ }
1310
+ </style>