af-mobile-client-vue3 1.3.35 → 1.3.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/.cursorrules +60 -60
  2. package/.editorconfig +9 -9
  3. package/.env +10 -10
  4. package/.env.development +1 -1
  5. package/.env.production +1 -1
  6. package/.node-version +1 -1
  7. package/.vscode/extensions.json +12 -12
  8. package/.vscode/settings.json +68 -68
  9. package/CLAUDE.md +218 -218
  10. package/README.md +182 -182
  11. package/af-example-mobile-vue-web.iml +9 -9
  12. package/build/vite/index.ts +98 -98
  13. package/build/vite/optimize.ts +34 -34
  14. package/build/vite/vconsole.ts +47 -47
  15. package/commitlint.config.ts +32 -32
  16. package/compress.js +36 -36
  17. package/eslint.config.ts +31 -31
  18. package/index.html +23 -23
  19. package/mock/data.ts +20 -20
  20. package/mock/index.ts +7 -7
  21. package/mock/modules/prose.mock.ts +13 -13
  22. package/mock/modules/user.mock.ts +95 -95
  23. package/mock/util.ts +19 -19
  24. package/netlify.toml +12 -12
  25. package/package.json +114 -114
  26. package/postcss.config.ts +27 -27
  27. package/public/favicon.svg +4 -4
  28. package/public/safari-pinned-tab.svg +4 -4
  29. package/scripts/verifyCommit.js +19 -19
  30. package/src/App.vue +79 -79
  31. package/src/api/auth/index.ts +77 -77
  32. package/src/api/auth/types.ts +200 -200
  33. package/src/api/mock/index.ts +30 -30
  34. package/src/api/user/index.ts +40 -40
  35. package/src/assets/img/user/login/background-shadow-1.svg +20 -20
  36. package/src/assets/img/user/login/logo-background.svg +20 -20
  37. package/src/bootstrap.ts +26 -26
  38. package/src/components/core/BeautifulLoading/index.vue +52 -52
  39. package/src/components/core/ImageUploader/index.vue +251 -251
  40. package/src/components/core/NavBar/index.vue +53 -53
  41. package/src/components/core/Tabbar/index.vue +32 -32
  42. package/src/components/core/Uploader/index.vue +124 -124
  43. package/src/components/core/XGridDropOption/index.vue +154 -154
  44. package/src/components/core/XMultiSelect/index.vue +183 -183
  45. package/src/components/core/XSelect/index.vue +149 -149
  46. package/src/components/data/CardContainer/CardContainer.vue +118 -118
  47. package/src/components/data/CardContainer/CardHeader.vue +99 -99
  48. package/src/components/data/InfoDisplay/index.vue +132 -132
  49. package/src/components/data/UserDetail/api.ts +24 -24
  50. package/src/components/data/UserDetail/index.vue +620 -620
  51. package/src/components/data/UserDetail/recordEntries.ts +159 -159
  52. package/src/components/data/UserDetail/types.ts +26 -26
  53. package/src/components/data/XBadge/index.vue +82 -82
  54. package/src/components/data/XCellDetail/index.vue +105 -105
  55. package/src/components/data/XCellList/XCellList.md +432 -432
  56. package/src/components/data/XCellList/index.vue +1436 -1436
  57. package/src/components/data/XCellListFilter/QrScanner/index.vue +207 -207
  58. package/src/components/data/XCellListFilter/QrScanner/startScanAnimation.ts +53 -53
  59. package/src/components/data/XCellListFilter/VpnRecognition/index.vue +119 -119
  60. package/src/components/data/XCellListFilter/index.vue +705 -705
  61. package/src/components/data/XForm/index.vue +659 -659
  62. package/src/components/data/XFormGroup/doc/DeviceForm.vue +122 -122
  63. package/src/components/data/XFormGroup/doc/FormGroupDemo.vue +56 -56
  64. package/src/components/data/XFormGroup/doc/README.md +286 -286
  65. package/src/components/data/XFormGroup/doc/UserForm.vue +102 -102
  66. package/src/components/data/XFormGroup/index.vue +240 -240
  67. package/src/components/data/XFormItem/index.vue +1311 -1311
  68. package/src/components/data/XOlMap/README.md +227 -227
  69. package/src/components/data/XOlMap/XLocationPicker/index.vue +226 -226
  70. package/src/components/data/XOlMap/index.vue +1490 -1490
  71. package/src/components/data/XOlMap/types.ts +149 -149
  72. package/src/components/data/XOlMap/utils/wgs84ToGcj02.ts +171 -171
  73. package/src/components/data/XReportForm/DateTimeSecondsPicker.vue +208 -208
  74. package/src/components/data/XReportForm/XReportFormJsonRender.vue +220 -220
  75. package/src/components/data/XReportForm/index.vue +1393 -1393
  76. package/src/components/data/XReportGrid/XAddReport/XAddReport.vue +198 -198
  77. package/src/components/data/XReportGrid/XAddReport/index.js +3 -3
  78. package/src/components/data/XReportGrid/XAddReport/index.md +53 -53
  79. package/src/components/data/XReportGrid/XAddReport/index.ts +10 -10
  80. package/src/components/data/XReportGrid/XReport.vue +960 -960
  81. package/src/components/data/XReportGrid/XReportDemo.vue +33 -33
  82. package/src/components/data/XReportGrid/XReportDesign.vue +597 -597
  83. package/src/components/data/XReportGrid/XReportDrawer/XReportDrawer.vue +148 -148
  84. package/src/components/data/XReportGrid/XReportDrawer/index.js +3 -3
  85. package/src/components/data/XReportGrid/XReportDrawer/index.ts +10 -10
  86. package/src/components/data/XReportGrid/XReportJsonRender.vue +399 -399
  87. package/src/components/data/XReportGrid/XReportTrGroup.vue +592 -592
  88. package/src/components/data/XReportGrid/index.md +46 -46
  89. package/src/components/data/XReportGrid/print.js +184 -184
  90. package/src/components/data/XSignature/index.vue +284 -284
  91. package/src/components/data/XTag/index.vue +10 -10
  92. package/src/components/layout/NormalDataLayout/index.vue +69 -69
  93. package/src/components/layout/TabBarLayout/index.vue +40 -40
  94. package/src/composables/dark.ts +5 -5
  95. package/src/config/routes.ts +9 -9
  96. package/src/constants/index.ts +2 -2
  97. package/src/enums/requestEnum.ts +25 -25
  98. package/src/expression/ExpressionRunner.ts +28 -28
  99. package/src/expression/TestExpression.ts +510 -510
  100. package/src/expression/core/Delegate.ts +116 -116
  101. package/src/expression/core/Expression.ts +1359 -1359
  102. package/src/expression/core/Program.ts +985 -985
  103. package/src/expression/core/Token.ts +29 -29
  104. package/src/expression/enums/ExpressionType.ts +81 -81
  105. package/src/expression/enums/TokenType.ts +11 -11
  106. package/src/expression/exception/BreakWayException.ts +2 -2
  107. package/src/expression/exception/ContinueWayException.ts +2 -2
  108. package/src/expression/exception/ExpressionException.ts +29 -29
  109. package/src/expression/exception/ReturnWayException.ts +14 -14
  110. package/src/expression/exception/ServiceException.ts +22 -22
  111. package/src/expression/instances/JSONArray.ts +52 -52
  112. package/src/expression/instances/JSONObject.ts +118 -118
  113. package/src/expression/instances/LogicConsole.ts +31 -31
  114. package/src/font-style/font.css +4 -4
  115. package/src/hooks/useBoolean.ts +26 -26
  116. package/src/hooks/useCommon.ts +9 -9
  117. package/src/hooks/useLoading.ts +16 -16
  118. package/src/hooks/useLogin.ts +97 -97
  119. package/src/icons/svg/check-in.svg +32 -32
  120. package/src/icons/svg/dark.svg +4 -4
  121. package/src/icons/svg/github.svg +4 -4
  122. package/src/icons/svg/light.svg +4 -4
  123. package/src/icons/svg/link.svg +4 -4
  124. package/src/icons/svgo.yml +22 -22
  125. package/src/layout/GridView/index.vue +16 -16
  126. package/src/layout/PageLayout.vue +9 -9
  127. package/src/layout/SingleLayout.vue +9 -9
  128. package/src/locales/en-US.json +128 -128
  129. package/src/locales/zh-CN.json +128 -128
  130. package/src/logic/LogicRunner.ts +67 -67
  131. package/src/logic/TestLogic.ts +13 -13
  132. package/src/logic/plugins/common/DateTools.ts +35 -35
  133. package/src/logic/plugins/common/VueTools.ts +30 -30
  134. package/src/logic/plugins/index.ts +7 -7
  135. package/src/main.ts +44 -44
  136. package/src/plugins/AppData.ts +38 -38
  137. package/src/plugins/GetLoginInfoService.ts +10 -10
  138. package/src/plugins/collectIcons.ts +10 -10
  139. package/src/plugins/index.ts +11 -11
  140. package/src/router/README.md +8 -8
  141. package/src/router/external-routes.ts +60 -60
  142. package/src/router/guards.ts +131 -131
  143. package/src/router/index.ts +35 -35
  144. package/src/router/invoiceRoutes.ts +33 -33
  145. package/src/router/routes.ts +421 -421
  146. package/src/services/api/Login.ts +6 -6
  147. package/src/services/api/common.ts +109 -109
  148. package/src/services/api/index.ts +7 -7
  149. package/src/services/api/manage.ts +8 -8
  150. package/src/services/api/search.ts +16 -16
  151. package/src/services/api/user.ts +17 -17
  152. package/src/services/restTools.ts +56 -56
  153. package/src/services/v3Api.ts +147 -147
  154. package/src/stores/index.ts +13 -13
  155. package/src/stores/modules/counter.ts +19 -19
  156. package/src/stores/modules/homeApp.ts +55 -55
  157. package/src/stores/modules/routeCache.ts +22 -22
  158. package/src/stores/modules/setting.ts +87 -87
  159. package/src/stores/modules/user.ts +326 -326
  160. package/src/stores/mutation-type.ts +12 -12
  161. package/src/styles/app.less +36 -36
  162. package/src/styles/login.less +109 -109
  163. package/src/styles/var.less +25 -25
  164. package/src/types/auth.ts +89 -89
  165. package/src/types/env.d.ts +16 -16
  166. package/src/types/platform.ts +194 -194
  167. package/src/types/settings.ts +1 -1
  168. package/src/types/vue-router.d.ts +13 -13
  169. package/src/utils/Storage.ts +124 -124
  170. package/src/utils/authority-utils.ts +84 -84
  171. package/src/utils/common.ts +41 -41
  172. package/src/utils/crypto.ts +39 -39
  173. package/src/utils/dataUtil.ts +42 -42
  174. package/src/utils/dictUtil.ts +52 -52
  175. package/src/utils/http/index.ts +201 -201
  176. package/src/utils/i18n.ts +72 -72
  177. package/src/utils/indexedDB.ts +195 -195
  178. package/src/utils/inline-px-to-vw.ts +28 -28
  179. package/src/utils/mobileUtil.ts +33 -33
  180. package/src/utils/platform-auth.ts +135 -135
  181. package/src/utils/progress.ts +19 -19
  182. package/src/utils/queryFormDefaultRangePicker.ts +57 -57
  183. package/src/utils/routerUtil.ts +271 -271
  184. package/src/utils/runEvalFunction.ts +13 -13
  185. package/src/utils/secureStorage.ts +70 -70
  186. package/src/utils/set-page-title.ts +5 -5
  187. package/src/utils/validate.ts +6 -6
  188. package/src/views/chat/index.vue +153 -153
  189. package/src/views/common/Forbidden.vue +97 -97
  190. package/src/views/common/LoadError.vue +63 -63
  191. package/src/views/common/NotFound.vue +67 -67
  192. package/src/views/component/EvaluateRecordView/index.vue +40 -40
  193. package/src/views/component/IconifyView/index.vue +504 -504
  194. package/src/views/component/UserDetailView/UserDetailPage.vue +77 -77
  195. package/src/views/component/UserDetailView/index.vue +234 -234
  196. package/src/views/component/XCellDetailView/index.vue +217 -217
  197. package/src/views/component/XCellListView/index.vue +108 -108
  198. package/src/views/component/XFormAppraiseView/index.vue +174 -174
  199. package/src/views/component/XFormGroupView/index.vue +78 -78
  200. package/src/views/component/XFormView/index.vue +27 -27
  201. package/src/views/component/XOlMapView/index.vue +434 -434
  202. package/src/views/component/XOlMapView/testData.ts +64 -64
  203. package/src/views/component/XReportFormIframeView/index.vue +47 -47
  204. package/src/views/component/XReportFormView/index.vue +13 -13
  205. package/src/views/component/XReportGridView/index.vue +17 -17
  206. package/src/views/component/XRequestView/index.vue +234 -234
  207. package/src/views/component/XSignatureView/index.vue +50 -50
  208. package/src/views/component/index.vue +181 -181
  209. package/src/views/component/menu.vue +117 -117
  210. package/src/views/component/notice.vue +46 -46
  211. package/src/views/component/topNav.vue +36 -36
  212. package/src/views/external/index.vue +152 -152
  213. package/src/views/invoiceShow/index.vue +61 -61
  214. package/src/views/loading/AuthLoading.vue +345 -345
  215. package/src/views/user/login/ForgetPasswordForm.vue +94 -94
  216. package/src/views/user/login/LoginForm.vue +350 -350
  217. package/src/views/user/login/LoginTitle.vue +76 -76
  218. package/src/views/user/login/LoginWave.vue +109 -109
  219. package/src/views/user/login/index.vue +22 -22
  220. package/src/views/user/my/comm/ModifyPassword.vue +346 -346
  221. package/src/views/user/my/index.vue +507 -507
  222. package/src/views/user/register/index.vue +952 -952
  223. package/src/views/userRecords/AbnormalAlarmRecords.vue +21 -21
  224. package/src/views/userRecords/CardReplacementRecords.vue +21 -21
  225. package/src/views/userRecords/ChangeRecords.vue +19 -19
  226. package/src/views/userRecords/CommandViewRecords.vue +20 -20
  227. package/src/views/userRecords/GasCompensationRecords.vue +20 -20
  228. package/src/views/userRecords/InstrumentCollectionRecords.vue +21 -21
  229. package/src/views/userRecords/MeterRecords.vue +20 -20
  230. package/src/views/userRecords/OperateRecords.vue +51 -51
  231. package/src/views/userRecords/OtherChargeRecords.vue +19 -19
  232. package/src/views/userRecords/PaymentRecords.vue +28 -28
  233. package/src/views/userRecords/PriceAdjustmentRecords.vue +19 -19
  234. package/src/views/userRecords/ReplacementRecords.vue +19 -19
  235. package/src/views/userRecords/SafetyRecords.vue +19 -19
  236. package/src/views/userRecords/TransactionRecords.vue +21 -21
  237. package/src/views/userRecords/TransferRecords.vue +19 -19
  238. package/src/views/userRecords/operateRecordDetail/index.vue +316 -316
  239. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AddUserDetail.vue +124 -124
  240. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AdvanceDeliveryDetail.vue +88 -88
  241. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AutoAccountsCancelDetail.vue +205 -205
  242. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AutoAccountsDetail.vue +192 -192
  243. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BankDkDetail.vue +192 -192
  244. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BankPayDetail.vue +192 -192
  245. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BlacklistDetail.vue +153 -153
  246. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CancellationDetail.vue +101 -101
  247. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardMeterCenterCancelDetail.vue +127 -127
  248. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardMeterCenterDetail.vue +153 -153
  249. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardOverUserDetail.vue +153 -153
  250. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ChangeMeterCancelDetail.vue +166 -166
  251. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ChangeMeterDetail.vue +205 -205
  252. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/DisableManageDetail.vue +127 -127
  253. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/EnableManageDetail.vue +114 -114
  254. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/FaZheChangeDetail.vue +124 -124
  255. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/FeeDeductionDetail.vue +153 -153
  256. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/GasPriceChangeDetail.vue +126 -126
  257. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/InputtorChangeDetail.vue +126 -126
  258. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotMeterCenterCancelDetail.vue +114 -114
  259. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotMeterCenterDetail.vue +127 -127
  260. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotOpenDetail.vue +88 -88
  261. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineCardDetail.vue +101 -101
  262. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineMeterCenterCancelDetail.vue +218 -218
  263. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineMeterCenterDetail.vue +153 -153
  264. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OffGasAddGasDetail.vue +140 -140
  265. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OtherChargeCancelDetail.vue +127 -127
  266. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OtherChargeDetail.vue +114 -114
  267. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OverUserChangeDetail.vue +127 -127
  268. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReBillDetail.vue +127 -127
  269. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/RefundDetail.vue +114 -114
  270. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReplaceCardManageCancelDetail.vue +127 -127
  271. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReplaceCardManageDetail.vue +114 -114
  272. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/SaleCardGasDetail.vue +140 -140
  273. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/TransferManageCancelDetail.vue +152 -152
  274. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/TransferManageDetail.vue +178 -178
  275. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/UserChangeDetail.vue +123 -123
  276. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/WechatPayDetail.vue +192 -192
  277. package/src/views/userRecords/types.ts +66 -66
  278. package/tsconfig.json +39 -39
  279. package/uno.config.ts +82 -82
  280. package/vite.config.ts +114 -114
  281. package/.claude/settings.local.json +0 -14
