af-mobile-client-vue3 1.3.36 → 1.3.38

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 +38 -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,1393 +1,1393 @@
1
- <script setup lang="ts">
2
- import Uploader from '@af-mobile-client-vue3/components/core/Uploader/index.vue'
3
- import DateTimeSecondsPicker from '@af-mobile-client-vue3/components/data/XReportForm/DateTimeSecondsPicker.vue'
4
- import XReportFormJsonRender from '@af-mobile-client-vue3/components/data/XReportForm/XReportFormJsonRender.vue'
5
- import XSignature from '@af-mobile-client-vue3/components/data/XSignature/index.vue'
6
- import { getConfigByNameWithoutIndexedDB } from '@af-mobile-client-vue3/services/api/common'
7
- import {
8
- showFailToast,
9
- Button as vanButton,
10
- Cell as vanCell,
11
- CellGroup as vanCellGroup,
12
- Collapse as vanCollapse,
13
- CollapseItem as vanCollapseItem,
14
- Field as vanField,
15
- Form as vanForm,
16
- NavBar as vanNavBar,
17
- Skeleton as vanSkeleton,
18
- } from 'vant'
19
- import { defineEmits, nextTick, reactive, ref, watch } from 'vue'
20
- // 移除 VueHashCalendar,使用自定义的 DateTimeSecondsPicker
21
-
22
- // ------------------------- 类型定义 -------------------------
23
- interface configDefine {
24
- data?: any
25
- designMode?: string
26
- title?: string | undefined
27
- subTitle?: Array<cell> | undefined
28
- columns?: Array<any>
29
- slotsDeclare?: Array<string> | undefined
30
- tempData?: any
31
- }
32
-
33
- interface getConfigLock {
34
- status: boolean
35
- }
36
-
37
- interface cell {
38
- type: string
39
- dataIndex: string
40
- format: string | undefined
41
- }
42
-
43
- // ------------------------- 数据 -------------------------
44
- const props = defineProps({
45
- // 配置名
46
- configName: String,
47
- // 是否将动态行的label显示在标题上
48
- showInputColumnsLabelOnTitle: { default: false },
49
- slotName: String,
50
- showNav: { default: true },
51
- localConfig: { default: undefined },
52
- configData: { default: undefined },
53
- })
54
- const emit = defineEmits(['saveConfig'])
55
- // 原始配置
56
- const activatedConfig: configDefine = reactive({})
57
- // 激活的配置名
58
- const activatedConfigName = ref('')
59
- // 下载的配置
60
- const configFromWeb = reactive({})
61
- // 定时器
62
- let timer: NodeJS.Timeout
63
- // 控制组件渲染
64
- const scanFinish = ref(false)
65
- // 折叠面板当前激活的值
66
- const activeCollapseName = ref('副标题')
67
-
68
- watch(() => props.configName, () => {
69
- // 这里是你的处理逻辑
70
- initComponent()
71
- })
72
-
73
- // ------------------------- 方法 -------------------------
74
- // 为inputs类型的数据,整理格式,供form展示用
75
- function formatInputs(cell: cell): string[] {
76
- const format = cell.format
77
- const arr = format.split('{}')
78
- const result = []
79
- const reg = /[\u4E00-\u9FA5a-z]/gi
80
- arr.forEach((str) => {
81
- if (str.length >= 1 && str.match(reg)) {
82
- const strs = str.match(reg)
83
- result.push(strs.join(''))
84
- }
85
- })
86
- if (!activatedConfig.data[cell.dataIndex][result.length - 1]) {
87
- let dataKeySet = Object.keys(activatedConfig.data)
88
- dataKeySet = dataKeySet.filter((key) => {
89
- return key.startsWith(cell.dataIndex)
90
- })
91
- dataKeySet.sort()
92
- const target = dataKeySet[0]
93
- dataKeySet.shift()
94
- dataKeySet.forEach((key) => {
95
- activatedConfig.data[key].forEach((item) => {
96
- activatedConfig.data[target].push(item)
97
- })
98
- })
99
-
100
- for (let i = 0; i < result.length; i++) {
101
- if (!activatedConfig.data[cell.dataIndex][i])
102
- activatedConfig.data[cell.dataIndex][i] = undefined
103
- }
104
- }
105
- return result
106
- }
107
-
108
- // 左上角返回按钮
109
- function onClickLeft(): void {
110
- history.back()
111
- }
112
-
113
- function generateDefaultRequiredMessage(field: any): string {
114
- const label = field.label || field.text || field.valueText || ''
115
-
116
- switch (field.type) {
117
- case 'input':
118
- return `请填写${label || '此项'}`
119
- case 'datePicker':
120
- return `请选择${label || '日期'}`
121
- case 'timePicker':
122
- return `请选择${label || '时间'}`
123
- case 'dateTimeSecondsPicker':
124
- return `请选择${label || '完整时间'}`
125
- case 'curDateInput':
126
- return `请设置${label || '时间'}`
127
- case 'signature':
128
- return `请完成${label || '签名'}`
129
- case 'images':
130
- return `请上传${label || '图片'}`
131
- case 'inputs':
132
- return `请完成${label || '此项'}的填写`
133
- default:
134
- return `请完成${label || '此项'}的填写`
135
- }
136
- }
137
-
138
- // 检查字段值是否为空
139
- function isFieldEmpty(value: any): boolean {
140
- if (value === null || value === undefined || value === '') {
141
- return true
142
- }
143
- if (Array.isArray(value) && value.length === 0) {
144
- return true
145
- }
146
- return typeof value === 'object' && Object.keys(value).length === 0
147
- }
148
-
149
- // 表单校验
150
- function validateForm(): boolean {
151
- const errors: string[] = []
152
-
153
- // 校验主表单字段
154
- activatedConfig.columns.forEach((row: any) => {
155
- if (row.required === true) {
156
- const dataIndex = row.dataIndex
157
- let value
158
-
159
- if (dataIndex.includes('@@@')) {
160
- value = activatedConfig.tempData?.[dataIndex]
161
- }
162
- else {
163
- if (row.type === 'images') {
164
- value = activatedConfig.data.images?.[dataIndex]
165
- }
166
- else {
167
- value = activatedConfig.data[dataIndex]
168
- }
169
- }
170
-
171
- if (isFieldEmpty(value)) {
172
- const message = row.requiredMessage || generateDefaultRequiredMessage(row)
173
- errors.push(message)
174
- }
175
- }
176
-
177
- // 校验动态行中的字段
178
- if (row.type === 'inputColumns' && row.definition) {
179
- const dynamicData = activatedConfig.data[row.dataIndex]
180
- if (Array.isArray(dynamicData)) {
181
- dynamicData.forEach((item: any, index: number) => {
182
- row.definition.forEach((def: any) => {
183
- if (def.required === true) {
184
- const value = item[def.dataIndex]
185
- if (isFieldEmpty(value)) {
186
- const message = def.requiredMessage || generateDefaultRequiredMessage(def)
187
- errors.push(`第${index + 1}行:${message}`)
188
- }
189
- }
190
- })
191
- })
192
- }
193
- }
194
- })
195
-
196
- // 校验副标题字段(如果有的话)
197
- if (activatedConfig.subTitle) {
198
- activatedConfig.subTitle.forEach((subCell: any) => {
199
- if (subCell.required === true) {
200
- const value = activatedConfig.data[subCell.dataIndex]
201
- if (isFieldEmpty(value)) {
202
- const message = subCell.requiredMessage || generateDefaultRequiredMessage(subCell)
203
- errors.push(message)
204
- }
205
- }
206
- })
207
- }
208
-
209
- if (errors.length > 0) {
210
- // 显示第一个错误信息
211
- showFailToast(errors[0])
212
-
213
- return false
214
- }
215
-
216
- return true
217
- }
218
-
219
- // 提交按钮
220
- function onSubmit(): void {
221
- // 调试:打印所有字段的required状态
222
- console.log('所有字段配置:', activatedConfig.columns.map(row => ({
223
- type: row.type,
224
- label: row.label,
225
- required: row.required,
226
- dataIndex: row.dataIndex,
227
- })))
228
-
229
- // 先进行表单校验
230
- if (!validateForm()) {
231
- return
232
- }
233
-
234
- if (activatedConfig.tempData) {
235
- const keys = Object.keys(activatedConfig.tempData)
236
- keys.forEach((key) => {
237
- changeDeepObject(activatedConfig.data, key, activatedConfig.tempData[key])
238
- })
239
- }
240
- console.warn('保存', activatedConfig)
241
- emit('saveConfig', activatedConfig)
242
- }
243
-
244
- // // 动态行新增
245
- // function addInputColumns(dataIndex: string, define: Array<any>): void {
246
- // const temp = {}
247
- // define.forEach((def) => {
248
- // let begin = def.initialValue
249
- // if (def.type === 'increment') {
250
- // if (activatedConfig.data[dataIndex].length > 0) {
251
- // begin = activatedConfig.data[dataIndex][activatedConfig.data[dataIndex].length - 1][def.dataIndex]
252
- // begin = Number.parseInt(begin)
253
- // }
254
- // temp[def.dataIndex] = begin + def.step
255
- // }
256
- // else {
257
- // temp[def.dataIndex] = ''
258
- // }
259
- // })
260
- // activatedConfig.data[dataIndex].push(temp)
261
- // }
262
- //
263
- // // 动态行删除
264
- // function removeInputColumns(dataIndex: string) {
265
- // activatedConfig.data[dataIndex].pop()
266
- // }
267
-
268
- // 扫描所有插槽名
269
- function scanConfigName(config: configDefine, resut: Array<string>): void {
270
- if (config.slotsDeclare) {
271
- config.slotsDeclare.forEach((name) => {
272
- resut.push(name)
273
- })
274
- }
275
- }
276
-
277
- // 检查slot是否在配置文件中包含,如果没有包含,则视为非法获取
278
- function checkSlotDefine(config: configDefine): boolean {
279
- const slotsDeclare = config.slotsDeclare
280
- const total = slotsDeclare.length
281
- let count = 0
282
- slotsDeclare.forEach((declare) => {
283
- config.columns.forEach((row: Array<any>) => {
284
- row.forEach((cell) => {
285
- if (cell.slotConfig === declare)
286
- count++
287
- })
288
- })
289
- })
290
-
291
- return count === total
292
- }
293
-
294
- // 扫描配置,如果有插槽则拼接插槽
295
- function scanConfigSlot(config: configDefine): void {
296
- const columnsArr: Array<any> = config.columns
297
- for (let i = 0; i < columnsArr.length; i++) {
298
- for (let j = 0; j < columnsArr[i].length; j++) {
299
- // 如果发现type为slot,开始匹配对应的slot配置文件
300
- if (columnsArr[i][j].type === 'slot') {
301
- const targetName = columnsArr[i][j].slotConfig
302
- // 找不到目标插槽配置
303
- if (!configFromWeb[targetName] || !configFromWeb[targetName].columns)
304
- return
305
-
306
- // 替换columns,合并data
307
- config.columns[i] = []
308
- const before = config.columns.slice(0, i)
309
- let after = config.columns.slice(i + 1, config.columns.length)
310
-
311
- const addArr = []
312
-
313
- if (configFromWeb[targetName].cardMode === true) {
314
- const temp = [{
315
- type: 'card',
316
- content: [],
317
- label: configFromWeb[targetName].title,
318
- }]
319
- for (let k = 0; k < configFromWeb[targetName].columns.length; k++) {
320
- configFromWeb[targetName].columns[k].forEach((cell) => {
321
- temp[0].content.push(cell)
322
- })
323
- }
324
- addArr.push(temp)
325
- }
326
- else {
327
- for (let k = 0; k < configFromWeb[targetName].columns.length; k++) {
328
- const temp = []
329
- configFromWeb[targetName].columns[k].forEach((cell) => {
330
- temp.push(cell)
331
- })
332
- addArr.push(temp)
333
- }
334
- }
335
-
336
- const newArr = []
337
- // 拼接之前的数组
338
- if (before.length > 0) {
339
- if (before.length >= 1) {
340
- before.forEach((item) => {
341
- newArr.push(item)
342
- })
343
- }
344
- else {
345
- newArr.push(before)
346
- }
347
- }
348
-
349
- addArr.forEach((arr) => {
350
- newArr.push(arr)
351
- })
352
-
353
- // 拼接之后的数组
354
- if (after.length === 1) {
355
- if (after[0].type === 'slot' || after[0][0].type === 'slot')
356
- after = []
357
- }
358
- if (after.length > 0) {
359
- if (after.length >= 1) {
360
- after.forEach((item) => {
361
- newArr.push(item)
362
- })
363
- }
364
- else {
365
- newArr.push(after)
366
- }
367
- }
368
-
369
- config.columns = newArr
370
- if (configFromWeb[targetName].slotsDeclare)
371
- config.slotsDeclare = configFromWeb[targetName].slotsDeclare
372
-
373
- else
374
- config.slotsDeclare = []
375
-
376
- if (config.data.images && configFromWeb[targetName].data.images) {
377
- config.data.images = { ...config.data.images, ...configFromWeb[targetName].data.images }
378
- delete configFromWeb[targetName].data.images
379
- }
380
- config.data = { ...config.data, ...configFromWeb[targetName].data }
381
- }
382
- }
383
- }
384
- Object.assign(activatedConfig, config)
385
- }
386
-
387
- // 获取并拼装插槽
388
- function getConfigAndJoin(config: configDefine, outerLock: getConfigLock): void {
389
- // 检查主配置插槽声明是否合法
390
- const check = checkSlotDefine(config)
391
- const waitForDownloadSlotName = []
392
- if (check) {
393
- // 扫描主配置中声明的插槽名
394
- scanConfigName(config, waitForDownloadSlotName)
395
-
396
- const total = waitForDownloadSlotName.length
397
- let count = 0
398
-
399
- // 挨个获取插槽
400
- waitForDownloadSlotName.forEach((configName) => {
401
- getConfigByNameWithoutIndexedDB(configName).then((res) => {
402
- configFromWeb[configName] = res
403
- count++
404
- })
405
- })
406
-
407
- const timer = setInterval(() => {
408
- if (count >= total) {
409
- clearInterval(timer)
410
- scanConfigSlot(config)
411
- if (config.slotsDeclare.length > 0) {
412
- const lock: getConfigLock = { status: true }
413
- getConfigAndJoin(config, lock)
414
- const innerTimer = setInterval(() => {
415
- if (!lock.status) {
416
- clearInterval(innerTimer)
417
- outerLock.status = false
418
- }
419
- }, 100)
420
- }
421
- else {
422
- outerLock.status = false
423
- }
424
- }
425
- }, 100)
426
- }
427
- else {
428
- outerLock.status = false
429
- }
430
- }
431
-
432
- function deserializeFunctionAndRun(functionStr: string, value: any) {
433
- try {
434
- // eslint-disable-next-line no-new-func
435
- const fun = new Function('value', 'activatedConfig', `return (${functionStr})(value, activatedConfig)`)
436
- return fun(value, activatedConfig)
437
- }
438
- catch (err) {
439
- console.error('Failed to deserialize and run function:', err)
440
- return null
441
- }
442
- }
443
-
444
- function deserializeFunctionAndRunWithConfig(functionStr: string) {
445
- try {
446
- // eslint-disable-next-line no-new-func
447
- const fun = new Function('activatedConfig', `return (${functionStr})(activatedConfig)`)
448
- return fun(activatedConfig)
449
- }
450
- catch (err) {
451
- console.error('Failed to deserialize and run function with config:', err)
452
- return null
453
- }
454
- }
455
-
456
- // 将table配置的表格转换为适合Form展示的格式
457
- function formatConfigToForm(config: configDefine): void {
458
- const formColumns: Array<object> = []
459
- const labels: Array<string> = []
460
- for (let i = 0; i < config.columns.length; i++) {
461
- let tempObj: {
462
- customFunction?: any
463
- inputColumns?: boolean
464
- dataIndex: string
465
- label: string
466
- listType?: string
467
- listLength?: number
468
- valueText?: string
469
- type: string | undefined
470
- format?: string
471
- definition?: Array<any>
472
- editable?: boolean
473
- inputReadOnly?: boolean
474
- content?: Array<any>
475
- text?: string
476
- required?: boolean
477
- requiredMessage?: string
478
- } = {
479
- label: '',
480
- type: undefined,
481
- dataIndex: '',
482
- }
483
-
484
- const row: Array<any> = config.columns[i]
485
- let listArr = []
486
- for (let j = 0; j < row.length; j++) {
487
- switch (row[j].type) {
488
- case 'column' :
489
- if (row[j].customFunctionForDynamicDataIndex)
490
- row[j].dataIndex = deserializeFunctionAndRunWithConfig(row[j].customFunctionForDynamicDataIndex)
491
- if (j === row.length - 1) {
492
- if (row[j].customFunction) {
493
- tempObj.customFunction = row[j].customFunction
494
- tempObj.label = deserializeFunctionAndRun(row[j].customFunction, row[j].dataIndex)
495
- }
496
- else {
497
- labels.push(tempObj.label)
498
- labels.push(row[j].text)
499
- tempObj = {
500
- label: '',
501
- type: undefined,
502
- dataIndex: '',
503
- }
504
- }
505
- }
506
- else if (tempObj.label && tempObj.dataIndex) {
507
- formColumns.push(tempObj)
508
- tempObj = {
509
- label: '',
510
- type: undefined,
511
- dataIndex: '',
512
- }
513
- }
514
- else if (tempObj.customFunction) {
515
- formColumns.push(tempObj)
516
- tempObj = {
517
- label: '',
518
- type: undefined,
519
- dataIndex: '',
520
- }
521
- }
522
- else if (tempObj.label && !tempObj.dataIndex) {
523
- labels.push(tempObj.label)
524
- tempObj = {
525
- label: '',
526
- type: undefined,
527
- dataIndex: '',
528
- }
529
- }
530
- if (row[j].customFunction) {
531
- tempObj.customFunction = row[j].customFunction
532
- tempObj.label = deserializeFunctionAndRun(row[j].customFunction, row[j].dataIndex)
533
- }
534
- else {
535
- tempObj.label = row[j].text
536
- }
537
- break
538
- case 'value' :
539
- tempObj.valueText = row[j].value
540
- tempObj.editable = false
541
- tempObj.type = 'showValue'
542
- break
543
- case 'input' :
544
- if (row[j].customFunctionForDynamicDataIndex)
545
- row[j].dataIndex = deserializeFunctionAndRunWithConfig(row[j].customFunctionForDynamicDataIndex)
546
- tempObj.dataIndex = row[j].dataIndex
547
- tempObj.type = 'input'
548
- if (row[j].inputReadOnly)
549
- tempObj.inputReadOnly = true
550
- tempObj.required = row[j].required
551
- break
552
- case 'curDateInput' :
553
- tempObj.dataIndex = row[j].dataIndex
554
- tempObj.text = row[j].text
555
- tempObj.type = 'curDateInput'
556
- tempObj.required = row[j].required
557
- break
558
- case 'datePicker' :
559
- tempObj.dataIndex = row[j].dataIndex
560
- tempObj.text = row[j].text
561
- tempObj.type = 'datePicker'
562
- tempObj.required = row[j].required
563
- break
564
- case 'timePicker' :
565
- tempObj.dataIndex = row[j].dataIndex
566
- tempObj.text = row[j].text
567
- tempObj.type = 'timePicker'
568
- tempObj.required = row[j].required
569
- break
570
- case 'dateTimeSecondsPicker' :
571
- tempObj.dataIndex = row[j].dataIndex
572
- tempObj.text = row[j].text
573
- tempObj.type = 'dateTimeSecondsPicker'
574
- tempObj.required = row[j].required
575
- break
576
- case 'signature' :
577
- tempObj.dataIndex = row[j].dataIndex
578
- tempObj.text = row[j].text
579
- tempObj.type = 'signature'
580
- tempObj.required = row[j].required
581
- break
582
- case 'inputs' :
583
- if (!tempObj.format)
584
- tempObj.format = ''
585
- if (!tempObj.dataIndex)
586
- tempObj.dataIndex = row[j].dataIndex
587
- tempObj.format += row[j].format
588
- tempObj.type = 'inputs'
589
- if (row[j].inputReadOnly)
590
- tempObj.inputReadOnly = true
591
- break
592
- case 'images' :
593
- tempObj.dataIndex = row[j].dataIndex
594
- tempObj.type = 'images'
595
- break
596
- case 'inputColumns' :
597
- tempObj.label = ''
598
- tempObj.dataIndex = row[j].dataIndex
599
- tempObj.definition = row[j].definition
600
- if (labels.length > 0) {
601
- for (let k = 0; k < tempObj.definition.length; k++) {
602
- if (labels[k].length > 1) {
603
- tempObj.definition[k].label = labels[k]
604
- if (props.showInputColumnsLabelOnTitle) { // 动态行label展示在标题
605
- if (k === tempObj.definition.length - 1)
606
- tempObj.label = `${tempObj.label} ${labels[k].replace(/\s*/g, '')}`
607
- else
608
- tempObj.label = `${tempObj.label} ${labels[k].replace(/\s*/g, '')} :`
609
- }
610
- else { // 动态行仅展示为’动态行‘
611
- tempObj.label = '动态行'
612
- }
613
- }
614
- else {
615
- tempObj.definition[k].label = ''
616
- }
617
- }
618
- }
619
- tempObj.type = 'inputColumns'
620
- break
621
- case 'list' :
622
- tempObj.valueText = row[j].listHead
623
- tempObj.type = 'list'
624
- tempObj.listType = row[j].listType
625
- tempObj.dataIndex = row[j].dataIndex
626
- if (row[j].content)
627
- tempObj.content = row[j].content
628
- tempObj.listLength = row[j].listLength
629
- listArr.push(tempObj)
630
- tempObj = {
631
- label: '',
632
- type: undefined,
633
- dataIndex: '',
634
- }
635
- }
636
- }
637
- if (listArr.length > 0)
638
- formColumns.push({ type: 'list', content: listArr })
639
- else if (tempObj.type || tempObj.label)
640
- formColumns.push(tempObj)
641
- listArr = []
642
- tempObj = {
643
- label: '',
644
- type: undefined,
645
- dataIndex: '',
646
- }
647
- }
648
- activatedConfig.columns = formColumns
649
- }
650
-
651
- // 获取子组件更新的file列表
652
- function updateFile(files, index) {
653
- files.forEach((file) => {
654
- if (file.content)
655
- delete file.content
656
- if (file.file)
657
- delete file.file
658
- if (file.objectUrl)
659
- delete file.objectUrl
660
- })
661
- if (index.includes('@@@'))
662
- changeDeepObjectDeepCopy(activatedConfig.data.images, index, files)
663
- else
664
- activatedConfig.data.images[index] = files
665
- }
666
-
667
- // function handleInputDeepChange(event, dataIndex) {
668
- // this.$forceUpdate()
669
- // }
670
- function getDeepObject(obj, strPath) {
671
- const arr = strPath.split('@@@')
672
- let result = obj[arr[0]]
673
- arr.shift()
674
- try {
675
- while (arr.length > 0) {
676
- result = result[arr[0]]
677
- arr.shift()
678
- }
679
- }
680
- catch {
681
- result = undefined
682
- }
683
- return result
684
- }
685
-
686
- function changeDeepObject(obj, strPath, newVal) {
687
- const arr = strPath.split('@@@')
688
- if (arr.length === 1) {
689
- obj[arr[0]] = newVal
690
- }
691
- else {
692
- let result = obj[arr[0]]
693
- arr.shift()
694
- while (arr.length > 1) {
695
- result = result[arr[0]]
696
- arr.shift()
697
- }
698
- result[arr[0]] = newVal
699
- }
700
- }
701
-
702
- function changeDeepObjectDeepCopy(obj, strPath, newVal) {
703
- const arr = strPath.split('@@@')
704
- if (arr.length === 1) {
705
- obj[arr[0]] = newVal
706
- }
707
- else {
708
- let result = obj[arr[0]]
709
- arr.shift()
710
- while (arr.length > 1) {
711
- result = result[arr[0]]
712
- arr.shift()
713
- }
714
- Object.assign(result[arr[0]], newVal)
715
- }
716
- }
717
-
718
- // ------------------------- 初始化 -------------------------
719
- initComponent()
720
-
721
- function initComponent() {
722
- if (props.localConfig !== undefined && Object.keys(props.localConfig).length > 0) {
723
- Object.assign(activatedConfig, props.localConfig)
724
-
725
- if (activatedConfig.designMode === 'json') {
726
- activatedConfig.data = {}
727
- Object.assign(activatedConfig.data, props.configData)
728
- nextTick(() => {
729
- scanFinish.value = true
730
- })
731
- return
732
- }
733
- else {
734
- const lock = { status: true }
735
- if (activatedConfig.slotsDeclare) {
736
- getConfigAndJoin(activatedConfig, lock)
737
- timer = setInterval(() => {
738
- if (!lock.status) {
739
- clearInterval(timer)
740
- // 将配置文件格式更改为更利于Form的组织形式
741
- formatConfigToForm(activatedConfig)
742
- nextTick(() => {
743
- scanFinish.value = true
744
- })
745
- }
746
- }, 100)
747
- }
748
- else {
749
- formatConfigToForm(activatedConfig)
750
- nextTick(() => {
751
- scanFinish.value = true
752
- })
753
- }
754
- if (props.configData !== undefined)
755
- Object.assign(activatedConfig.data, props.configData)
756
-
757
- activatedConfig.columns.forEach((row) => {
758
- if (row.dataIndex && row.dataIndex.includes('@@@')) {
759
- if (!activatedConfig.tempData) {
760
- activatedConfig.tempData = {}
761
- }
762
- if (row.type === 'images') {
763
- activatedConfig.tempData[row.dataIndex] = getDeepObject(activatedConfig.data.images, row.dataIndex)
764
- }
765
- else {
766
- activatedConfig.tempData[row.dataIndex] = getDeepObject(activatedConfig.data, row.dataIndex)
767
- }
768
- }
769
- // 为了兼容旧的结构,保留原有的逻辑
770
- if (row.forEach) {
771
- row.forEach((item) => {
772
- if (item.dataIndex && item.dataIndex.includes('@@@')) {
773
- if (!activatedConfig.tempData) {
774
- activatedConfig.tempData = {}
775
- }
776
- if (item.type === 'images') {
777
- activatedConfig.tempData[item.dataIndex] = getDeepObject(activatedConfig.data.images, item.dataIndex)
778
- }
779
- else {
780
- activatedConfig.tempData[item.dataIndex] = getDeepObject(activatedConfig.data, item.dataIndex)
781
- }
782
- }
783
- })
784
- }
785
- })
786
- console.warn('初始化完成,配置:', activatedConfig)
787
- }
788
- }
789
-
790
- if (props.slotName)
791
- activatedConfigName.value = props.slotName
792
-
793
- else
794
- activatedConfigName.value = props.configName
795
-
796
- getConfigByNameWithoutIndexedDB(activatedConfigName.value).then((result) => {
797
- Object.assign(activatedConfig, result)
798
- if (activatedConfig.designMode === 'json') {
799
- if (props.configData === undefined) {
800
- console.error('无法找到json配置的数据')
801
- }
802
- else {
803
- activatedConfig.data = {}
804
- Object.assign(activatedConfig.data, props.configData)
805
- nextTick(() => {
806
- scanFinish.value = true
807
- })
808
- }
809
- }
810
- else {
811
- const lock = { status: true }
812
- if (activatedConfig.slotsDeclare) {
813
- getConfigAndJoin(activatedConfig, lock)
814
- timer = setInterval(() => {
815
- if (!lock.status) {
816
- clearInterval(timer)
817
- // 将配置文件格式更改为更利于Form的组织形式
818
- formatConfigToForm(activatedConfig)
819
- nextTick(() => {
820
- scanFinish.value = true
821
- })
822
- }
823
- }, 100)
824
- }
825
- else {
826
- formatConfigToForm(activatedConfig)
827
- nextTick(() => {
828
- scanFinish.value = true
829
- })
830
- }
831
- if (props.configData !== undefined)
832
- Object.assign(activatedConfig.data, props.configData)
833
- }
834
-
835
- activatedConfig.columns.forEach((row) => {
836
- row.forEach((item) => {
837
- if (item.dataIndex && item.dataIndex.includes('@@@')) {
838
- if (!activatedConfig.tempData) {
839
- activatedConfig.tempData = {}
840
- activatedConfig.tempData[item.dataIndex] = getDeepObject(activatedConfig.data, item.dataIndex)
841
- if (item.type === 'images')
842
- activatedConfig.tempData[item.dataIndex] = getDeepObject(activatedConfig.data.images, item.dataIndex)
843
- }
844
- else {
845
- activatedConfig.tempData[item.dataIndex] = getDeepObject(activatedConfig.data, item.dataIndex)
846
- if (item.type === 'images')
847
- activatedConfig.tempData[item.dataIndex] = getDeepObject(activatedConfig.data.images, item.dataIndex)
848
- }
849
- }
850
- })
851
- })
852
- }).catch((e) => {
853
- console.error(e)
854
- })
855
- }
856
- function getNow() {
857
- const date = new Date()
858
- const year = date.getFullYear()
859
- const month = String(date.getMonth() + 1).padStart(2, '0')
860
- const day = String(date.getDate()).padStart(2, '0')
861
- const hours = String(date.getHours()).padStart(2, '0')
862
- const minutes = String(date.getMinutes()).padStart(2, '0')
863
- const seconds = String(date.getSeconds()).padStart(2, '0')
864
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
865
- }
866
-
867
- // 移除了旧的日期时间选择器相关方法,现在使用 DateTimeSecondsPicker 组件
868
- </script>
869
-
870
- <template>
871
- <!-- 标题栏 -->
872
- <van-nav-bar
873
- v-if="showNav"
874
- title="动态报表表单"
875
- left-text="返回"
876
- left-arrow
877
- @click-left="onClickLeft"
878
- />
879
- <div v-if="scanFinish" class="main">
880
- <template v-if="activatedConfig.designMode === 'json'">
881
- <XReportFormJsonRender :json-config="activatedConfig" />
882
- </template>
883
- <template v-else>
884
- <!-- 标题 -->
885
- <h2 v-if="activatedConfig.title" class="title" v-html="activatedConfig.title" />
886
- <!-- 副标题 -->
887
- <!-- <div v-if="activatedConfig.subTitle && activatedConfig.subTitle.length > 0" class="form_item"> -->
888
- <!-- <van-collapse v-model="activeCollapseName" accordion> -->
889
- <!-- <van-collapse-item title="副标题" name="副标题"> -->
890
- <!-- <van-form> -->
891
- <!-- <van-cell-group inset> -->
892
- <!-- <template v-for="(subCell, cellIndex) in activatedConfig.subTitle" :key="cellIndex"> -->
893
- <!-- <template v-if="subCell.type === 'inputs'"> -->
894
- <!-- <template v-for="(item, itemIndex) in formatInputs(subCell)" :key="itemIndex"> -->
895
- <!-- <van-field -->
896
- <!-- v-model="activatedConfig.data[subCell.dataIndex][itemIndex]" -->
897
- <!-- :label="`${item}:`" -->
898
- <!-- clearable -->
899
- <!-- type="textarea" -->
900
- <!-- rows="1" -->
901
- <!-- autosize -->
902
- <!-- :placeholder="`请输入${item}`" -->
903
- <!-- /> -->
904
- <!-- </template> -->
905
- <!-- </template> -->
906
- <!-- <template v-else> -->
907
- <!-- <van-field -->
908
- <!-- v-model="activatedConfig.data[subCell.dataIndex]" -->
909
- <!-- :label="`${subCell.format}:`" -->
910
- <!-- clearable -->
911
- <!-- type="textarea" -->
912
- <!-- rows="1" -->
913
- <!-- autosize -->
914
- <!-- :placeholder="`请输入${subCell.format}`" -->
915
- <!-- /> -->
916
- <!-- </template> -->
917
- <!-- </template> -->
918
- <!-- </van-cell-group> -->
919
- <!-- </van-form> -->
920
- <!-- </van-collapse-item> -->
921
- <!-- </van-collapse> -->
922
- <!-- </div> -->
923
- <!-- 表单项 -->
924
- <div class="form_item">
925
- <van-collapse v-model="activeCollapseName" accordion>
926
- <van-collapse-item title="操作卡内容" name="操作卡内容">
927
- <van-form>
928
- <template v-for="(row, index) in activatedConfig.columns" :key="index">
929
- <!-- value属性 -->
930
- <van-cell-group v-if="row.type === 'showValue'" inset class="cell_group">
931
- <van-cell center :title="row.label" :value="row.valueText" />
932
- </van-cell-group>
933
- <!-- input -->
934
- <van-cell-group v-else-if="row.type === 'input'" inset class="cell_group" :title="row.label" :class="{ 'required-field': row.required }">
935
- <template v-if="row.inputReadOnly === true">
936
- <template v-if="row.dataIndex.includes('@@@')">
937
- <van-field
938
- v-model="activatedConfig.tempData[row.dataIndex]"
939
- :label="`${row.label}:`"
940
- :placeholder="`请输入${row.label}`"
941
- clearable
942
- type="textarea"
943
- rows="1"
944
- :disabled="true"
945
- autosize
946
- />
947
- </template>
948
- <template v-else>
949
- <van-field
950
- v-model="activatedConfig.data[row.dataIndex]"
951
- :label="`${row.label}:`"
952
- :placeholder="`请输入${row.label}`"
953
- clearable
954
- type="textarea"
955
- rows="1"
956
- :disabled="true"
957
- autosize
958
- />
959
- </template>
960
- </template>
961
- <template v-else>
962
- <template v-if="row.dataIndex.includes('@@@')">
963
- <van-field
964
- v-model="activatedConfig.tempData[row.dataIndex]"
965
- :label="`${row.label}:`"
966
- :placeholder="`请输入${row.label}`"
967
- clearable
968
- type="textarea"
969
- rows="1"
970
- autosize
971
- />
972
- </template>
973
- <template v-else>
974
- <van-field
975
- v-model="activatedConfig.data[row.dataIndex]"
976
- :label="`${row.label}:`"
977
- :placeholder="`请输入${row.label}`"
978
- clearable
979
- type="textarea"
980
- rows="1"
981
- autosize
982
- />
983
- </template>
984
- </template>
985
- </van-cell-group>
986
- <!-- curDateInput -->
987
- <van-cell-group v-else-if="row.type === 'curDateInput'" inset style="margin-top: 4vh">
988
- <div class="cur-date-input-container">
989
- <div class="cur-date-input-content">
990
- <div class="cur-date-input-label">
991
- {{ row.valueText || '当前时间' }}
992
- </div>
993
- <div class="cur-date-input-value">
994
- {{ activatedConfig.data[row.dataIndex] || '未操作' }}
995
- </div>
996
- </div>
997
- <div class="cur-date-input-action">
998
- <van-button
999
- v-if="!activatedConfig.data[row.dataIndex]"
1000
- size="small"
1001
- type="primary"
1002
- @click="activatedConfig.data[row.dataIndex] = getNow()"
1003
- >
1004
- {{ row.text || '设置时间' }}
1005
- </van-button>
1006
- <van-button
1007
- v-else
1008
- size="small"
1009
- type="default"
1010
- @click="activatedConfig.data[row.dataIndex] = getNow()"
1011
- >
1012
- {{ row.text || '重新设置' }}
1013
- </van-button>
1014
- </div>
1015
- </div>
1016
- </van-cell-group>
1017
- <!-- signature -->
1018
- <van-cell-group v-else-if="row.type === 'signature'" inset style="margin-top: 4vh">
1019
- <van-field
1020
- :label="row.valueText ? `${row.valueText}:` : ''"
1021
-
1022
- rows="1"
1023
- autosize readonly
1024
- >
1025
- <template #button>
1026
- <XSignature v-model="activatedConfig.data[row.dataIndex]" />
1027
- </template>
1028
- </van-field>
1029
- </van-cell-group>
1030
- <!-- datePicker -->
1031
- <van-cell-group v-else-if="row.type === 'datePicker'" inset :title="row.label" :class="{ 'required-field': row.required }">
1032
- <DateTimeSecondsPicker
1033
- v-model="activatedConfig.data[row.dataIndex]"
1034
- :label="`${row.label || '日期'}:`"
1035
- placeholder="请选择日期"
1036
- title="选择日期"
1037
- format="YYYY-MM-DD"
1038
- />
1039
- </van-cell-group>
1040
- <!-- timePicker -->
1041
- <van-cell-group v-else-if="row.type === 'timePicker'" inset :title="row.label" :class="{ 'required-field': row.required }">
1042
- <DateTimeSecondsPicker
1043
- v-model="activatedConfig.data[row.dataIndex]"
1044
- :label="`${row.label || '时间'}:`"
1045
- placeholder="请选择时间"
1046
- title="选择时间"
1047
- format="YYYY-MM-DD HH:mm:ss"
1048
- />
1049
- </van-cell-group>
1050
- <!-- dateTimeSecondsPicker -->
1051
- <van-cell-group v-else-if="row.type === 'dateTimeSecondsPicker'" inset :title="row.label" :class="{ 'required-field': row.required }">
1052
- <DateTimeSecondsPicker
1053
- v-model="activatedConfig.data[row.dataIndex]"
1054
- :label="`${row.label || '完整时间'}:`"
1055
- placeholder="请选择完整时间"
1056
- title="选择完整时间"
1057
- format="YYYY-MM-DD HH:mm:ss"
1058
- />
1059
- </van-cell-group>
1060
- <!-- inputs -->
1061
- <van-cell-group v-else-if="row.type === 'inputs'" inset :title="row.label || ''" :class="{ 'required-field': row.required }">
1062
- <template v-if="row.inputReadOnly === true">
1063
- <template v-for="(item, _index) in formatInputs(row)" :key="_index">
1064
- <van-field
1065
- v-model="activatedConfig.data[row.dataIndex][_index]"
1066
- :label="`${item}:`"
1067
- :placeholder="`请输入${item}`"
1068
- clearable
1069
- type="textarea"
1070
- rows="1"
1071
- :disabled="true"
1072
- autosize
1073
- />
1074
- </template>
1075
- </template>
1076
- <template v-else>
1077
- <template v-for="(item, _index) in formatInputs(row)" :key="_index">
1078
- <van-field
1079
- v-model="activatedConfig.data[row.dataIndex][_index]"
1080
- :label="`${item}:`"
1081
- :placeholder="`请输入${item}`"
1082
- clearable
1083
- type="textarea"
1084
- rows="1"
1085
- autosize
1086
- />
1087
- </template>
1088
- </template>
1089
- </van-cell-group>
1090
- <!-- images -->
1091
- <van-cell-group v-else-if="row.type === 'images'" inset :title="row.label">
1092
- <template v-if="row.dataIndex.includes('@@@')">
1093
- <Uploader
1094
- upload-mode="oss"
1095
- :image-list="activatedConfig.tempData[row.dataIndex]"
1096
- :outer-index="row.dataIndex"
1097
- authority="admin"
1098
- @update-file-list="updateFile"
1099
- />
1100
- </template>
1101
- <template v-else>
1102
- <Uploader
1103
- upload-mode="oss"
1104
- :image-list="activatedConfig.data.images[row.dataIndex]"
1105
- :outer-index="row.dataIndex"
1106
- authority="admin"
1107
- @update-file-list="updateFile"
1108
- />
1109
- </template>
1110
- </van-cell-group>
1111
- <!-- 动态行 -->
1112
- <template v-else-if="row.type === 'inputColumns'">
1113
- <!-- <van-divider class="divider"> -->
1114
- <!-- {{ row.label }} -->
1115
- <!-- </van-divider> -->
1116
- <van-cell-group inset :title="row.label" style="background-color: #eff2f5">
1117
- <van-cell-group v-for="(_item, _index) in activatedConfig.data[row.dataIndex]" :key="_index" inset class="my-cell-group" style="margin: 0 0 10px 0">
1118
- <div v-for="(def, defIndex) in row.definition" :key="defIndex">
1119
- <template v-if="def.type === 'curDateInput'">
1120
- <div class="cur-date-input-container">
1121
- <div class="cur-date-input-content">
1122
- <div class="cur-date-input-label">
1123
- {{ def.label }}
1124
- </div>
1125
- <div class="cur-date-input-value">
1126
- {{ activatedConfig.data[row.dataIndex][_index][def.dataIndex] || '未设置' }}
1127
- </div>
1128
- </div>
1129
- <div class="cur-date-input-action">
1130
- <van-button
1131
- v-if="!activatedConfig.data[row.dataIndex][_index][def.dataIndex]"
1132
- size="small"
1133
- type="primary"
1134
- @click="activatedConfig.data[row.dataIndex][_index][def.dataIndex] = getNow()"
1135
- >
1136
- {{ def.text || '设置时间' }}
1137
- </van-button>
1138
- <van-button
1139
- v-else
1140
- size="small"
1141
- type="default"
1142
- @click="activatedConfig.data[row.dataIndex][_index][def.dataIndex] = getNow()"
1143
- >
1144
- {{ def.text || '重新设置' }}
1145
- </van-button>
1146
- </div>
1147
- </div>
1148
- </template>
1149
- <van-field
1150
- v-else
1151
- v-model="activatedConfig.data[row.dataIndex][_index][def.dataIndex]"
1152
- :label="`${def.label}:`"
1153
- type="textarea"
1154
- rows="1"
1155
- autosize
1156
- :placeholder="`请输入${def.label}`"
1157
- clearable
1158
- :class="{ 'required-field': def.required }"
1159
- />
1160
- </div>
1161
- </van-cell-group>
1162
- </van-cell-group>
1163
-
1164
- <!-- <div class="button_group"> -->
1165
- <!-- <van-button class="input_columns_button" plain type="primary" @click="addInputColumns(row.dataIndex, row.definition)"> -->
1166
- <!-- <van-icon name="plus" /> -->
1167
- <!-- </van-button> -->
1168
- <!-- <van-button class="input_columns_button" plain type="primary" @click="removeInputColumns(row.dataIndex)"> -->
1169
- <!-- <van-icon name="minus" /> -->
1170
- <!-- </van-button> -->
1171
- <!-- </div> -->
1172
- </template>
1173
- <!-- list -->
1174
- <template v-else-if="row.type === 'list'">
1175
- <template v-for="(listIndex) in row.content[0].listLength" :key="listIndex">
1176
- <div style="margin-top: 3vh; margin-bottom: 3vh">
1177
- <van-cell-group inset :title="row.label" style="background-color: #eff2f5">
1178
- <template v-for="(_cell, cellIndex) in row.content" :key="listIndex + cellIndex">
1179
- <template v-if="_cell.listType === 'input'">
1180
- <van-field
1181
- v-model="activatedConfig.data[_cell.dataIndex][listIndex]"
1182
- :label="_cell.valueText"
1183
- type="textarea"
1184
- autosize
1185
- />
1186
- </template>
1187
- <template v-else-if="_cell.listType === 'value'">
1188
- <van-field
1189
- :disabled="true"
1190
- :label="_cell.valueText"
1191
- type="textarea"
1192
- :placeholder="_cell.content[listIndex - 1]"
1193
- autosize
1194
- />
1195
- </template>
1196
- </template>
1197
- </van-cell-group>
1198
- </div>
1199
- </template>
1200
- </template>
1201
- </template>
1202
- </van-form>
1203
- </van-collapse-item>
1204
- </van-collapse>
1205
- </div>
1206
- <!-- 提交按钮 -->
1207
- <div class="submit_button">
1208
- <van-button round block type="primary" native-type="submit" @click="onSubmit">
1209
- 提交
1210
- </van-button>
1211
- </div>
1212
- </template>
1213
- </div>
1214
- <!-- 骨架屏 -->
1215
- <div v-else class="skeleton">
1216
- <van-skeleton title :row="5" />
1217
- </div>
1218
-
1219
- <!-- 已移除 VueHashCalendar,现在使用内置的 DateTimeSecondsPicker 组件 -->
1220
- </template>
1221
-
1222
- <style scoped lang="less">
1223
- .main {
1224
- padding-top: 4vh;
1225
- width: 100vw;
1226
- height: 100vh;
1227
- background-color: #eff2f5;
1228
-
1229
- .title {
1230
- padding-bottom: 2vh;
1231
- color: rgb(50, 50, 51);
1232
- text-align: center;
1233
- margin: 0 0 3vh;
1234
- }
1235
-
1236
- .text_box {
1237
- margin-top: 2vh;
1238
- margin-bottom: 2vh;
1239
- }
1240
-
1241
- .main_text {
1242
- padding-left: 16px;
1243
- font-weight: 400;
1244
- line-height: 1.6;
1245
- margin: 0 0 40px;
1246
- color: #969799;
1247
- font-size: 14px;
1248
- }
1249
-
1250
- .show_value_item {
1251
- text-align: center;
1252
- font-size: 1.2em;
1253
- }
1254
-
1255
- .cell_group {
1256
- //margin-top: 2vh;
1257
- //margin-bottom: 2vh;
1258
- }
1259
-
1260
- .form_item {
1261
- margin-top: 2vh;
1262
- }
1263
-
1264
- .button_group {
1265
- text-align: center;
1266
- margin-top: 3vh;
1267
- margin-bottom: 3vh;
1268
- }
1269
-
1270
- .button_group > :first-child {
1271
- margin-right: 3vw;
1272
- }
1273
-
1274
- .divider {
1275
- color: #1989fa;
1276
- border-color: #1989fa;
1277
- padding: 0 16px;
1278
- }
1279
-
1280
- .submit_button {
1281
- background-color: #eff2f5;
1282
- padding: 5vh;
1283
- }
1284
- }
1285
-
1286
- .skeleton {
1287
- margin-top: 5vh;
1288
- }
1289
- .my-cell-group {
1290
- margin: 0 0 10px 0;
1291
- }
1292
- :deep(.van-collapse-item__content) {
1293
- background-color: #eff2f5;
1294
- padding: 10px 0;
1295
- }
1296
- :deep(.van-cell-group__title) {
1297
- padding-top: 10px;
1298
- padding-bottom: 10px;
1299
- }
1300
- :deep(.van-field__label) {
1301
- font-weight: 600;
1302
- }
1303
-
1304
- // 必填字段星号样式 - 只对星号生效
1305
- :deep(.van-field__label) {
1306
- // 将包含星号的文本进行特殊处理
1307
- &[style*='color'] {
1308
- color: #323233 !important;
1309
- }
1310
- }
1311
-
1312
- // 使用自定义类名来处理必填字段
1313
- .required-field :deep(.van-field__label) {
1314
- color: #323233;
1315
-
1316
- &::before {
1317
- content: '*';
1318
- color: #ee0a24;
1319
- margin-right: 2px;
1320
- }
1321
- }
1322
- :deep(.van-uploader__wrapper) {
1323
- padding: 10px;
1324
- display: flex;
1325
- flex-wrap: wrap;
1326
- justify-content: space-between; /* 水平方向上左右分散对齐 */
1327
- align-items: center; /* 垂直方向上上下中心对齐 */
1328
- }
1329
-
1330
- // 自定义的当前日期输入框样式
1331
- .cur-date-input-container {
1332
- padding: 16px;
1333
- display: flex;
1334
- align-items: flex-start;
1335
- justify-content: space-between;
1336
- background: white;
1337
- border-radius: 8px;
1338
-
1339
- .cur-date-input-content {
1340
- flex: 1;
1341
- margin-right: 12px;
1342
- min-width: 0; // 防止flex子元素撑破容器
1343
-
1344
- .cur-date-input-label {
1345
- font-size: 14px;
1346
- font-weight: 600;
1347
- color: #323233;
1348
- line-height: 1.4;
1349
- margin-bottom: 4px;
1350
- word-wrap: break-word;
1351
- white-space: normal;
1352
- display: -webkit-box;
1353
- -webkit-line-clamp: 4; // 最多显示2行
1354
- -webkit-box-orient: vertical;
1355
- overflow: hidden;
1356
- }
1357
-
1358
- .cur-date-input-value {
1359
- font-size: 15px;
1360
- font-weight: 600;
1361
- color: #666;
1362
- line-height: 1.3;
1363
- word-wrap: break-word;
1364
- white-space: normal;
1365
- }
1366
- }
1367
-
1368
- .cur-date-input-action {
1369
- flex-shrink: 0;
1370
- display: flex;
1371
- align-items: center;
1372
- }
1373
- }
1374
-
1375
- // 动态行中的当前日期输入框样式调整
1376
- .my-cell-group .cur-date-input-container {
1377
- padding: 12px;
1378
- margin: 8px 0;
1379
-
1380
- .cur-date-input-content {
1381
- margin-right: 8px;
1382
-
1383
- .cur-date-input-label {
1384
- font-size: 13px;
1385
- margin-bottom: 2px;
1386
- }
1387
-
1388
- .cur-date-input-value {
1389
- font-size: 12px;
1390
- }
1391
- }
1392
- }
1393
- </style>
1
+ <script setup lang="ts">
2
+ import Uploader from '@af-mobile-client-vue3/components/core/Uploader/index.vue'
3
+ import DateTimeSecondsPicker from '@af-mobile-client-vue3/components/data/XReportForm/DateTimeSecondsPicker.vue'
4
+ import XReportFormJsonRender from '@af-mobile-client-vue3/components/data/XReportForm/XReportFormJsonRender.vue'
5
+ import XSignature from '@af-mobile-client-vue3/components/data/XSignature/index.vue'
6
+ import { getConfigByNameWithoutIndexedDB } from '@af-mobile-client-vue3/services/api/common'
7
+ import {
8
+ showFailToast,
9
+ Button as vanButton,
10
+ Cell as vanCell,
11
+ CellGroup as vanCellGroup,
12
+ Collapse as vanCollapse,
13
+ CollapseItem as vanCollapseItem,
14
+ Field as vanField,
15
+ Form as vanForm,
16
+ NavBar as vanNavBar,
17
+ Skeleton as vanSkeleton,
18
+ } from 'vant'
19
+ import { defineEmits, nextTick, reactive, ref, watch } from 'vue'
20
+ // 移除 VueHashCalendar,使用自定义的 DateTimeSecondsPicker
21
+
22
+ // ------------------------- 类型定义 -------------------------
23
+ interface configDefine {
24
+ data?: any
25
+ designMode?: string
26
+ title?: string | undefined
27
+ subTitle?: Array<cell> | undefined
28
+ columns?: Array<any>
29
+ slotsDeclare?: Array<string> | undefined
30
+ tempData?: any
31
+ }
32
+
33
+ interface getConfigLock {
34
+ status: boolean
35
+ }
36
+
37
+ interface cell {
38
+ type: string
39
+ dataIndex: string
40
+ format: string | undefined
41
+ }
42
+
43
+ // ------------------------- 数据 -------------------------
44
+ const props = defineProps({
45
+ // 配置名
46
+ configName: String,
47
+ // 是否将动态行的label显示在标题上
48
+ showInputColumnsLabelOnTitle: { default: false },
49
+ slotName: String,
50
+ showNav: { default: true },
51
+ localConfig: { default: undefined },
52
+ configData: { default: undefined },
53
+ })
54
+ const emit = defineEmits(['saveConfig'])
55
+ // 原始配置
56
+ const activatedConfig: configDefine = reactive({})
57
+ // 激活的配置名
58
+ const activatedConfigName = ref('')
59
+ // 下载的配置
60
+ const configFromWeb = reactive({})
61
+ // 定时器
62
+ let timer: NodeJS.Timeout
63
+ // 控制组件渲染
64
+ const scanFinish = ref(false)
65
+ // 折叠面板当前激活的值
66
+ const activeCollapseName = ref('副标题')
67
+
68
+ watch(() => props.configName, () => {
69
+ // 这里是你的处理逻辑
70
+ initComponent()
71
+ })
72
+
73
+ // ------------------------- 方法 -------------------------
74
+ // 为inputs类型的数据,整理格式,供form展示用
75
+ function formatInputs(cell: cell): string[] {
76
+ const format = cell.format
77
+ const arr = format.split('{}')
78
+ const result = []
79
+ const reg = /[\u4E00-\u9FA5a-z]/gi
80
+ arr.forEach((str) => {
81
+ if (str.length >= 1 && str.match(reg)) {
82
+ const strs = str.match(reg)
83
+ result.push(strs.join(''))
84
+ }
85
+ })
86
+ if (!activatedConfig.data[cell.dataIndex][result.length - 1]) {
87
+ let dataKeySet = Object.keys(activatedConfig.data)
88
+ dataKeySet = dataKeySet.filter((key) => {
89
+ return key.startsWith(cell.dataIndex)
90
+ })
91
+ dataKeySet.sort()
92
+ const target = dataKeySet[0]
93
+ dataKeySet.shift()
94
+ dataKeySet.forEach((key) => {
95
+ activatedConfig.data[key].forEach((item) => {
96
+ activatedConfig.data[target].push(item)
97
+ })
98
+ })
99
+
100
+ for (let i = 0; i < result.length; i++) {
101
+ if (!activatedConfig.data[cell.dataIndex][i])
102
+ activatedConfig.data[cell.dataIndex][i] = undefined
103
+ }
104
+ }
105
+ return result
106
+ }
107
+
108
+ // 左上角返回按钮
109
+ function onClickLeft(): void {
110
+ history.back()
111
+ }
112
+
113
+ function generateDefaultRequiredMessage(field: any): string {
114
+ const label = field.label || field.text || field.valueText || ''
115
+
116
+ switch (field.type) {
117
+ case 'input':
118
+ return `请填写${label || '此项'}`
119
+ case 'datePicker':
120
+ return `请选择${label || '日期'}`
121
+ case 'timePicker':
122
+ return `请选择${label || '时间'}`
123
+ case 'dateTimeSecondsPicker':
124
+ return `请选择${label || '完整时间'}`
125
+ case 'curDateInput':
126
+ return `请设置${label || '时间'}`
127
+ case 'signature':
128
+ return `请完成${label || '签名'}`
129
+ case 'images':
130
+ return `请上传${label || '图片'}`
131
+ case 'inputs':
132
+ return `请完成${label || '此项'}的填写`
133
+ default:
134
+ return `请完成${label || '此项'}的填写`
135
+ }
136
+ }
137
+
138
+ // 检查字段值是否为空
139
+ function isFieldEmpty(value: any): boolean {
140
+ if (value === null || value === undefined || value === '') {
141
+ return true
142
+ }
143
+ if (Array.isArray(value) && value.length === 0) {
144
+ return true
145
+ }
146
+ return typeof value === 'object' && Object.keys(value).length === 0
147
+ }
148
+
149
+ // 表单校验
150
+ function validateForm(): boolean {
151
+ const errors: string[] = []
152
+
153
+ // 校验主表单字段
154
+ activatedConfig.columns.forEach((row: any) => {
155
+ if (row.required === true) {
156
+ const dataIndex = row.dataIndex
157
+ let value
158
+
159
+ if (dataIndex.includes('@@@')) {
160
+ value = activatedConfig.tempData?.[dataIndex]
161
+ }
162
+ else {
163
+ if (row.type === 'images') {
164
+ value = activatedConfig.data.images?.[dataIndex]
165
+ }
166
+ else {
167
+ value = activatedConfig.data[dataIndex]
168
+ }
169
+ }
170
+
171
+ if (isFieldEmpty(value)) {
172
+ const message = row.requiredMessage || generateDefaultRequiredMessage(row)
173
+ errors.push(message)
174
+ }
175
+ }
176
+
177
+ // 校验动态行中的字段
178
+ if (row.type === 'inputColumns' && row.definition) {
179
+ const dynamicData = activatedConfig.data[row.dataIndex]
180
+ if (Array.isArray(dynamicData)) {
181
+ dynamicData.forEach((item: any, index: number) => {
182
+ row.definition.forEach((def: any) => {
183
+ if (def.required === true) {
184
+ const value = item[def.dataIndex]
185
+ if (isFieldEmpty(value)) {
186
+ const message = def.requiredMessage || generateDefaultRequiredMessage(def)
187
+ errors.push(`第${index + 1}行:${message}`)
188
+ }
189
+ }
190
+ })
191
+ })
192
+ }
193
+ }
194
+ })
195
+
196
+ // 校验副标题字段(如果有的话)
197
+ if (activatedConfig.subTitle) {
198
+ activatedConfig.subTitle.forEach((subCell: any) => {
199
+ if (subCell.required === true) {
200
+ const value = activatedConfig.data[subCell.dataIndex]
201
+ if (isFieldEmpty(value)) {
202
+ const message = subCell.requiredMessage || generateDefaultRequiredMessage(subCell)
203
+ errors.push(message)
204
+ }
205
+ }
206
+ })
207
+ }
208
+
209
+ if (errors.length > 0) {
210
+ // 显示第一个错误信息
211
+ showFailToast(errors[0])
212
+
213
+ return false
214
+ }
215
+
216
+ return true
217
+ }
218
+
219
+ // 提交按钮
220
+ function onSubmit(): void {
221
+ // 调试:打印所有字段的required状态
222
+ console.log('所有字段配置:', activatedConfig.columns.map(row => ({
223
+ type: row.type,
224
+ label: row.label,
225
+ required: row.required,
226
+ dataIndex: row.dataIndex,
227
+ })))
228
+
229
+ // 先进行表单校验
230
+ if (!validateForm()) {
231
+ return
232
+ }
233
+
234
+ if (activatedConfig.tempData) {
235
+ const keys = Object.keys(activatedConfig.tempData)
236
+ keys.forEach((key) => {
237
+ changeDeepObject(activatedConfig.data, key, activatedConfig.tempData[key])
238
+ })
239
+ }
240
+ console.warn('保存', activatedConfig)
241
+ emit('saveConfig', activatedConfig)
242
+ }
243
+
244
+ // // 动态行新增
245
+ // function addInputColumns(dataIndex: string, define: Array<any>): void {
246
+ // const temp = {}
247
+ // define.forEach((def) => {
248
+ // let begin = def.initialValue
249
+ // if (def.type === 'increment') {
250
+ // if (activatedConfig.data[dataIndex].length > 0) {
251
+ // begin = activatedConfig.data[dataIndex][activatedConfig.data[dataIndex].length - 1][def.dataIndex]
252
+ // begin = Number.parseInt(begin)
253
+ // }
254
+ // temp[def.dataIndex] = begin + def.step
255
+ // }
256
+ // else {
257
+ // temp[def.dataIndex] = ''
258
+ // }
259
+ // })
260
+ // activatedConfig.data[dataIndex].push(temp)
261
+ // }
262
+ //
263
+ // // 动态行删除
264
+ // function removeInputColumns(dataIndex: string) {
265
+ // activatedConfig.data[dataIndex].pop()
266
+ // }
267
+
268
+ // 扫描所有插槽名
269
+ function scanConfigName(config: configDefine, resut: Array<string>): void {
270
+ if (config.slotsDeclare) {
271
+ config.slotsDeclare.forEach((name) => {
272
+ resut.push(name)
273
+ })
274
+ }
275
+ }
276
+
277
+ // 检查slot是否在配置文件中包含,如果没有包含,则视为非法获取
278
+ function checkSlotDefine(config: configDefine): boolean {
279
+ const slotsDeclare = config.slotsDeclare
280
+ const total = slotsDeclare.length
281
+ let count = 0
282
+ slotsDeclare.forEach((declare) => {
283
+ config.columns.forEach((row: Array<any>) => {
284
+ row.forEach((cell) => {
285
+ if (cell.slotConfig === declare)
286
+ count++
287
+ })
288
+ })
289
+ })
290
+
291
+ return count === total
292
+ }
293
+
294
+ // 扫描配置,如果有插槽则拼接插槽
295
+ function scanConfigSlot(config: configDefine): void {
296
+ const columnsArr: Array<any> = config.columns
297
+ for (let i = 0; i < columnsArr.length; i++) {
298
+ for (let j = 0; j < columnsArr[i].length; j++) {
299
+ // 如果发现type为slot,开始匹配对应的slot配置文件
300
+ if (columnsArr[i][j].type === 'slot') {
301
+ const targetName = columnsArr[i][j].slotConfig
302
+ // 找不到目标插槽配置
303
+ if (!configFromWeb[targetName] || !configFromWeb[targetName].columns)
304
+ return
305
+
306
+ // 替换columns,合并data
307
+ config.columns[i] = []
308
+ const before = config.columns.slice(0, i)
309
+ let after = config.columns.slice(i + 1, config.columns.length)
310
+
311
+ const addArr = []
312
+
313
+ if (configFromWeb[targetName].cardMode === true) {
314
+ const temp = [{
315
+ type: 'card',
316
+ content: [],
317
+ label: configFromWeb[targetName].title,
318
+ }]
319
+ for (let k = 0; k < configFromWeb[targetName].columns.length; k++) {
320
+ configFromWeb[targetName].columns[k].forEach((cell) => {
321
+ temp[0].content.push(cell)
322
+ })
323
+ }
324
+ addArr.push(temp)
325
+ }
326
+ else {
327
+ for (let k = 0; k < configFromWeb[targetName].columns.length; k++) {
328
+ const temp = []
329
+ configFromWeb[targetName].columns[k].forEach((cell) => {
330
+ temp.push(cell)
331
+ })
332
+ addArr.push(temp)
333
+ }
334
+ }
335
+
336
+ const newArr = []
337
+ // 拼接之前的数组
338
+ if (before.length > 0) {
339
+ if (before.length >= 1) {
340
+ before.forEach((item) => {
341
+ newArr.push(item)
342
+ })
343
+ }
344
+ else {
345
+ newArr.push(before)
346
+ }
347
+ }
348
+
349
+ addArr.forEach((arr) => {
350
+ newArr.push(arr)
351
+ })
352
+
353
+ // 拼接之后的数组
354
+ if (after.length === 1) {
355
+ if (after[0].type === 'slot' || after[0][0].type === 'slot')
356
+ after = []
357
+ }
358
+ if (after.length > 0) {
359
+ if (after.length >= 1) {
360
+ after.forEach((item) => {
361
+ newArr.push(item)
362
+ })
363
+ }
364
+ else {
365
+ newArr.push(after)
366
+ }
367
+ }
368
+
369
+ config.columns = newArr
370
+ if (configFromWeb[targetName].slotsDeclare)
371
+ config.slotsDeclare = configFromWeb[targetName].slotsDeclare
372
+
373
+ else
374
+ config.slotsDeclare = []
375
+
376
+ if (config.data.images && configFromWeb[targetName].data.images) {
377
+ config.data.images = { ...config.data.images, ...configFromWeb[targetName].data.images }
378
+ delete configFromWeb[targetName].data.images
379
+ }
380
+ config.data = { ...config.data, ...configFromWeb[targetName].data }
381
+ }
382
+ }
383
+ }
384
+ Object.assign(activatedConfig, config)
385
+ }
386
+
387
+ // 获取并拼装插槽
388
+ function getConfigAndJoin(config: configDefine, outerLock: getConfigLock): void {
389
+ // 检查主配置插槽声明是否合法
390
+ const check = checkSlotDefine(config)
391
+ const waitForDownloadSlotName = []
392
+ if (check) {
393
+ // 扫描主配置中声明的插槽名
394
+ scanConfigName(config, waitForDownloadSlotName)
395
+
396
+ const total = waitForDownloadSlotName.length
397
+ let count = 0
398
+
399
+ // 挨个获取插槽
400
+ waitForDownloadSlotName.forEach((configName) => {
401
+ getConfigByNameWithoutIndexedDB(configName).then((res) => {
402
+ configFromWeb[configName] = res
403
+ count++
404
+ })
405
+ })
406
+
407
+ const timer = setInterval(() => {
408
+ if (count >= total) {
409
+ clearInterval(timer)
410
+ scanConfigSlot(config)
411
+ if (config.slotsDeclare.length > 0) {
412
+ const lock: getConfigLock = { status: true }
413
+ getConfigAndJoin(config, lock)
414
+ const innerTimer = setInterval(() => {
415
+ if (!lock.status) {
416
+ clearInterval(innerTimer)
417
+ outerLock.status = false
418
+ }
419
+ }, 100)
420
+ }
421
+ else {
422
+ outerLock.status = false
423
+ }
424
+ }
425
+ }, 100)
426
+ }
427
+ else {
428
+ outerLock.status = false
429
+ }
430
+ }
431
+
432
+ function deserializeFunctionAndRun(functionStr: string, value: any) {
433
+ try {
434
+ // eslint-disable-next-line no-new-func
435
+ const fun = new Function('value', 'activatedConfig', `return (${functionStr})(value, activatedConfig)`)
436
+ return fun(value, activatedConfig)
437
+ }
438
+ catch (err) {
439
+ console.error('Failed to deserialize and run function:', err)
440
+ return null
441
+ }
442
+ }
443
+
444
+ function deserializeFunctionAndRunWithConfig(functionStr: string) {
445
+ try {
446
+ // eslint-disable-next-line no-new-func
447
+ const fun = new Function('activatedConfig', `return (${functionStr})(activatedConfig)`)
448
+ return fun(activatedConfig)
449
+ }
450
+ catch (err) {
451
+ console.error('Failed to deserialize and run function with config:', err)
452
+ return null
453
+ }
454
+ }
455
+
456
+ // 将table配置的表格转换为适合Form展示的格式
457
+ function formatConfigToForm(config: configDefine): void {
458
+ const formColumns: Array<object> = []
459
+ const labels: Array<string> = []
460
+ for (let i = 0; i < config.columns.length; i++) {
461
+ let tempObj: {
462
+ customFunction?: any
463
+ inputColumns?: boolean
464
+ dataIndex: string
465
+ label: string
466
+ listType?: string
467
+ listLength?: number
468
+ valueText?: string
469
+ type: string | undefined
470
+ format?: string
471
+ definition?: Array<any>
472
+ editable?: boolean
473
+ inputReadOnly?: boolean
474
+ content?: Array<any>
475
+ text?: string
476
+ required?: boolean
477
+ requiredMessage?: string
478
+ } = {
479
+ label: '',
480
+ type: undefined,
481
+ dataIndex: '',
482
+ }
483
+
484
+ const row: Array<any> = config.columns[i]
485
+ let listArr = []
486
+ for (let j = 0; j < row.length; j++) {
487
+ switch (row[j].type) {
488
+ case 'column' :
489
+ if (row[j].customFunctionForDynamicDataIndex)
490
+ row[j].dataIndex = deserializeFunctionAndRunWithConfig(row[j].customFunctionForDynamicDataIndex)
491
+ if (j === row.length - 1) {
492
+ if (row[j].customFunction) {
493
+ tempObj.customFunction = row[j].customFunction
494
+ tempObj.label = deserializeFunctionAndRun(row[j].customFunction, row[j].dataIndex)
495
+ }
496
+ else {
497
+ labels.push(tempObj.label)
498
+ labels.push(row[j].text)
499
+ tempObj = {
500
+ label: '',
501
+ type: undefined,
502
+ dataIndex: '',
503
+ }
504
+ }
505
+ }
506
+ else if (tempObj.label && tempObj.dataIndex) {
507
+ formColumns.push(tempObj)
508
+ tempObj = {
509
+ label: '',
510
+ type: undefined,
511
+ dataIndex: '',
512
+ }
513
+ }
514
+ else if (tempObj.customFunction) {
515
+ formColumns.push(tempObj)
516
+ tempObj = {
517
+ label: '',
518
+ type: undefined,
519
+ dataIndex: '',
520
+ }
521
+ }
522
+ else if (tempObj.label && !tempObj.dataIndex) {
523
+ labels.push(tempObj.label)
524
+ tempObj = {
525
+ label: '',
526
+ type: undefined,
527
+ dataIndex: '',
528
+ }
529
+ }
530
+ if (row[j].customFunction) {
531
+ tempObj.customFunction = row[j].customFunction
532
+ tempObj.label = deserializeFunctionAndRun(row[j].customFunction, row[j].dataIndex)
533
+ }
534
+ else {
535
+ tempObj.label = row[j].text
536
+ }
537
+ break
538
+ case 'value' :
539
+ tempObj.valueText = row[j].value
540
+ tempObj.editable = false
541
+ tempObj.type = 'showValue'
542
+ break
543
+ case 'input' :
544
+ if (row[j].customFunctionForDynamicDataIndex)
545
+ row[j].dataIndex = deserializeFunctionAndRunWithConfig(row[j].customFunctionForDynamicDataIndex)
546
+ tempObj.dataIndex = row[j].dataIndex
547
+ tempObj.type = 'input'
548
+ if (row[j].inputReadOnly)
549
+ tempObj.inputReadOnly = true
550
+ tempObj.required = row[j].required
551
+ break
552
+ case 'curDateInput' :
553
+ tempObj.dataIndex = row[j].dataIndex
554
+ tempObj.text = row[j].text
555
+ tempObj.type = 'curDateInput'
556
+ tempObj.required = row[j].required
557
+ break
558
+ case 'datePicker' :
559
+ tempObj.dataIndex = row[j].dataIndex
560
+ tempObj.text = row[j].text
561
+ tempObj.type = 'datePicker'
562
+ tempObj.required = row[j].required
563
+ break
564
+ case 'timePicker' :
565
+ tempObj.dataIndex = row[j].dataIndex
566
+ tempObj.text = row[j].text
567
+ tempObj.type = 'timePicker'
568
+ tempObj.required = row[j].required
569
+ break
570
+ case 'dateTimeSecondsPicker' :
571
+ tempObj.dataIndex = row[j].dataIndex
572
+ tempObj.text = row[j].text
573
+ tempObj.type = 'dateTimeSecondsPicker'
574
+ tempObj.required = row[j].required
575
+ break
576
+ case 'signature' :
577
+ tempObj.dataIndex = row[j].dataIndex
578
+ tempObj.text = row[j].text
579
+ tempObj.type = 'signature'
580
+ tempObj.required = row[j].required
581
+ break
582
+ case 'inputs' :
583
+ if (!tempObj.format)
584
+ tempObj.format = ''
585
+ if (!tempObj.dataIndex)
586
+ tempObj.dataIndex = row[j].dataIndex
587
+ tempObj.format += row[j].format
588
+ tempObj.type = 'inputs'
589
+ if (row[j].inputReadOnly)
590
+ tempObj.inputReadOnly = true
591
+ break
592
+ case 'images' :
593
+ tempObj.dataIndex = row[j].dataIndex
594
+ tempObj.type = 'images'
595
+ break
596
+ case 'inputColumns' :
597
+ tempObj.label = ''
598
+ tempObj.dataIndex = row[j].dataIndex
599
+ tempObj.definition = row[j].definition
600
+ if (labels.length > 0) {
601
+ for (let k = 0; k < tempObj.definition.length; k++) {
602
+ if (labels[k].length > 1) {
603
+ tempObj.definition[k].label = labels[k]
604
+ if (props.showInputColumnsLabelOnTitle) { // 动态行label展示在标题
605
+ if (k === tempObj.definition.length - 1)
606
+ tempObj.label = `${tempObj.label} ${labels[k].replace(/\s*/g, '')}`
607
+ else
608
+ tempObj.label = `${tempObj.label} ${labels[k].replace(/\s*/g, '')} :`
609
+ }
610
+ else { // 动态行仅展示为’动态行‘
611
+ tempObj.label = '动态行'
612
+ }
613
+ }
614
+ else {
615
+ tempObj.definition[k].label = ''
616
+ }
617
+ }
618
+ }
619
+ tempObj.type = 'inputColumns'
620
+ break
621
+ case 'list' :
622
+ tempObj.valueText = row[j].listHead
623
+ tempObj.type = 'list'
624
+ tempObj.listType = row[j].listType
625
+ tempObj.dataIndex = row[j].dataIndex
626
+ if (row[j].content)
627
+ tempObj.content = row[j].content
628
+ tempObj.listLength = row[j].listLength
629
+ listArr.push(tempObj)
630
+ tempObj = {
631
+ label: '',
632
+ type: undefined,
633
+ dataIndex: '',
634
+ }
635
+ }
636
+ }
637
+ if (listArr.length > 0)
638
+ formColumns.push({ type: 'list', content: listArr })
639
+ else if (tempObj.type || tempObj.label)
640
+ formColumns.push(tempObj)
641
+ listArr = []
642
+ tempObj = {
643
+ label: '',
644
+ type: undefined,
645
+ dataIndex: '',
646
+ }
647
+ }
648
+ activatedConfig.columns = formColumns
649
+ }
650
+
651
+ // 获取子组件更新的file列表
652
+ function updateFile(files, index) {
653
+ files.forEach((file) => {
654
+ if (file.content)
655
+ delete file.content
656
+ if (file.file)
657
+ delete file.file
658
+ if (file.objectUrl)
659
+ delete file.objectUrl
660
+ })
661
+ if (index.includes('@@@'))
662
+ changeDeepObjectDeepCopy(activatedConfig.data.images, index, files)
663
+ else
664
+ activatedConfig.data.images[index] = files
665
+ }
666
+
667
+ // function handleInputDeepChange(event, dataIndex) {
668
+ // this.$forceUpdate()
669
+ // }
670
+ function getDeepObject(obj, strPath) {
671
+ const arr = strPath.split('@@@')
672
+ let result = obj[arr[0]]
673
+ arr.shift()
674
+ try {
675
+ while (arr.length > 0) {
676
+ result = result[arr[0]]
677
+ arr.shift()
678
+ }
679
+ }
680
+ catch {
681
+ result = undefined
682
+ }
683
+ return result
684
+ }
685
+
686
+ function changeDeepObject(obj, strPath, newVal) {
687
+ const arr = strPath.split('@@@')
688
+ if (arr.length === 1) {
689
+ obj[arr[0]] = newVal
690
+ }
691
+ else {
692
+ let result = obj[arr[0]]
693
+ arr.shift()
694
+ while (arr.length > 1) {
695
+ result = result[arr[0]]
696
+ arr.shift()
697
+ }
698
+ result[arr[0]] = newVal
699
+ }
700
+ }
701
+
702
+ function changeDeepObjectDeepCopy(obj, strPath, newVal) {
703
+ const arr = strPath.split('@@@')
704
+ if (arr.length === 1) {
705
+ obj[arr[0]] = newVal
706
+ }
707
+ else {
708
+ let result = obj[arr[0]]
709
+ arr.shift()
710
+ while (arr.length > 1) {
711
+ result = result[arr[0]]
712
+ arr.shift()
713
+ }
714
+ Object.assign(result[arr[0]], newVal)
715
+ }
716
+ }
717
+
718
+ // ------------------------- 初始化 -------------------------
719
+ initComponent()
720
+
721
+ function initComponent() {
722
+ if (props.localConfig !== undefined && Object.keys(props.localConfig).length > 0) {
723
+ Object.assign(activatedConfig, props.localConfig)
724
+
725
+ if (activatedConfig.designMode === 'json') {
726
+ activatedConfig.data = {}
727
+ Object.assign(activatedConfig.data, props.configData)
728
+ nextTick(() => {
729
+ scanFinish.value = true
730
+ })
731
+ return
732
+ }
733
+ else {
734
+ const lock = { status: true }
735
+ if (activatedConfig.slotsDeclare) {
736
+ getConfigAndJoin(activatedConfig, lock)
737
+ timer = setInterval(() => {
738
+ if (!lock.status) {
739
+ clearInterval(timer)
740
+ // 将配置文件格式更改为更利于Form的组织形式
741
+ formatConfigToForm(activatedConfig)
742
+ nextTick(() => {
743
+ scanFinish.value = true
744
+ })
745
+ }
746
+ }, 100)
747
+ }
748
+ else {
749
+ formatConfigToForm(activatedConfig)
750
+ nextTick(() => {
751
+ scanFinish.value = true
752
+ })
753
+ }
754
+ if (props.configData !== undefined)
755
+ Object.assign(activatedConfig.data, props.configData)
756
+
757
+ activatedConfig.columns.forEach((row) => {
758
+ if (row.dataIndex && row.dataIndex.includes('@@@')) {
759
+ if (!activatedConfig.tempData) {
760
+ activatedConfig.tempData = {}
761
+ }
762
+ if (row.type === 'images') {
763
+ activatedConfig.tempData[row.dataIndex] = getDeepObject(activatedConfig.data.images, row.dataIndex)
764
+ }
765
+ else {
766
+ activatedConfig.tempData[row.dataIndex] = getDeepObject(activatedConfig.data, row.dataIndex)
767
+ }
768
+ }
769
+ // 为了兼容旧的结构,保留原有的逻辑
770
+ if (row.forEach) {
771
+ row.forEach((item) => {
772
+ if (item.dataIndex && item.dataIndex.includes('@@@')) {
773
+ if (!activatedConfig.tempData) {
774
+ activatedConfig.tempData = {}
775
+ }
776
+ if (item.type === 'images') {
777
+ activatedConfig.tempData[item.dataIndex] = getDeepObject(activatedConfig.data.images, item.dataIndex)
778
+ }
779
+ else {
780
+ activatedConfig.tempData[item.dataIndex] = getDeepObject(activatedConfig.data, item.dataIndex)
781
+ }
782
+ }
783
+ })
784
+ }
785
+ })
786
+ console.warn('初始化完成,配置:', activatedConfig)
787
+ }
788
+ }
789
+
790
+ if (props.slotName)
791
+ activatedConfigName.value = props.slotName
792
+
793
+ else
794
+ activatedConfigName.value = props.configName
795
+
796
+ getConfigByNameWithoutIndexedDB(activatedConfigName.value).then((result) => {
797
+ Object.assign(activatedConfig, result)
798
+ if (activatedConfig.designMode === 'json') {
799
+ if (props.configData === undefined) {
800
+ console.error('无法找到json配置的数据')
801
+ }
802
+ else {
803
+ activatedConfig.data = {}
804
+ Object.assign(activatedConfig.data, props.configData)
805
+ nextTick(() => {
806
+ scanFinish.value = true
807
+ })
808
+ }
809
+ }
810
+ else {
811
+ const lock = { status: true }
812
+ if (activatedConfig.slotsDeclare) {
813
+ getConfigAndJoin(activatedConfig, lock)
814
+ timer = setInterval(() => {
815
+ if (!lock.status) {
816
+ clearInterval(timer)
817
+ // 将配置文件格式更改为更利于Form的组织形式
818
+ formatConfigToForm(activatedConfig)
819
+ nextTick(() => {
820
+ scanFinish.value = true
821
+ })
822
+ }
823
+ }, 100)
824
+ }
825
+ else {
826
+ formatConfigToForm(activatedConfig)
827
+ nextTick(() => {
828
+ scanFinish.value = true
829
+ })
830
+ }
831
+ if (props.configData !== undefined)
832
+ Object.assign(activatedConfig.data, props.configData)
833
+ }
834
+
835
+ activatedConfig.columns.forEach((row) => {
836
+ row.forEach((item) => {
837
+ if (item.dataIndex && item.dataIndex.includes('@@@')) {
838
+ if (!activatedConfig.tempData) {
839
+ activatedConfig.tempData = {}
840
+ activatedConfig.tempData[item.dataIndex] = getDeepObject(activatedConfig.data, item.dataIndex)
841
+ if (item.type === 'images')
842
+ activatedConfig.tempData[item.dataIndex] = getDeepObject(activatedConfig.data.images, item.dataIndex)
843
+ }
844
+ else {
845
+ activatedConfig.tempData[item.dataIndex] = getDeepObject(activatedConfig.data, item.dataIndex)
846
+ if (item.type === 'images')
847
+ activatedConfig.tempData[item.dataIndex] = getDeepObject(activatedConfig.data.images, item.dataIndex)
848
+ }
849
+ }
850
+ })
851
+ })
852
+ }).catch((e) => {
853
+ console.error(e)
854
+ })
855
+ }
856
+ function getNow() {
857
+ const date = new Date()
858
+ const year = date.getFullYear()
859
+ const month = String(date.getMonth() + 1).padStart(2, '0')
860
+ const day = String(date.getDate()).padStart(2, '0')
861
+ const hours = String(date.getHours()).padStart(2, '0')
862
+ const minutes = String(date.getMinutes()).padStart(2, '0')
863
+ const seconds = String(date.getSeconds()).padStart(2, '0')
864
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
865
+ }
866
+
867
+ // 移除了旧的日期时间选择器相关方法,现在使用 DateTimeSecondsPicker 组件
868
+ </script>
869
+
870
+ <template>
871
+ <!-- 标题栏 -->
872
+ <van-nav-bar
873
+ v-if="showNav"
874
+ title="动态报表表单"
875
+ left-text="返回"
876
+ left-arrow
877
+ @click-left="onClickLeft"
878
+ />
879
+ <div v-if="scanFinish" class="main">
880
+ <template v-if="activatedConfig.designMode === 'json'">
881
+ <XReportFormJsonRender :json-config="activatedConfig" />
882
+ </template>
883
+ <template v-else>
884
+ <!-- 标题 -->
885
+ <h2 v-if="activatedConfig.title" class="title" v-html="activatedConfig.title" />
886
+ <!-- 副标题 -->
887
+ <!-- <div v-if="activatedConfig.subTitle && activatedConfig.subTitle.length > 0" class="form_item"> -->
888
+ <!-- <van-collapse v-model="activeCollapseName" accordion> -->
889
+ <!-- <van-collapse-item title="副标题" name="副标题"> -->
890
+ <!-- <van-form> -->
891
+ <!-- <van-cell-group inset> -->
892
+ <!-- <template v-for="(subCell, cellIndex) in activatedConfig.subTitle" :key="cellIndex"> -->
893
+ <!-- <template v-if="subCell.type === 'inputs'"> -->
894
+ <!-- <template v-for="(item, itemIndex) in formatInputs(subCell)" :key="itemIndex"> -->
895
+ <!-- <van-field -->
896
+ <!-- v-model="activatedConfig.data[subCell.dataIndex][itemIndex]" -->
897
+ <!-- :label="`${item}:`" -->
898
+ <!-- clearable -->
899
+ <!-- type="textarea" -->
900
+ <!-- rows="1" -->
901
+ <!-- autosize -->
902
+ <!-- :placeholder="`请输入${item}`" -->
903
+ <!-- /> -->
904
+ <!-- </template> -->
905
+ <!-- </template> -->
906
+ <!-- <template v-else> -->
907
+ <!-- <van-field -->
908
+ <!-- v-model="activatedConfig.data[subCell.dataIndex]" -->
909
+ <!-- :label="`${subCell.format}:`" -->
910
+ <!-- clearable -->
911
+ <!-- type="textarea" -->
912
+ <!-- rows="1" -->
913
+ <!-- autosize -->
914
+ <!-- :placeholder="`请输入${subCell.format}`" -->
915
+ <!-- /> -->
916
+ <!-- </template> -->
917
+ <!-- </template> -->
918
+ <!-- </van-cell-group> -->
919
+ <!-- </van-form> -->
920
+ <!-- </van-collapse-item> -->
921
+ <!-- </van-collapse> -->
922
+ <!-- </div> -->
923
+ <!-- 表单项 -->
924
+ <div class="form_item">
925
+ <van-collapse v-model="activeCollapseName" accordion>
926
+ <van-collapse-item title="操作卡内容" name="操作卡内容">
927
+ <van-form>
928
+ <template v-for="(row, index) in activatedConfig.columns" :key="index">
929
+ <!-- value属性 -->
930
+ <van-cell-group v-if="row.type === 'showValue'" inset class="cell_group">
931
+ <van-cell center :title="row.label" :value="row.valueText" />
932
+ </van-cell-group>
933
+ <!-- input -->
934
+ <van-cell-group v-else-if="row.type === 'input'" inset class="cell_group" :title="row.label" :class="{ 'required-field': row.required }">
935
+ <template v-if="row.inputReadOnly === true">
936
+ <template v-if="row.dataIndex.includes('@@@')">
937
+ <van-field
938
+ v-model="activatedConfig.tempData[row.dataIndex]"
939
+ :label="`${row.label}:`"
940
+ :placeholder="`请输入${row.label}`"
941
+ clearable
942
+ type="textarea"
943
+ rows="1"
944
+ :disabled="true"
945
+ autosize
946
+ />
947
+ </template>
948
+ <template v-else>
949
+ <van-field
950
+ v-model="activatedConfig.data[row.dataIndex]"
951
+ :label="`${row.label}:`"
952
+ :placeholder="`请输入${row.label}`"
953
+ clearable
954
+ type="textarea"
955
+ rows="1"
956
+ :disabled="true"
957
+ autosize
958
+ />
959
+ </template>
960
+ </template>
961
+ <template v-else>
962
+ <template v-if="row.dataIndex.includes('@@@')">
963
+ <van-field
964
+ v-model="activatedConfig.tempData[row.dataIndex]"
965
+ :label="`${row.label}:`"
966
+ :placeholder="`请输入${row.label}`"
967
+ clearable
968
+ type="textarea"
969
+ rows="1"
970
+ autosize
971
+ />
972
+ </template>
973
+ <template v-else>
974
+ <van-field
975
+ v-model="activatedConfig.data[row.dataIndex]"
976
+ :label="`${row.label}:`"
977
+ :placeholder="`请输入${row.label}`"
978
+ clearable
979
+ type="textarea"
980
+ rows="1"
981
+ autosize
982
+ />
983
+ </template>
984
+ </template>
985
+ </van-cell-group>
986
+ <!-- curDateInput -->
987
+ <van-cell-group v-else-if="row.type === 'curDateInput'" inset style="margin-top: 4vh">
988
+ <div class="cur-date-input-container">
989
+ <div class="cur-date-input-content">
990
+ <div class="cur-date-input-label">
991
+ {{ row.valueText || '当前时间' }}
992
+ </div>
993
+ <div class="cur-date-input-value">
994
+ {{ activatedConfig.data[row.dataIndex] || '未操作' }}
995
+ </div>
996
+ </div>
997
+ <div class="cur-date-input-action">
998
+ <van-button
999
+ v-if="!activatedConfig.data[row.dataIndex]"
1000
+ size="small"
1001
+ type="primary"
1002
+ @click="activatedConfig.data[row.dataIndex] = getNow()"
1003
+ >
1004
+ {{ row.text || '设置时间' }}
1005
+ </van-button>
1006
+ <van-button
1007
+ v-else
1008
+ size="small"
1009
+ type="default"
1010
+ @click="activatedConfig.data[row.dataIndex] = getNow()"
1011
+ >
1012
+ {{ row.text || '重新设置' }}
1013
+ </van-button>
1014
+ </div>
1015
+ </div>
1016
+ </van-cell-group>
1017
+ <!-- signature -->
1018
+ <van-cell-group v-else-if="row.type === 'signature'" inset style="margin-top: 4vh">
1019
+ <van-field
1020
+ :label="row.valueText ? `${row.valueText}:` : ''"
1021
+
1022
+ rows="1"
1023
+ autosize readonly
1024
+ >
1025
+ <template #button>
1026
+ <XSignature v-model="activatedConfig.data[row.dataIndex]" />
1027
+ </template>
1028
+ </van-field>
1029
+ </van-cell-group>
1030
+ <!-- datePicker -->
1031
+ <van-cell-group v-else-if="row.type === 'datePicker'" inset :title="row.label" :class="{ 'required-field': row.required }">
1032
+ <DateTimeSecondsPicker
1033
+ v-model="activatedConfig.data[row.dataIndex]"
1034
+ :label="`${row.label || '日期'}:`"
1035
+ placeholder="请选择日期"
1036
+ title="选择日期"
1037
+ format="YYYY-MM-DD"
1038
+ />
1039
+ </van-cell-group>
1040
+ <!-- timePicker -->
1041
+ <van-cell-group v-else-if="row.type === 'timePicker'" inset :title="row.label" :class="{ 'required-field': row.required }">
1042
+ <DateTimeSecondsPicker
1043
+ v-model="activatedConfig.data[row.dataIndex]"
1044
+ :label="`${row.label || '时间'}:`"
1045
+ placeholder="请选择时间"
1046
+ title="选择时间"
1047
+ format="YYYY-MM-DD HH:mm:ss"
1048
+ />
1049
+ </van-cell-group>
1050
+ <!-- dateTimeSecondsPicker -->
1051
+ <van-cell-group v-else-if="row.type === 'dateTimeSecondsPicker'" inset :title="row.label" :class="{ 'required-field': row.required }">
1052
+ <DateTimeSecondsPicker
1053
+ v-model="activatedConfig.data[row.dataIndex]"
1054
+ :label="`${row.label || '完整时间'}:`"
1055
+ placeholder="请选择完整时间"
1056
+ title="选择完整时间"
1057
+ format="YYYY-MM-DD HH:mm:ss"
1058
+ />
1059
+ </van-cell-group>
1060
+ <!-- inputs -->
1061
+ <van-cell-group v-else-if="row.type === 'inputs'" inset :title="row.label || ''" :class="{ 'required-field': row.required }">
1062
+ <template v-if="row.inputReadOnly === true">
1063
+ <template v-for="(item, _index) in formatInputs(row)" :key="_index">
1064
+ <van-field
1065
+ v-model="activatedConfig.data[row.dataIndex][_index]"
1066
+ :label="`${item}:`"
1067
+ :placeholder="`请输入${item}`"
1068
+ clearable
1069
+ type="textarea"
1070
+ rows="1"
1071
+ :disabled="true"
1072
+ autosize
1073
+ />
1074
+ </template>
1075
+ </template>
1076
+ <template v-else>
1077
+ <template v-for="(item, _index) in formatInputs(row)" :key="_index">
1078
+ <van-field
1079
+ v-model="activatedConfig.data[row.dataIndex][_index]"
1080
+ :label="`${item}:`"
1081
+ :placeholder="`请输入${item}`"
1082
+ clearable
1083
+ type="textarea"
1084
+ rows="1"
1085
+ autosize
1086
+ />
1087
+ </template>
1088
+ </template>
1089
+ </van-cell-group>
1090
+ <!-- images -->
1091
+ <van-cell-group v-else-if="row.type === 'images'" inset :title="row.label">
1092
+ <template v-if="row.dataIndex.includes('@@@')">
1093
+ <Uploader
1094
+ upload-mode="oss"
1095
+ :image-list="activatedConfig.tempData[row.dataIndex]"
1096
+ :outer-index="row.dataIndex"
1097
+ authority="admin"
1098
+ @update-file-list="updateFile"
1099
+ />
1100
+ </template>
1101
+ <template v-else>
1102
+ <Uploader
1103
+ upload-mode="oss"
1104
+ :image-list="activatedConfig.data.images[row.dataIndex]"
1105
+ :outer-index="row.dataIndex"
1106
+ authority="admin"
1107
+ @update-file-list="updateFile"
1108
+ />
1109
+ </template>
1110
+ </van-cell-group>
1111
+ <!-- 动态行 -->
1112
+ <template v-else-if="row.type === 'inputColumns'">
1113
+ <!-- <van-divider class="divider"> -->
1114
+ <!-- {{ row.label }} -->
1115
+ <!-- </van-divider> -->
1116
+ <van-cell-group inset :title="row.label" style="background-color: #eff2f5">
1117
+ <van-cell-group v-for="(_item, _index) in activatedConfig.data[row.dataIndex]" :key="_index" inset class="my-cell-group" style="margin: 0 0 10px 0">
1118
+ <div v-for="(def, defIndex) in row.definition" :key="defIndex">
1119
+ <template v-if="def.type === 'curDateInput'">
1120
+ <div class="cur-date-input-container">
1121
+ <div class="cur-date-input-content">
1122
+ <div class="cur-date-input-label">
1123
+ {{ def.label }}
1124
+ </div>
1125
+ <div class="cur-date-input-value">
1126
+ {{ activatedConfig.data[row.dataIndex][_index][def.dataIndex] || '未设置' }}
1127
+ </div>
1128
+ </div>
1129
+ <div class="cur-date-input-action">
1130
+ <van-button
1131
+ v-if="!activatedConfig.data[row.dataIndex][_index][def.dataIndex]"
1132
+ size="small"
1133
+ type="primary"
1134
+ @click="activatedConfig.data[row.dataIndex][_index][def.dataIndex] = getNow()"
1135
+ >
1136
+ {{ def.text || '设置时间' }}
1137
+ </van-button>
1138
+ <van-button
1139
+ v-else
1140
+ size="small"
1141
+ type="default"
1142
+ @click="activatedConfig.data[row.dataIndex][_index][def.dataIndex] = getNow()"
1143
+ >
1144
+ {{ def.text || '重新设置' }}
1145
+ </van-button>
1146
+ </div>
1147
+ </div>
1148
+ </template>
1149
+ <van-field
1150
+ v-else
1151
+ v-model="activatedConfig.data[row.dataIndex][_index][def.dataIndex]"
1152
+ :label="`${def.label}:`"
1153
+ type="textarea"
1154
+ rows="1"
1155
+ autosize
1156
+ :placeholder="`请输入${def.label}`"
1157
+ clearable
1158
+ :class="{ 'required-field': def.required }"
1159
+ />
1160
+ </div>
1161
+ </van-cell-group>
1162
+ </van-cell-group>
1163
+
1164
+ <!-- <div class="button_group"> -->
1165
+ <!-- <van-button class="input_columns_button" plain type="primary" @click="addInputColumns(row.dataIndex, row.definition)"> -->
1166
+ <!-- <van-icon name="plus" /> -->
1167
+ <!-- </van-button> -->
1168
+ <!-- <van-button class="input_columns_button" plain type="primary" @click="removeInputColumns(row.dataIndex)"> -->
1169
+ <!-- <van-icon name="minus" /> -->
1170
+ <!-- </van-button> -->
1171
+ <!-- </div> -->
1172
+ </template>
1173
+ <!-- list -->
1174
+ <template v-else-if="row.type === 'list'">
1175
+ <template v-for="(listIndex) in row.content[0].listLength" :key="listIndex">
1176
+ <div style="margin-top: 3vh; margin-bottom: 3vh">
1177
+ <van-cell-group inset :title="row.label" style="background-color: #eff2f5">
1178
+ <template v-for="(_cell, cellIndex) in row.content" :key="listIndex + cellIndex">
1179
+ <template v-if="_cell.listType === 'input'">
1180
+ <van-field
1181
+ v-model="activatedConfig.data[_cell.dataIndex][listIndex]"
1182
+ :label="_cell.valueText"
1183
+ type="textarea"
1184
+ autosize
1185
+ />
1186
+ </template>
1187
+ <template v-else-if="_cell.listType === 'value'">
1188
+ <van-field
1189
+ :disabled="true"
1190
+ :label="_cell.valueText"
1191
+ type="textarea"
1192
+ :placeholder="_cell.content[listIndex - 1]"
1193
+ autosize
1194
+ />
1195
+ </template>
1196
+ </template>
1197
+ </van-cell-group>
1198
+ </div>
1199
+ </template>
1200
+ </template>
1201
+ </template>
1202
+ </van-form>
1203
+ </van-collapse-item>
1204
+ </van-collapse>
1205
+ </div>
1206
+ <!-- 提交按钮 -->
1207
+ <div class="submit_button">
1208
+ <van-button round block type="primary" native-type="submit" @click="onSubmit">
1209
+ 提交
1210
+ </van-button>
1211
+ </div>
1212
+ </template>
1213
+ </div>
1214
+ <!-- 骨架屏 -->
1215
+ <div v-else class="skeleton">
1216
+ <van-skeleton title :row="5" />
1217
+ </div>
1218
+
1219
+ <!-- 已移除 VueHashCalendar,现在使用内置的 DateTimeSecondsPicker 组件 -->
1220
+ </template>
1221
+
1222
+ <style scoped lang="less">
1223
+ .main {
1224
+ padding-top: 4vh;
1225
+ width: 100vw;
1226
+ height: 100vh;
1227
+ background-color: #eff2f5;
1228
+
1229
+ .title {
1230
+ padding-bottom: 2vh;
1231
+ color: rgb(50, 50, 51);
1232
+ text-align: center;
1233
+ margin: 0 0 3vh;
1234
+ }
1235
+
1236
+ .text_box {
1237
+ margin-top: 2vh;
1238
+ margin-bottom: 2vh;
1239
+ }
1240
+
1241
+ .main_text {
1242
+ padding-left: 16px;
1243
+ font-weight: 400;
1244
+ line-height: 1.6;
1245
+ margin: 0 0 40px;
1246
+ color: #969799;
1247
+ font-size: 14px;
1248
+ }
1249
+
1250
+ .show_value_item {
1251
+ text-align: center;
1252
+ font-size: 1.2em;
1253
+ }
1254
+
1255
+ .cell_group {
1256
+ //margin-top: 2vh;
1257
+ //margin-bottom: 2vh;
1258
+ }
1259
+
1260
+ .form_item {
1261
+ margin-top: 2vh;
1262
+ }
1263
+
1264
+ .button_group {
1265
+ text-align: center;
1266
+ margin-top: 3vh;
1267
+ margin-bottom: 3vh;
1268
+ }
1269
+
1270
+ .button_group > :first-child {
1271
+ margin-right: 3vw;
1272
+ }
1273
+
1274
+ .divider {
1275
+ color: #1989fa;
1276
+ border-color: #1989fa;
1277
+ padding: 0 16px;
1278
+ }
1279
+
1280
+ .submit_button {
1281
+ background-color: #eff2f5;
1282
+ padding: 5vh;
1283
+ }
1284
+ }
1285
+
1286
+ .skeleton {
1287
+ margin-top: 5vh;
1288
+ }
1289
+ .my-cell-group {
1290
+ margin: 0 0 10px 0;
1291
+ }
1292
+ :deep(.van-collapse-item__content) {
1293
+ background-color: #eff2f5;
1294
+ padding: 10px 0;
1295
+ }
1296
+ :deep(.van-cell-group__title) {
1297
+ padding-top: 10px;
1298
+ padding-bottom: 10px;
1299
+ }
1300
+ :deep(.van-field__label) {
1301
+ font-weight: 600;
1302
+ }
1303
+
1304
+ // 必填字段星号样式 - 只对星号生效
1305
+ :deep(.van-field__label) {
1306
+ // 将包含星号的文本进行特殊处理
1307
+ &[style*='color'] {
1308
+ color: #323233 !important;
1309
+ }
1310
+ }
1311
+
1312
+ // 使用自定义类名来处理必填字段
1313
+ .required-field :deep(.van-field__label) {
1314
+ color: #323233;
1315
+
1316
+ &::before {
1317
+ content: '*';
1318
+ color: #ee0a24;
1319
+ margin-right: 2px;
1320
+ }
1321
+ }
1322
+ :deep(.van-uploader__wrapper) {
1323
+ padding: 10px;
1324
+ display: flex;
1325
+ flex-wrap: wrap;
1326
+ justify-content: space-between; /* 水平方向上左右分散对齐 */
1327
+ align-items: center; /* 垂直方向上上下中心对齐 */
1328
+ }
1329
+
1330
+ // 自定义的当前日期输入框样式
1331
+ .cur-date-input-container {
1332
+ padding: 16px;
1333
+ display: flex;
1334
+ align-items: flex-start;
1335
+ justify-content: space-between;
1336
+ background: white;
1337
+ border-radius: 8px;
1338
+
1339
+ .cur-date-input-content {
1340
+ flex: 1;
1341
+ margin-right: 12px;
1342
+ min-width: 0; // 防止flex子元素撑破容器
1343
+
1344
+ .cur-date-input-label {
1345
+ font-size: 14px;
1346
+ font-weight: 600;
1347
+ color: #323233;
1348
+ line-height: 1.4;
1349
+ margin-bottom: 4px;
1350
+ word-wrap: break-word;
1351
+ white-space: normal;
1352
+ display: -webkit-box;
1353
+ -webkit-line-clamp: 4; // 最多显示2行
1354
+ -webkit-box-orient: vertical;
1355
+ overflow: hidden;
1356
+ }
1357
+
1358
+ .cur-date-input-value {
1359
+ font-size: 15px;
1360
+ font-weight: 600;
1361
+ color: #666;
1362
+ line-height: 1.3;
1363
+ word-wrap: break-word;
1364
+ white-space: normal;
1365
+ }
1366
+ }
1367
+
1368
+ .cur-date-input-action {
1369
+ flex-shrink: 0;
1370
+ display: flex;
1371
+ align-items: center;
1372
+ }
1373
+ }
1374
+
1375
+ // 动态行中的当前日期输入框样式调整
1376
+ .my-cell-group .cur-date-input-container {
1377
+ padding: 12px;
1378
+ margin: 8px 0;
1379
+
1380
+ .cur-date-input-content {
1381
+ margin-right: 8px;
1382
+
1383
+ .cur-date-input-label {
1384
+ font-size: 13px;
1385
+ margin-bottom: 2px;
1386
+ }
1387
+
1388
+ .cur-date-input-value {
1389
+ font-size: 12px;
1390
+ }
1391
+ }
1392
+ }
1393
+ </style>