af-mobile-client-vue3 1.3.30 → 1.3.31

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 (283) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.cursorrules +60 -60
  3. package/.editorconfig +9 -9
  4. package/.env +10 -10
  5. package/.env.development +1 -1
  6. package/.env.production +1 -1
  7. package/.node-version +1 -1
  8. package/.vscode/extensions.json +12 -12
  9. package/.vscode/settings.json +68 -66
  10. package/CLAUDE.md +218 -189
  11. package/README.md +182 -182
  12. package/af-example-mobile-vue-web.iml +9 -9
  13. package/build/vite/index.ts +98 -98
  14. package/build/vite/optimize.ts +34 -34
  15. package/build/vite/vconsole.ts +47 -47
  16. package/commitlint.config.ts +32 -32
  17. package/compress.js +36 -36
  18. package/eslint.config.ts +31 -30
  19. package/index.html +23 -23
  20. package/mock/data.ts +20 -20
  21. package/mock/index.ts +7 -7
  22. package/mock/modules/prose.mock.ts +13 -13
  23. package/mock/modules/user.mock.ts +95 -152
  24. package/mock/util.ts +19 -19
  25. package/netlify.toml +12 -12
  26. package/package.json +114 -114
  27. package/postcss.config.ts +27 -27
  28. package/public/favicon.svg +4 -4
  29. package/public/safari-pinned-tab.svg +4 -4
  30. package/scripts/verifyCommit.js +19 -19
  31. package/src/App.vue +79 -79
  32. package/src/api/auth/index.ts +77 -0
  33. package/src/api/auth/types.ts +200 -0
  34. package/src/api/mock/index.ts +30 -30
  35. package/src/api/user/index.ts +40 -40
  36. package/src/assets/img/user/login/background-shadow-1.svg +20 -20
  37. package/src/assets/img/user/login/logo-background.svg +20 -20
  38. package/src/bootstrap.ts +26 -26
  39. package/src/components/core/BeautifulLoading/index.vue +52 -52
  40. package/src/components/core/ImageUploader/index.vue +251 -251
  41. package/src/components/core/NavBar/index.vue +53 -53
  42. package/src/components/core/Tabbar/index.vue +32 -32
  43. package/src/components/core/Uploader/index.vue +124 -124
  44. package/src/components/core/XGridDropOption/index.vue +154 -154
  45. package/src/components/core/XMultiSelect/index.vue +183 -183
  46. package/src/components/core/XSelect/index.vue +149 -149
  47. package/src/components/data/CardContainer/CardContainer.vue +118 -118
  48. package/src/components/data/CardContainer/CardHeader.vue +99 -99
  49. package/src/components/data/InfoDisplay/index.vue +132 -132
  50. package/src/components/data/UserDetail/api.ts +24 -24
  51. package/src/components/data/UserDetail/index.vue +620 -620
  52. package/src/components/data/UserDetail/recordEntries.ts +159 -159
  53. package/src/components/data/UserDetail/types.ts +26 -26
  54. package/src/components/data/XBadge/index.vue +82 -82
  55. package/src/components/data/XCellDetail/index.vue +105 -105
  56. package/src/components/data/XCellList/XCellList.md +432 -432
  57. package/src/components/data/XCellList/index.vue +1436 -1436
  58. package/src/components/data/XCellListFilter/QrScanner/index.vue +207 -207
  59. package/src/components/data/XCellListFilter/QrScanner/startScanAnimation.ts +53 -53
  60. package/src/components/data/XCellListFilter/VpnRecognition/index.vue +119 -119
  61. package/src/components/data/XCellListFilter/index.vue +705 -705
  62. package/src/components/data/XForm/index.vue +659 -659
  63. package/src/components/data/XFormGroup/doc/DeviceForm.vue +122 -122
  64. package/src/components/data/XFormGroup/doc/FormGroupDemo.vue +56 -56
  65. package/src/components/data/XFormGroup/doc/README.md +286 -286
  66. package/src/components/data/XFormGroup/doc/UserForm.vue +102 -102
  67. package/src/components/data/XFormGroup/index.vue +240 -240
  68. package/src/components/data/XFormItem/index.vue +1310 -1310
  69. package/src/components/data/XOlMap/README.md +227 -227
  70. package/src/components/data/XOlMap/XLocationPicker/index.vue +226 -226
  71. package/src/components/data/XOlMap/index.vue +1490 -1490
  72. package/src/components/data/XOlMap/types.ts +149 -149
  73. package/src/components/data/XOlMap/utils/{wgs84ToGcj02.js → wgs84ToGcj02.ts} +171 -154
  74. package/src/components/data/XReportForm/DateTimeSecondsPicker.vue +208 -208
  75. package/src/components/data/XReportForm/XReportFormJsonRender.vue +220 -220
  76. package/src/components/data/XReportForm/index.vue +1393 -1393
  77. package/src/components/data/XReportGrid/XAddReport/XAddReport.vue +198 -198
  78. package/src/components/data/XReportGrid/XAddReport/index.js +3 -3
  79. package/src/components/data/XReportGrid/XAddReport/index.md +53 -53
  80. package/src/components/data/XReportGrid/XAddReport/index.ts +10 -10
  81. package/src/components/data/XReportGrid/XReport.vue +960 -960
  82. package/src/components/data/XReportGrid/XReportDemo.vue +33 -33
  83. package/src/components/data/XReportGrid/XReportDesign.vue +597 -597
  84. package/src/components/data/XReportGrid/XReportDrawer/XReportDrawer.vue +148 -148
  85. package/src/components/data/XReportGrid/XReportDrawer/index.js +3 -3
  86. package/src/components/data/XReportGrid/XReportDrawer/index.ts +10 -10
  87. package/src/components/data/XReportGrid/XReportJsonRender.vue +399 -399
  88. package/src/components/data/XReportGrid/XReportTrGroup.vue +592 -592
  89. package/src/components/data/XReportGrid/index.md +46 -46
  90. package/src/components/data/XReportGrid/print.js +184 -184
  91. package/src/components/data/XSignature/index.vue +284 -284
  92. package/src/components/data/XTag/index.vue +10 -10
  93. package/src/components/layout/NormalDataLayout/index.vue +69 -69
  94. package/src/components/layout/TabBarLayout/index.vue +40 -40
  95. package/src/composables/dark.ts +5 -5
  96. package/src/config/routes.ts +9 -9
  97. package/src/constants/index.ts +2 -2
  98. package/src/enums/requestEnum.ts +25 -25
  99. package/src/expression/ExpressionRunner.ts +28 -28
  100. package/src/expression/TestExpression.ts +510 -510
  101. package/src/expression/core/Delegate.ts +116 -116
  102. package/src/expression/core/Expression.ts +1359 -1359
  103. package/src/expression/core/Program.ts +985 -985
  104. package/src/expression/core/Token.ts +29 -29
  105. package/src/expression/enums/ExpressionType.ts +81 -81
  106. package/src/expression/enums/TokenType.ts +11 -11
  107. package/src/expression/exception/BreakWayException.ts +2 -2
  108. package/src/expression/exception/ContinueWayException.ts +2 -2
  109. package/src/expression/exception/ExpressionException.ts +29 -29
  110. package/src/expression/exception/ReturnWayException.ts +14 -14
  111. package/src/expression/exception/ServiceException.ts +22 -22
  112. package/src/expression/instances/JSONArray.ts +52 -52
  113. package/src/expression/instances/JSONObject.ts +118 -118
  114. package/src/expression/instances/LogicConsole.ts +31 -31
  115. package/src/font-style/font.css +4 -4
  116. package/src/hooks/useBoolean.ts +26 -26
  117. package/src/hooks/useCommon.ts +9 -9
  118. package/src/hooks/useLoading.ts +16 -16
  119. package/src/hooks/useLogin.ts +97 -97
  120. package/src/icons/svg/check-in.svg +32 -32
  121. package/src/icons/svg/dark.svg +4 -4
  122. package/src/icons/svg/github.svg +4 -4
  123. package/src/icons/svg/light.svg +4 -4
  124. package/src/icons/svg/link.svg +4 -4
  125. package/src/icons/svgo.yml +22 -22
  126. package/src/layout/GridView/index.vue +16 -16
  127. package/src/layout/PageLayout.vue +9 -9
  128. package/src/layout/SingleLayout.vue +9 -9
  129. package/src/locales/en-US.json +128 -128
  130. package/src/locales/zh-CN.json +128 -128
  131. package/src/logic/LogicRunner.ts +67 -67
  132. package/src/logic/TestLogic.ts +13 -13
  133. package/src/logic/plugins/common/DateTools.ts +35 -35
  134. package/src/logic/plugins/common/VueTools.ts +30 -30
  135. package/src/logic/plugins/index.ts +7 -7
  136. package/src/main.ts +44 -44
  137. package/src/plugins/AppData.ts +38 -38
  138. package/src/plugins/GetLoginInfoService.ts +10 -10
  139. package/src/plugins/collectIcons.ts +10 -10
  140. package/src/plugins/index.ts +11 -11
  141. package/src/router/README.md +8 -8
  142. package/src/router/external-routes.ts +60 -0
  143. package/src/router/guards.ts +131 -59
  144. package/src/router/index.ts +35 -35
  145. package/src/router/invoiceRoutes.ts +33 -33
  146. package/src/router/routes.ts +426 -347
  147. package/src/services/api/Login.ts +6 -6
  148. package/src/services/api/common.ts +109 -109
  149. package/src/services/api/index.ts +7 -7
  150. package/src/services/api/manage.ts +8 -8
  151. package/src/services/api/search.ts +16 -16
  152. package/src/services/api/user.ts +17 -17
  153. package/src/services/restTools.ts +56 -56
  154. package/src/services/v3Api.ts +147 -147
  155. package/src/stores/index.ts +13 -13
  156. package/src/stores/modules/counter.ts +19 -19
  157. package/src/stores/modules/homeApp.ts +55 -55
  158. package/src/stores/modules/routeCache.ts +22 -23
  159. package/src/stores/modules/setting.ts +87 -87
  160. package/src/stores/modules/user.ts +326 -235
  161. package/src/stores/mutation-type.ts +12 -7
  162. package/src/styles/app.less +36 -36
  163. package/src/styles/login.less +109 -109
  164. package/src/styles/var.less +25 -25
  165. package/src/types/auth.ts +85 -0
  166. package/src/types/env.d.ts +16 -16
  167. package/src/types/platform.ts +194 -0
  168. package/src/types/settings.ts +1 -1
  169. package/src/types/vue-router.d.ts +13 -9
  170. package/src/utils/Storage.ts +124 -124
  171. package/src/utils/authority-utils.ts +84 -84
  172. package/src/utils/common.ts +41 -41
  173. package/src/utils/crypto.ts +39 -39
  174. package/src/utils/dataUtil.ts +42 -42
  175. package/src/utils/dictUtil.ts +52 -52
  176. package/src/utils/http/index.ts +201 -199
  177. package/src/utils/i18n.ts +72 -72
  178. package/src/utils/indexedDB.ts +195 -195
  179. package/src/utils/inline-px-to-vw.ts +28 -28
  180. package/src/utils/mobileUtil.ts +33 -34
  181. package/src/utils/platform-auth.ts +134 -0
  182. package/src/utils/progress.ts +19 -19
  183. package/src/utils/routerUtil.ts +271 -271
  184. package/src/utils/runEvalFunction.ts +13 -13
  185. package/src/utils/secureStorage.ts +70 -71
  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 -0
  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 -129
  198. package/src/views/component/XFormAppraiseView/index.vue +174 -174
  199. package/src/views/component/XFormGroupView/index.vue +78 -82
  200. package/src/views/component/XFormView/index.vue +27 -27
  201. package/src/views/component/XOlMapView/XLocationPicker/index.vue +118 -118
  202. package/src/views/component/XOlMapView/index.vue +434 -434
  203. package/src/views/component/XOlMapView/testData.ts +64 -64
  204. package/src/views/component/XReportFormIframeView/index.vue +47 -47
  205. package/src/views/component/XReportFormView/index.vue +13 -13
  206. package/src/views/component/XReportGridView/index.vue +17 -17
  207. package/src/views/component/XRequestView/index.vue +234 -234
  208. package/src/views/component/XSignatureView/index.vue +50 -50
  209. package/src/views/component/index.vue +181 -181
  210. package/src/views/component/menu.vue +117 -117
  211. package/src/views/component/notice.vue +46 -46
  212. package/src/views/component/topNav.vue +36 -36
  213. package/src/views/external/index.vue +152 -0
  214. package/src/views/invoiceShow/index.vue +61 -61
  215. package/src/views/loading/AuthLoading.vue +345 -0
  216. package/src/views/user/login/ForgetPasswordForm.vue +94 -94
  217. package/src/views/user/login/LoginForm.vue +350 -347
  218. package/src/views/user/login/LoginTitle.vue +76 -76
  219. package/src/views/user/login/LoginWave.vue +109 -109
  220. package/src/views/user/login/index.vue +22 -22
  221. package/src/views/user/my/comm/ModifyPassword.vue +346 -346
  222. package/src/views/user/my/index.vue +507 -507
  223. package/src/views/user/register/index.vue +952 -952
  224. package/src/views/userRecords/AbnormalAlarmRecords.vue +21 -21
  225. package/src/views/userRecords/CardReplacementRecords.vue +21 -21
  226. package/src/views/userRecords/ChangeRecords.vue +19 -19
  227. package/src/views/userRecords/CommandViewRecords.vue +20 -20
  228. package/src/views/userRecords/GasCompensationRecords.vue +20 -20
  229. package/src/views/userRecords/InstrumentCollectionRecords.vue +21 -21
  230. package/src/views/userRecords/MeterRecords.vue +20 -20
  231. package/src/views/userRecords/OperateRecords.vue +51 -51
  232. package/src/views/userRecords/OtherChargeRecords.vue +19 -19
  233. package/src/views/userRecords/PaymentRecords.vue +28 -28
  234. package/src/views/userRecords/PriceAdjustmentRecords.vue +19 -19
  235. package/src/views/userRecords/ReplacementRecords.vue +19 -19
  236. package/src/views/userRecords/SafetyRecords.vue +19 -19
  237. package/src/views/userRecords/TransactionRecords.vue +21 -21
  238. package/src/views/userRecords/TransferRecords.vue +19 -19
  239. package/src/views/userRecords/operateRecordDetail/index.vue +316 -316
  240. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AddUserDetail.vue +124 -124
  241. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AdvanceDeliveryDetail.vue +88 -88
  242. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AutoAccountsCancelDetail.vue +205 -205
  243. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/AutoAccountsDetail.vue +192 -192
  244. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BankDkDetail.vue +192 -192
  245. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BankPayDetail.vue +192 -192
  246. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/BlacklistDetail.vue +153 -153
  247. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CancellationDetail.vue +101 -101
  248. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardMeterCenterCancelDetail.vue +127 -127
  249. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardMeterCenterDetail.vue +153 -153
  250. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/CardOverUserDetail.vue +153 -153
  251. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ChangeMeterCancelDetail.vue +166 -166
  252. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ChangeMeterDetail.vue +205 -205
  253. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/DisableManageDetail.vue +127 -127
  254. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/EnableManageDetail.vue +114 -114
  255. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/FaZheChangeDetail.vue +124 -124
  256. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/FeeDeductionDetail.vue +153 -153
  257. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/GasPriceChangeDetail.vue +126 -126
  258. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/InputtorChangeDetail.vue +126 -126
  259. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotMeterCenterCancelDetail.vue +114 -114
  260. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotMeterCenterDetail.vue +127 -127
  261. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/IotOpenDetail.vue +88 -88
  262. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineCardDetail.vue +101 -101
  263. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineMeterCenterCancelDetail.vue +218 -218
  264. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/MachineMeterCenterDetail.vue +153 -153
  265. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OffGasAddGasDetail.vue +140 -140
  266. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OtherChargeCancelDetail.vue +127 -127
  267. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OtherChargeDetail.vue +114 -114
  268. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/OverUserChangeDetail.vue +127 -127
  269. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReBillDetail.vue +127 -127
  270. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/RefundDetail.vue +114 -114
  271. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReplaceCardManageCancelDetail.vue +127 -127
  272. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/ReplaceCardManageDetail.vue +114 -114
  273. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/SaleCardGasDetail.vue +140 -140
  274. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/TransferManageCancelDetail.vue +152 -152
  275. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/TransferManageDetail.vue +178 -178
  276. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/UserChangeDetail.vue +123 -123
  277. package/src/views/userRecords/operateRecordDetail/operateRecordDetails/WechatPayDetail.vue +192 -192
  278. package/src/views/userRecords/types.ts +66 -66
  279. package/tsconfig.json +39 -39
  280. package/uno.config.ts +82 -82
  281. package/vite.config.ts +119 -118
  282. package/src/router/types.ts +0 -7
  283. package/src/utils/wechatUtil.ts +0 -9
@@ -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>