@@ -1,1311 +1,1311 @@
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, runLogic, getConfigByNameAsync])
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
- center
886
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
887
- :label="labelData"
888
- :label-align="labelAlign"
889
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
890
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
891
- :required="attr.rule.required === 'true'"
892
- >
893
- <template #input>
894
- <VanRadioGroup v-model="modelData" direction="horizontal" :disabled="readonly">
895
- <VanRadio v-for="(item, index) in option" :key="index" style="padding: 2px" :name="item[columnsField.value]" :value="item[columnsField.value]">
896
- {{ item[columnsField.text] }}
897
- </VanRadio>
898
- </VanRadioGroup>
899
- </template>
900
- </VanField>
901
-
902
- <!-- 单选框-查询 -->
903
- <VanField
904
- v-if="attr.type === 'radio' && mode === '查询' && showItem"
905
- name="radio"
906
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
907
- :label="labelData"
908
- :label-align="labelAlign"
909
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
910
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
911
- >
912
- <template #input>
913
- <XGridDropOption
914
- v-model="(modelData as string)"
915
- :column-num="labelData ? 3 : 4"
916
- :columns="option"
917
- />
918
- </template>
919
- </VanField>
920
-
921
- <!-- 步进器 -->
922
- <VanField
923
- v-if="attr.type === 'stepper' && showItem"
924
- name="stepper"
925
- :label="labelData"
926
- :label-align="labelAlign"
927
- :input-align="attr.inputAlign ? attr.inputAlign : 'center'"
928
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
929
- :required="attr.rule.required === 'true'"
930
- >
931
- <template #input>
932
- <VanStepper v-model="modelData as any" :disabled="readonly" />
933
- </template>
934
- </VanField>
935
-
936
- <!-- 评分 -->
937
- <VanField
938
- v-if="attr.type === 'rate' && showItem"
939
- name="rate"
940
- :label="labelData"
941
- :label-align="labelAlign"
942
- :input-align="attr.inputAlign ? attr.inputAlign : 'center'"
943
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
944
- :required="attr.rule.required === 'true'"
945
- >
946
- <template #input>
947
- <VanRate v-model="modelData as number" :size="25" :readonly="readonly" void-color="#eee" void-icon="star" color="#ffd21e" />
948
- </template>
949
- </VanField>
950
-
951
- <!-- 滑块 -->
952
- <VanField
953
- v-if="attr.type === 'slider' && showItem"
954
- name="slider"
955
- :label="labelData"
956
- :label-align="labelAlign"
957
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
958
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
959
- :required="attr.rule.required === 'true'"
960
- >
961
- <template #input>
962
- <VanSlider v-model="modelData as number" :readonly="readonly" />
963
- </template>
964
- </VanField>
965
-
966
- <!-- 文件上传 -->
967
- <!-- 图片上传, 手机端拍照 -->
968
- <VanField
969
- v-if="(attr.type === 'image' || attr.type === 'file') && showItem"
970
- name="image"
971
- :label="labelData"
972
- :label-align="labelAlign"
973
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
974
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
975
- :required="attr.rule.required === 'true'"
976
- >
977
- <template #input>
978
- <ImageUploader
979
- upload-mode="server"
980
- :image-list="(modelData as any[])"
981
- authority="admin"
982
- :attr="attr"
983
- :mode="props.mode"
984
- @update-file-list="updateFile"
985
- />
986
- </template>
987
- </VanField>
988
-
989
- <!-- 选择器 琉璃中不存在,不进行维护后续将删除 -->
990
- <VanField
991
- v-if="attr.type === 'picker' && showItem"
992
- v-model="pickerValue"
993
- name="picker"
994
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
995
- :label="labelData"
996
- :label-align="labelAlign"
997
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
998
- readonly
999
- is-link
1000
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1001
- @click="readonly ? null : showPicker = true"
1002
- />
1003
- <VanPopup v-model:show="showPicker" round position="bottom" teleport="body" overlay-class="date-picker-overlay">
1004
- <VanPicker
1005
- v-model="(modelData as Numeric[])"
1006
- :title="attr.name"
1007
- :columns="attr.selectKey"
1008
- :readonly="readonly"
1009
- :columns-field-names="attr.customFieldName ? attr.customFieldName : { text: 'text', value: 'value', children: 'children' }"
1010
- :confirm-button-text="attr.confirmButtonText || attr.confirmButtonText === '' ? attr.confirmButtonText : '确认'"
1011
- :cancel-button-text="attr.cancelButtonText || attr.cancelButtonText === '' ? attr.cancelButtonText : '取消'"
1012
- @cancel="showPicker = false"
1013
- @confirm="onPickerConfirm"
1014
- />
1015
- </VanPopup>
1016
-
1017
- <!-- 日历选择-查询 -->
1018
- <VanField
1019
- v-if="attr.type === 'rangePicker' && mode === '查询' && showItem"
1020
- v-model="(pickerValue as string | number)"
1021
- is-link
1022
- readonly
1023
- name="rangePicker"
1024
- :label="labelData"
1025
- :label-align="labelAlign"
1026
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1027
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1028
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1029
- @click="calendarShow = true"
1030
- />
1031
- <VanCalendar
1032
- v-model:show="calendarShow"
1033
- switch-mode="year-month"
1034
- type="range"
1035
- teleport="body"
1036
- overlay-class="date-picker-overlay"
1037
- :show-confirm="attr.showConfirm"
1038
- @confirm="onCalendarConfirm"
1039
- />
1040
-
1041
- <!-- 日期选择-非查询 -->
1042
- <VanField
1043
- v-if="(attr.type === 'datePicker' || attr.type === 'rangePicker') && mode !== '查询' && showItem"
1044
- v-model="(modelData as string | number)"
1045
- name="datePicker"
1046
- :label="labelData"
1047
- :label-align="labelAlign"
1048
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1049
- readonly
1050
- :is-link="true"
1051
- :placeholder="placeholder"
1052
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1053
- :required="attr.rule.required === 'true'"
1054
- @click="readonly ? null : showDataTimePicker()"
1055
- />
1056
- <VanPopup v-model:show="showDatePicker" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1057
- <VanPickerGroup
1058
- :title="attr.name"
1059
- :tabs="['选择日期', '选择时间']"
1060
- next-step-text="下一步"
1061
- :confirm-button-text="attr.confirmButtonText ? attr.confirmButtonText : '确认'"
1062
- :cancel-button-text="attr.cancelButtonText ? attr.cancelButtonText : '取消'"
1063
- @confirm="onDateTimePickerConfirm"
1064
- @cancel="onPickerCancel"
1065
- >
1066
- <VanDatePicker
1067
- v-model="dateTimePickerValue.date"
1068
- :columns-type="attr.dateColumnsType || ['year', 'month', 'day']"
1069
- />
1070
- <VanTimePicker
1071
- v-model="dateTimePickerValue.time"
1072
- :columns-type="attr.timeColumnsType || ['hour', 'minute', 'second']"
1073
- :min-time="attr.minTime ? attr.minTime : '00:00:00'"
1074
- :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
1075
- />
1076
- </VanPickerGroup>
1077
- </VanPopup>
1078
-
1079
- <!-- 日期选择-查询 -->
1080
- <VanField
1081
- v-if="attr.type === 'datePicker' && mode === '查询' && showItem"
1082
- v-model="(modelData as string | number)"
1083
- name="datePicker"
1084
- :label="labelData"
1085
- :label-align="labelAlign"
1086
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1087
- readonly
1088
- :is-link="true"
1089
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1090
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1091
- @click="showDatePicker = true"
1092
- />
1093
- <VanPopup v-model:show="showDatePicker" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1094
- <VanPickerGroup
1095
- :title="attr.name"
1096
- :tabs="['选择日期', '选择时间']"
1097
- next-step-text="下一步"
1098
- :confirm-button-text="attr.confirmButtonText ? attr.confirmButtonText : '确认'"
1099
- :cancel-button-text="attr.cancelButtonText ? attr.cancelButtonText : '取消'"
1100
- @confirm="onDateTimePickerConfirm"
1101
- @cancel="onPickerCancel"
1102
- >
1103
- <VanDatePicker
1104
- v-model="dateTimePickerValue.date"
1105
- :columns-type="attr.dateColumnsType || ['year', 'month', 'day']"
1106
- />
1107
- <VanTimePicker
1108
- v-model="dateTimePickerValue.time"
1109
- :columns-type="attr.timeColumnsType || ['hour', 'minute', 'second']"
1110
- :min-time="attr.minTime ? attr.minTime : '00:00:00'"
1111
- :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
1112
- />
1113
- </VanPickerGroup>
1114
- </VanPopup>
1115
-
1116
- <!-- 时间选择 --该配置未在pc找到不进行维护 后续将删除 -->
1117
- <VanField
1118
- v-if="attr.type === 'timePicker' && showItem"
1119
- v-model="timePickerValue"
1120
- name="timePicker"
1121
- is-link
1122
- readonly
1123
- :placeholder="attr.placeholder"
1124
- :label="labelData"
1125
- :label-align="labelAlign"
1126
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1127
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
1128
- @click="showTimePicker = true"
1129
- />
1130
- <VanPopup v-model:show="showTimePicker" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1131
- <VanTimePicker
1132
- v-model="modelData as string[]"
1133
- :title="attr.name"
1134
- :columns-type="attr.columnsType ? attr.columnsType : ['hour', 'minute', 'second']"
1135
- :min-time="attr.minTime ? attr.minTime : '00:00:00'"
1136
- :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
1137
- :readonly="readonly"
1138
- @cancel="showTimePicker = false"
1139
- @confirm="onTimePickerConfirm"
1140
- />
1141
- </VanPopup>
1142
-
1143
- <!-- 省市区选择 -->
1144
- <VanField
1145
- v-if="(attr.type === 'area' || attr.type === 'citySelect') && showItem"
1146
- v-model="area"
1147
- name="area"
1148
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1149
- is-link
1150
- readonly
1151
- :label="labelData"
1152
- :label-align="labelAlign"
1153
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1154
- :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
1155
- :required="attr.rule.required === 'true'"
1156
- @click="readonly ? null : showArea = true"
1157
- />
1158
- <VanPopup v-model:show="showArea" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1159
- <VanArea
1160
- v-model="modelData as string" :title="attr.name" :area-list="areaList"
1161
- @confirm="onAreaConfirm"
1162
- @cancel="showArea = false"
1163
- />
1164
- </VanPopup>
1165
-
1166
- <!-- 单选下拉列表 -->
1167
- <XSelect
1168
- v-if="attr.type === 'select' && showItem"
1169
- v-model="modelData"
1170
- :label="labelData"
1171
- :readonly="readonly"
1172
- clearable
1173
- :placeholder="placeholder"
1174
- :columns="option"
1175
- :option="attr.option ? attr.option : columnsField"
1176
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1177
- :required="attr.rule.required === 'true'"
1178
- />
1179
-
1180
- <!-- 文本区域 -->
1181
- <VanField
1182
- v-if="attr.type === 'textarea' && showItem"
1183
- v-model="(modelData as string)"
1184
- rows="3"
1185
- autosize
1186
- :label="labelData"
1187
- :label-align="labelAlign"
1188
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1189
- type="textarea"
1190
- :readonly="readonly"
1191
- :maxlength="attr.maxlength ? attr.maxlength : 200"
1192
- :placeholder="attr.placeholder ? attr.placeholder : `请输入${attr.name}`"
1193
- show-word-limit
1194
- :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
1195
- :required="attr.rule.required === 'true'"
1196
- />
1197
-
1198
- <!-- 文本输入框 -->
1199
- <VanField
1200
- v-if="(attr.type === 'input' || attr.type === 'intervalPicker') && showItem"
1201
- v-model="(modelData as string)"
1202
- style="align-items: center"
1203
- :label="labelData"
1204
- :label-align="labelAlign"
1205
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1206
- :type="attr.type as FieldType"
1207
- :readonly="readonly"
1208
- :disabled="attr.disabled"
1209
- :placeholder="placeholder"
1210
- :error-message="errorMessage"
1211
- :clearable="attr.clearable"
1212
- :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
1213
- :required="attr.rule.required === 'true'"
1214
- @blur="() => formTypeCheck(attr, modelData as string)"
1215
- >
1216
- <template #input>
1217
- <input
1218
- :value="modelData"
1219
- :readonly="readonly"
1220
- class="van-field__control"
1221
- :placeholder="placeholder"
1222
- style="flex: 1; min-width: 0;"
1223
- @input="e => modelData = (e.target as HTMLInputElement).value"
1224
- @blur="() => formTypeCheck(attr, modelData as string)"
1225
- >
1226
- <VanButton
1227
- v-if="attr.inputOnAfterName && attr.inputOnAfterFunc && !attr.inputOnAfterName.includes('|')"
1228
- class="action-btn"
1229
- round
1230
- type="primary"
1231
- size="small"
1232
- @click="emitFunc(attr.inputOnAfterFunc, attr)"
1233
- >
1234
- {{ attr.inputOnAfterName }}
1235
- </VanButton>
1236
- </template>
1237
- </VanField>
1238
-
1239
- <!-- 地址选择器 -->
1240
- <VanField
1241
- v-if="attr.type === 'addressSearch' && showItem"
1242
- v-model="modelData as string"
1243
- name="addressSearch"
1244
- :label="labelData"
1245
- :label-align="labelAlign"
1246
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1247
- readonly
1248
- is-link
1249
- :placeholder="placeholder"
1250
- :rules="[{ required: attr.rule.required === 'true', message: '请选择地址' }]"
1251
- :required="attr.rule.required === 'true'"
1252
- @click="readonly ? null : showAddressPicker = true"
1253
- />
1254
- <VanPopup
1255
- v-model:show="showAddressPicker"
1256
- position="bottom"
1257
- :style="{ height: '80vh' }"
1258
- teleport="body"
1259
- overlay-class="date-picker-overlay"
1260
- >
1261
- <XLocationPicker
1262
- :default-center="defaultMapCenter"
1263
- :service-name="serviceName"
1264
- @confirm="handleAddressConfirm"
1265
- />
1266
- </VanPopup>
1267
-
1268
- <!-- pc的树形选择框————》 手机端采用 Cascader 级联选择 -->
1269
- <VanField
1270
- v-if="attr.type === 'treeSelect' && showItem"
1271
- v-model="treeValue"
1272
- name="treeSelect"
1273
- :label="labelData"
1274
- :label-align="labelAlign"
1275
- :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1276
- readonly
1277
- is-link
1278
- :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1279
- :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1280
- :required="attr.rule.required === 'true'"
1281
- @click="readonly ? null : showTreeSelect = true"
1282
- />
1283
- <VanPopup
1284
- v-model:show="showTreeSelect"
1285
- position="bottom"
1286
- teleport="body"
1287
- overlay-class="date-picker-overlay"
1288
- >
1289
- <VanCascader
1290
- :options="cleanEmptyChildren(option, attr.customFieldName ? attr.customFieldName : { text: 'label', value: 'value', children: 'children' })"
1291
- :field-names="attr.customFieldName ? attr.customFieldName : { text: 'label', value: 'value', children: 'children' }"
1292
- :title="attr.name"
1293
- :closeable="true"
1294
- @close="showTreeSelect = false"
1295
- @finish="onTreeSelectFinish"
1296
- />
1297
- </VanPopup>
1298
- </div>
1299
- </template>
1300
-
1301
- <style scoped>
1302
- .date-picker-overlay {
1303
- background-color: rgba(0, 0, 0, 0.2); /* 设置为半透明的黑色 */
1304
- }
1305
- .action-btn {
1306
- border-radius: 10px;
1307
- margin-left: 8px;
1308
- min-width: 4rem;
1309
- max-width: 6rem;
1310
- }
1311
- </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, runLogic, getConfigByNameAsync])
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
+ center
886
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
887
+ :label="labelData"
888
+ :label-align="labelAlign"
889
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
890
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
891
+ :required="attr.rule.required === 'true'"
892
+ >
893
+ <template #input>
894
+ <VanRadioGroup v-model="modelData" direction="horizontal" :disabled="readonly">
895
+ <VanRadio v-for="(item, index) in option" :key="index" style="padding: 2px" :name="item[columnsField.value]" :value="item[columnsField.value]">
896
+ {{ item[columnsField.text] }}
897
+ </VanRadio>
898
+ </VanRadioGroup>
899
+ </template>
900
+ </VanField>
901
+
902
+ <!-- 单选框-查询 -->
903
+ <VanField
904
+ v-if="attr.type === 'radio' && mode === '查询' && showItem"
905
+ name="radio"
906
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
907
+ :label="labelData"
908
+ :label-align="labelAlign"
909
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
910
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
911
+ >
912
+ <template #input>
913
+ <XGridDropOption
914
+ v-model="(modelData as string)"
915
+ :column-num="labelData ? 3 : 4"
916
+ :columns="option"
917
+ />
918
+ </template>
919
+ </VanField>
920
+
921
+ <!-- 步进器 -->
922
+ <VanField
923
+ v-if="attr.type === 'stepper' && showItem"
924
+ name="stepper"
925
+ :label="labelData"
926
+ :label-align="labelAlign"
927
+ :input-align="attr.inputAlign ? attr.inputAlign : 'center'"
928
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
929
+ :required="attr.rule.required === 'true'"
930
+ >
931
+ <template #input>
932
+ <VanStepper v-model="modelData as any" :disabled="readonly" />
933
+ </template>
934
+ </VanField>
935
+
936
+ <!-- 评分 -->
937
+ <VanField
938
+ v-if="attr.type === 'rate' && showItem"
939
+ name="rate"
940
+ :label="labelData"
941
+ :label-align="labelAlign"
942
+ :input-align="attr.inputAlign ? attr.inputAlign : 'center'"
943
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
944
+ :required="attr.rule.required === 'true'"
945
+ >
946
+ <template #input>
947
+ <VanRate v-model="modelData as number" :size="25" :readonly="readonly" void-color="#eee" void-icon="star" color="#ffd21e" />
948
+ </template>
949
+ </VanField>
950
+
951
+ <!-- 滑块 -->
952
+ <VanField
953
+ v-if="attr.type === 'slider' && showItem"
954
+ name="slider"
955
+ :label="labelData"
956
+ :label-align="labelAlign"
957
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
958
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
959
+ :required="attr.rule.required === 'true'"
960
+ >
961
+ <template #input>
962
+ <VanSlider v-model="modelData as number" :readonly="readonly" />
963
+ </template>
964
+ </VanField>
965
+
966
+ <!-- 文件上传 -->
967
+ <!-- 图片上传, 手机端拍照 -->
968
+ <VanField
969
+ v-if="(attr.type === 'image' || attr.type === 'file') && showItem"
970
+ name="image"
971
+ :label="labelData"
972
+ :label-align="labelAlign"
973
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
974
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
975
+ :required="attr.rule.required === 'true'"
976
+ >
977
+ <template #input>
978
+ <ImageUploader
979
+ upload-mode="server"
980
+ :image-list="(modelData as any[])"
981
+ authority="admin"
982
+ :attr="attr"
983
+ :mode="props.mode"
984
+ @update-file-list="updateFile"
985
+ />
986
+ </template>
987
+ </VanField>
988
+
989
+ <!-- 选择器 琉璃中不存在,不进行维护后续将删除 -->
990
+ <VanField
991
+ v-if="attr.type === 'picker' && showItem"
992
+ v-model="pickerValue"
993
+ name="picker"
994
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
995
+ :label="labelData"
996
+ :label-align="labelAlign"
997
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
998
+ readonly
999
+ is-link
1000
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1001
+ @click="readonly ? null : showPicker = true"
1002
+ />
1003
+ <VanPopup v-model:show="showPicker" round position="bottom" teleport="body" overlay-class="date-picker-overlay">
1004
+ <VanPicker
1005
+ v-model="(modelData as Numeric[])"
1006
+ :title="attr.name"
1007
+ :columns="attr.selectKey"
1008
+ :readonly="readonly"
1009
+ :columns-field-names="attr.customFieldName ? attr.customFieldName : { text: 'text', value: 'value', children: 'children' }"
1010
+ :confirm-button-text="attr.confirmButtonText || attr.confirmButtonText === '' ? attr.confirmButtonText : '确认'"
1011
+ :cancel-button-text="attr.cancelButtonText || attr.cancelButtonText === '' ? attr.cancelButtonText : '取消'"
1012
+ @cancel="showPicker = false"
1013
+ @confirm="onPickerConfirm"
1014
+ />
1015
+ </VanPopup>
1016
+
1017
+ <!-- 日历选择-查询 -->
1018
+ <VanField
1019
+ v-if="attr.type === 'rangePicker' && mode === '查询' && showItem"
1020
+ v-model="(pickerValue as string | number)"
1021
+ is-link
1022
+ readonly
1023
+ name="rangePicker"
1024
+ :label="labelData"
1025
+ :label-align="labelAlign"
1026
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1027
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1028
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1029
+ @click="calendarShow = true"
1030
+ />
1031
+ <VanCalendar
1032
+ v-model:show="calendarShow"
1033
+ switch-mode="year-month"
1034
+ type="range"
1035
+ teleport="body"
1036
+ overlay-class="date-picker-overlay"
1037
+ :show-confirm="attr.showConfirm"
1038
+ @confirm="onCalendarConfirm"
1039
+ />
1040
+
1041
+ <!-- 日期选择-非查询 -->
1042
+ <VanField
1043
+ v-if="(attr.type === 'datePicker' || attr.type === 'rangePicker') && mode !== '查询' && showItem"
1044
+ v-model="(modelData as string | number)"
1045
+ name="datePicker"
1046
+ :label="labelData"
1047
+ :label-align="labelAlign"
1048
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1049
+ readonly
1050
+ :is-link="true"
1051
+ :placeholder="placeholder"
1052
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1053
+ :required="attr.rule.required === 'true'"
1054
+ @click="readonly ? null : showDataTimePicker()"
1055
+ />
1056
+ <VanPopup v-model:show="showDatePicker" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1057
+ <VanPickerGroup
1058
+ :title="attr.name"
1059
+ :tabs="['选择日期', '选择时间']"
1060
+ next-step-text="下一步"
1061
+ :confirm-button-text="attr.confirmButtonText ? attr.confirmButtonText : '确认'"
1062
+ :cancel-button-text="attr.cancelButtonText ? attr.cancelButtonText : '取消'"
1063
+ @confirm="onDateTimePickerConfirm"
1064
+ @cancel="onPickerCancel"
1065
+ >
1066
+ <VanDatePicker
1067
+ v-model="dateTimePickerValue.date"
1068
+ :columns-type="attr.dateColumnsType || ['year', 'month', 'day']"
1069
+ />
1070
+ <VanTimePicker
1071
+ v-model="dateTimePickerValue.time"
1072
+ :columns-type="attr.timeColumnsType || ['hour', 'minute', 'second']"
1073
+ :min-time="attr.minTime ? attr.minTime : '00:00:00'"
1074
+ :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
1075
+ />
1076
+ </VanPickerGroup>
1077
+ </VanPopup>
1078
+
1079
+ <!-- 日期选择-查询 -->
1080
+ <VanField
1081
+ v-if="attr.type === 'datePicker' && mode === '查询' && showItem"
1082
+ v-model="(modelData as string | number)"
1083
+ name="datePicker"
1084
+ :label="labelData"
1085
+ :label-align="labelAlign"
1086
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1087
+ readonly
1088
+ :is-link="true"
1089
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1090
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1091
+ @click="showDatePicker = true"
1092
+ />
1093
+ <VanPopup v-model:show="showDatePicker" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1094
+ <VanPickerGroup
1095
+ :title="attr.name"
1096
+ :tabs="['选择日期', '选择时间']"
1097
+ next-step-text="下一步"
1098
+ :confirm-button-text="attr.confirmButtonText ? attr.confirmButtonText : '确认'"
1099
+ :cancel-button-text="attr.cancelButtonText ? attr.cancelButtonText : '取消'"
1100
+ @confirm="onDateTimePickerConfirm"
1101
+ @cancel="onPickerCancel"
1102
+ >
1103
+ <VanDatePicker
1104
+ v-model="dateTimePickerValue.date"
1105
+ :columns-type="attr.dateColumnsType || ['year', 'month', 'day']"
1106
+ />
1107
+ <VanTimePicker
1108
+ v-model="dateTimePickerValue.time"
1109
+ :columns-type="attr.timeColumnsType || ['hour', 'minute', 'second']"
1110
+ :min-time="attr.minTime ? attr.minTime : '00:00:00'"
1111
+ :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
1112
+ />
1113
+ </VanPickerGroup>
1114
+ </VanPopup>
1115
+
1116
+ <!-- 时间选择 --该配置未在pc找到不进行维护 后续将删除 -->
1117
+ <VanField
1118
+ v-if="attr.type === 'timePicker' && showItem"
1119
+ v-model="timePickerValue"
1120
+ name="timePicker"
1121
+ is-link
1122
+ readonly
1123
+ :placeholder="attr.placeholder"
1124
+ :label="labelData"
1125
+ :label-align="labelAlign"
1126
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1127
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
1128
+ @click="showTimePicker = true"
1129
+ />
1130
+ <VanPopup v-model:show="showTimePicker" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1131
+ <VanTimePicker
1132
+ v-model="modelData as string[]"
1133
+ :title="attr.name"
1134
+ :columns-type="attr.columnsType ? attr.columnsType : ['hour', 'minute', 'second']"
1135
+ :min-time="attr.minTime ? attr.minTime : '00:00:00'"
1136
+ :max-time="attr.maxTime ? attr.maxTime : '23:59:59'"
1137
+ :readonly="readonly"
1138
+ @cancel="showTimePicker = false"
1139
+ @confirm="onTimePickerConfirm"
1140
+ />
1141
+ </VanPopup>
1142
+
1143
+ <!-- 省市区选择 -->
1144
+ <VanField
1145
+ v-if="(attr.type === 'area' || attr.type === 'citySelect') && showItem"
1146
+ v-model="area"
1147
+ name="area"
1148
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1149
+ is-link
1150
+ readonly
1151
+ :label="labelData"
1152
+ :label-align="labelAlign"
1153
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1154
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择' }]"
1155
+ :required="attr.rule.required === 'true'"
1156
+ @click="readonly ? null : showArea = true"
1157
+ />
1158
+ <VanPopup v-model:show="showArea" position="bottom" teleport="body" overlay-class="date-picker-overlay">
1159
+ <VanArea
1160
+ v-model="modelData as string" :title="attr.name" :area-list="areaList"
1161
+ @confirm="onAreaConfirm"
1162
+ @cancel="showArea = false"
1163
+ />
1164
+ </VanPopup>
1165
+
1166
+ <!-- 单选下拉列表 -->
1167
+ <XSelect
1168
+ v-if="attr.type === 'select' && showItem"
1169
+ v-model="modelData"
1170
+ :label="labelData"
1171
+ :readonly="readonly"
1172
+ clearable
1173
+ :placeholder="placeholder"
1174
+ :columns="option"
1175
+ :option="attr.option ? attr.option : columnsField"
1176
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1177
+ :required="attr.rule.required === 'true'"
1178
+ />
1179
+
1180
+ <!-- 文本区域 -->
1181
+ <VanField
1182
+ v-if="attr.type === 'textarea' && showItem"
1183
+ v-model="(modelData as string)"
1184
+ rows="3"
1185
+ autosize
1186
+ :label="labelData"
1187
+ :label-align="labelAlign"
1188
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1189
+ type="textarea"
1190
+ :readonly="readonly"
1191
+ :maxlength="attr.maxlength ? attr.maxlength : 200"
1192
+ :placeholder="attr.placeholder ? attr.placeholder : `请输入${attr.name}`"
1193
+ show-word-limit
1194
+ :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
1195
+ :required="attr.rule.required === 'true'"
1196
+ />
1197
+
1198
+ <!-- 文本输入框 -->
1199
+ <VanField
1200
+ v-if="(attr.type === 'input' || attr.type === 'intervalPicker') && showItem"
1201
+ v-model="(modelData as string)"
1202
+ style="align-items: center"
1203
+ :label="labelData"
1204
+ :label-align="labelAlign"
1205
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1206
+ :type="attr.type as FieldType"
1207
+ :readonly="readonly"
1208
+ :disabled="attr.disabled"
1209
+ :placeholder="placeholder"
1210
+ :error-message="errorMessage"
1211
+ :clearable="attr.clearable"
1212
+ :rules="[{ required: attr.rule.required === 'true', message: `请填写${attr.name}` }]"
1213
+ :required="attr.rule.required === 'true'"
1214
+ @blur="() => formTypeCheck(attr, modelData as string)"
1215
+ >
1216
+ <template #input>
1217
+ <input
1218
+ :value="modelData"
1219
+ :readonly="readonly"
1220
+ class="van-field__control"
1221
+ :placeholder="placeholder"
1222
+ style="flex: 1; min-width: 0;"
1223
+ @input="e => modelData = (e.target as HTMLInputElement).value"
1224
+ @blur="() => formTypeCheck(attr, modelData as string)"
1225
+ >
1226
+ <VanButton
1227
+ v-if="attr.inputOnAfterName && attr.inputOnAfterFunc && !attr.inputOnAfterName.includes('|')"
1228
+ class="action-btn"
1229
+ round
1230
+ type="primary"
1231
+ size="small"
1232
+ @click="emitFunc(attr.inputOnAfterFunc, attr)"
1233
+ >
1234
+ {{ attr.inputOnAfterName }}
1235
+ </VanButton>
1236
+ </template>
1237
+ </VanField>
1238
+
1239
+ <!-- 地址选择器 -->
1240
+ <VanField
1241
+ v-if="attr.type === 'addressSearch' && showItem"
1242
+ v-model="modelData as string"
1243
+ name="addressSearch"
1244
+ :label="labelData"
1245
+ :label-align="labelAlign"
1246
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1247
+ readonly
1248
+ is-link
1249
+ :placeholder="placeholder"
1250
+ :rules="[{ required: attr.rule.required === 'true', message: '请选择地址' }]"
1251
+ :required="attr.rule.required === 'true'"
1252
+ @click="readonly ? null : showAddressPicker = true"
1253
+ />
1254
+ <VanPopup
1255
+ v-model:show="showAddressPicker"
1256
+ position="bottom"
1257
+ :style="{ height: '80vh' }"
1258
+ teleport="body"
1259
+ overlay-class="date-picker-overlay"
1260
+ >
1261
+ <XLocationPicker
1262
+ :default-center="defaultMapCenter"
1263
+ :service-name="serviceName"
1264
+ @confirm="handleAddressConfirm"
1265
+ />
1266
+ </VanPopup>
1267
+
1268
+ <!-- pc的树形选择框————》 手机端采用 Cascader 级联选择 -->
1269
+ <VanField
1270
+ v-if="attr.type === 'treeSelect' && showItem"
1271
+ v-model="treeValue"
1272
+ name="treeSelect"
1273
+ :label="labelData"
1274
+ :label-align="labelAlign"
1275
+ :input-align="attr.inputAlign ? attr.inputAlign : 'left'"
1276
+ readonly
1277
+ is-link
1278
+ :placeholder="attr.placeholder ? attr.placeholder : `请选择${attr.name}`"
1279
+ :rules="[{ required: attr.rule.required === 'true', message: `请选择${attr.name}` }]"
1280
+ :required="attr.rule.required === 'true'"
1281
+ @click="readonly ? null : showTreeSelect = true"
1282
+ />
1283
+ <VanPopup
1284
+ v-model:show="showTreeSelect"
1285
+ position="bottom"
1286
+ teleport="body"
1287
+ overlay-class="date-picker-overlay"
1288
+ >
1289
+ <VanCascader
1290
+ :options="cleanEmptyChildren(option, attr.customFieldName ? attr.customFieldName : { text: 'label', value: 'value', children: 'children' })"
1291
+ :field-names="attr.customFieldName ? attr.customFieldName : { text: 'label', value: 'value', children: 'children' }"
1292
+ :title="attr.name"
1293
+ :closeable="true"
1294
+ @close="showTreeSelect = false"
1295
+ @finish="onTreeSelectFinish"
1296
+ />
1297
+ </VanPopup>
1298
+ </div>
1299
+ </template>
1300
+
1301
+ <style scoped>
1302
+ .date-picker-overlay {
1303
+ background-color: rgba(0, 0, 0, 0.2); /* 设置为半透明的黑色 */
1304
+ }
1305
+ .action-btn {
1306
+ border-radius: 10px;
1307
+ margin-left: 8px;
1308
+ min-width: 4rem;
1309
+ max-width: 6rem;
1310
+ }
1311
+ </style>