zhytech-ui-mobile 1.0.0 → 1.0.2

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 (84) hide show
  1. package/.eslintignore +11 -0
  2. package/.eslintrc.cjs +253 -0
  3. package/.prettierignore +0 -0
  4. package/.prettierrc.json +9 -0
  5. package/.vscode/settings.json +131 -0
  6. package/index.html +39 -0
  7. package/package.json +4 -26
  8. package/shims-uni.d.ts +7 -0
  9. package/src/App.vue +24 -0
  10. package/src/components/dynamicForm/componentRenderer.vue +207 -0
  11. package/src/components/dynamicForm/components/advanced/index.ts +13 -0
  12. package/src/components/dynamicForm/components/advanced/upload.vue +108 -0
  13. package/src/components/dynamicForm/components/advanced/uploadImage.vue +107 -0
  14. package/src/components/dynamicForm/components/answerSheetPopup/answerSheetItem.vue +58 -0
  15. package/src/components/dynamicForm/components/answerSheetPopup/index.vue +111 -0
  16. package/src/components/dynamicForm/components/application/employee.vue +140 -0
  17. package/src/components/dynamicForm/components/application/grade.vue +183 -0
  18. package/src/components/dynamicForm/components/application/index.ts +14 -0
  19. package/src/components/dynamicForm/components/application/post.vue +136 -0
  20. package/src/components/dynamicForm/components/base/checkbox.vue +143 -0
  21. package/src/components/dynamicForm/components/base/index.ts +15 -0
  22. package/src/components/dynamicForm/components/base/input.vue +99 -0
  23. package/src/components/dynamicForm/components/base/label.vue +29 -0
  24. package/src/components/dynamicForm/components/base/radio.vue +155 -0
  25. package/src/components/dynamicForm/components/componentType.ts +16 -0
  26. package/src/components/dynamicForm/components/layout/groupLayout.vue +103 -0
  27. package/src/components/dynamicForm/components/layout/index.ts +12 -0
  28. package/src/components/dynamicForm/formRenderer.vue +567 -0
  29. package/src/components/dynamicForm/index.ts +21 -0
  30. package/src/components/dynamicForm/types/componentAttribute/advanced/uploadAttribute.ts +35 -0
  31. package/src/components/dynamicForm/types/componentAttribute/application/employeeAttribute.ts +42 -0
  32. package/src/components/dynamicForm/types/componentAttribute/application/gradeAttribute.ts +54 -0
  33. package/src/components/dynamicForm/types/componentAttribute/application/postAttribute.ts +42 -0
  34. package/src/components/dynamicForm/types/componentAttribute/base/checkboxAttribute.ts +38 -0
  35. package/src/components/dynamicForm/types/componentAttribute/base/inputAttribute.ts +31 -0
  36. package/src/components/dynamicForm/types/componentAttribute/base/radioAttribute.ts +30 -0
  37. package/src/components/dynamicForm/types/componentAttribute/baseAttribute.ts +110 -0
  38. package/src/components/dynamicForm/types/componentAttribute/editAttribute.ts +70 -0
  39. package/src/components/dynamicForm/types/componentAttribute/index.ts +37 -0
  40. package/src/components/dynamicForm/types/componentAttribute/layout/groupLayoutAttribute.ts +39 -0
  41. package/src/components/dynamicForm/types/documentView.ts +110 -0
  42. package/src/components/dynamicForm/types/enum.ts +109 -0
  43. package/src/components/dynamicForm/types/formAttribute.ts +93 -0
  44. package/src/components/dynamicForm/types/uploadOption.ts +31 -0
  45. package/src/env.d.ts +8 -0
  46. package/src/hooks/useMessage.ts +44 -0
  47. package/src/hooks/useToast.ts +29 -0
  48. package/src/hooks/useUtils.ts +201 -0
  49. package/src/index.ts +59 -0
  50. package/src/main.ts +19 -0
  51. package/src/manifest.json +72 -0
  52. package/src/pages/dynamicFormDemo.vue +1260 -0
  53. package/src/pages/dynamicFormExaminationDemo.vue +567 -0
  54. package/src/pages.json +58 -0
  55. package/src/shime-uni.d.ts +6 -0
  56. package/src/uni.scss +76 -0
  57. package/src/unocss/index.ts +20 -0
  58. package/src/unocss/rules.ts +139 -0
  59. package/src/unocss/shortcuts.ts +53 -0
  60. package/src/unocss/theme/index.ts +13 -0
  61. package/src/unocss/theme/themeOption/dark.ts +35 -0
  62. package/src/unocss/theme/themeOption/primary.ts +33 -0
  63. package/src/unocss/variants.ts +110 -0
  64. package/tsconfig.json +19 -0
  65. package/uno.config.ts +63 -0
  66. package/vite.config.ts +83 -0
  67. package/dist/build/h5/style.css +0 -1
  68. package/dist/build/h5/zhytech-ui-mobile.es.js +0 -19661
  69. package/dist/build/h5/zhytech-ui-mobile.umd.js +0 -1
  70. package/dist/dev/true/style.css +0 -455
  71. package/dist/dev/true/zhytech-ui-mobile.es.js +0 -3440
  72. package/dist/dev/true/zhytech-ui-mobile.umd.js +0 -3443
  73. /package/{dist/build/h5 → src}/static/iconfont/iconfont.css +0 -0
  74. /package/{dist/build/h5 → src}/static/iconfont/iconfont.ttf +0 -0
  75. /package/{dist/build/h5 → src}/static/iconfont/iconfont.woff +0 -0
  76. /package/{dist/build/h5 → src}/static/iconfont/iconfont.woff2 +0 -0
  77. /package/{dist/build/h5 → src}/static/scss/actionSheet.scss +0 -0
  78. /package/{dist/build/h5 → src}/static/scss/button.scss +0 -0
  79. /package/{dist/build/h5 → src}/static/scss/checkbox.scss +0 -0
  80. /package/{dist/build/h5 → src}/static/scss/form.scss +0 -0
  81. /package/{dist/build/h5 → src}/static/scss/index.scss +0 -0
  82. /package/{dist/build/h5 → src}/static/scss/input.scss +0 -0
  83. /package/{dist/build/h5 → src}/static/scss/picker.scss +0 -0
  84. /package/{dist/build/h5 → src}/static/scss/radio.scss +0 -0
@@ -0,0 +1,567 @@
1
+ <!--
2
+ * FilePath : \src\components\dynamicForm\formRenderer.vue
3
+ * Author : 苏军志
4
+ * Date : 2024-04-09 09:18
5
+ * LastEditors : 苏军志
6
+ * LastEditTime : 2025-09-05 20:31
7
+ * Description : 动态表单渲染器
8
+ * CodeIterationRecord:
9
+ -->
10
+ <template>
11
+ <wd-config-provider :theme-vars="themeVars" class="h-full">
12
+ <view class="form-renderer y-aline-start h-full -top px-5 bg-#ffffff box-border">
13
+ <h3 class="mx-5 my-10 text-center c-#000000" v-if="!hiddenTitle && (title || formData.props.formName)">
14
+ {{ title ?? formData.props.formName }}
15
+ </h3>
16
+ <wd-form ref="formRenderer" custom-class="zhy flex-auto h-full overflow-y-auto" :model="formData.datas" :resetOnChange="false">
17
+ <component-renderer
18
+ :disabled="disabled"
19
+ :labelPosition="formData.props.labelPosition ?? 'top'"
20
+ :components="formData.components"
21
+ :showDescription="showDescription"
22
+ :onePageItemFlag="onePageItemFlag"
23
+ :currentShowIDArray="currentShowIDArray"
24
+ @validate="validate"
25
+ ></component-renderer>
26
+ </wd-form>
27
+ <view v-if="onePageItemFlag && showOnePageNavButton" class="text-right mb-5 sons-class-wd-button:mr-10!">
28
+ <wd-button type="primary" @click="switchPageItem('prev')"> 上一题 </wd-button>
29
+ <wd-button type="success" @click="switchPageItem('next')"> 下一题 </wd-button>
30
+ </view>
31
+ </view>
32
+ <answer-sheet-popup
33
+ v-if="showAnswerSheetFlag"
34
+ :showCorrectOrNot="showCorrectOrNot"
35
+ :formData="formData"
36
+ @showItem="scrollToField"
37
+ ></answer-sheet-popup>
38
+ <!-- toast消息的挂载点。 -->
39
+ <wd-toast />
40
+ </wd-config-provider>
41
+ </template>
42
+ <script lang="ts">
43
+ export default {
44
+ name: "zhy-form-renderer"
45
+ };
46
+ </script>
47
+ <script setup lang="ts">
48
+ import componentRenderer from "./componentRenderer.vue";
49
+ import answerSheetPopup from "./components/answerSheetPopup/index.vue";
50
+ import type { dynamicFormData, formAttribute } from "./types/formAttribute";
51
+ import type { uploadOption } from "./types/uploadOption";
52
+ const { removeEmptyAttribute } = useUtils();
53
+ const formRenderer = ref<any>();
54
+ const showDescription = ref<boolean>(false);
55
+ const props = defineProps({
56
+ /**
57
+ * @description: 表单数据,包含表单属性,组件集合、初始数据
58
+ */
59
+ formData: {
60
+ type: Object as PropType<dynamicFormData<formAttribute>>,
61
+ default: () => {
62
+ return {
63
+ components: [],
64
+ props: {
65
+ labelPosition: "top",
66
+ labelWidth: 90,
67
+ formType: "1"
68
+ },
69
+ datas: {}
70
+ };
71
+ },
72
+ required: true
73
+ },
74
+ /**
75
+ * @description: 文件上传配置参数
76
+ */
77
+ uploadOptions: {
78
+ type: Object as PropType<uploadOption>,
79
+ default: () => {
80
+ return {
81
+ serverApi: "",
82
+ headers: undefined,
83
+ autoUpload: false,
84
+ params: undefined
85
+ } as uploadOption;
86
+ }
87
+ },
88
+ /**
89
+ * @description: 监视标记
90
+ */
91
+ watching: {
92
+ type: Boolean,
93
+ default: false
94
+ },
95
+ /**
96
+ * 是否禁用
97
+ */
98
+ disabled: {
99
+ type: Boolean,
100
+ default: false
101
+ },
102
+ /**
103
+ * 标题
104
+ */
105
+ title: {
106
+ type: String,
107
+ default: undefined
108
+ },
109
+ /**
110
+ * 是否隐藏标题
111
+ */
112
+ hiddenTitle: {
113
+ type: Boolean,
114
+ default: false
115
+ },
116
+ /**
117
+ * 一页一项标记
118
+ */
119
+ onePageItemFlag: {
120
+ type: Boolean,
121
+ default: false
122
+ },
123
+ /**
124
+ * 一页一项时显示切题导航
125
+ */
126
+ showOnePageNavButton: {
127
+ type: Boolean,
128
+ default: false
129
+ },
130
+ /**
131
+ * 实时显示答案模式
132
+ */
133
+ realTimeDisplayAnswerMode: {
134
+ type: Boolean,
135
+ default: false
136
+ },
137
+ /**
138
+ * 每个项目都显示解析开关
139
+ */
140
+ everyItemDisplayAnalysisSwitch: {
141
+ type: Boolean,
142
+ default: false
143
+ }
144
+ });
145
+ const emits = defineEmits(["change", "switchPage"]);
146
+ const { formData, onePageItemFlag } = toRefs(props);
147
+ provide("realTimeDisplayAnswerMode", props.realTimeDisplayAnswerMode);
148
+ provide("everyItemDisplayAnalysisSwitch", props.everyItemDisplayAnalysisSwitch);
149
+ provide("uploadOptions", props.uploadOptions);
150
+ provide("formData", formData);
151
+ provide(
152
+ "formType",
153
+ computed(() => formData.value.props.formType)
154
+ );
155
+ watch(formData, () => {
156
+ initComponents(formData.value.components);
157
+ });
158
+ // 获取主题变量
159
+ const theme = inject("theme", ref({}));
160
+ const themeVars = computed(() => ({ ...theme.value }));
161
+ onMounted(() => initComponents(formData.value.components));
162
+ /**
163
+ * @description: 初始化表单属性
164
+ */
165
+ const initComponents = (components: Record<string, any>[]) => {
166
+ if (components?.length) {
167
+ components.forEach((component) => {
168
+ if (component.isLayout && component.children?.length) {
169
+ component.children.forEach((child: Record<string, any>) => {
170
+ child.rules = getFormRules(child);
171
+ });
172
+ } else {
173
+ component.rules = getFormRules(component);
174
+ }
175
+ });
176
+ allComponentIdArray.value = getComponentIDArray(formData.value.components);
177
+ currentShowIDArray.value = allComponentIdArray.value[0];
178
+ }
179
+ };
180
+ const { isEmpty } = useUtils();
181
+ /**
182
+ * @description: 获取组件的表单校验规则
183
+ * @param component 组件
184
+ * @return
185
+ */
186
+ const getFormRules = (component: Record<string, any>) => {
187
+ const componentProps = component.props;
188
+ let formRules: Record<string, any>[] = [];
189
+ if (componentProps.required) {
190
+ let formRule: Record<string, any> = {
191
+ required: componentProps.required,
192
+ message: componentProps.requiredMessage || "必填项"
193
+ };
194
+ formRule.validator = (value: any, rule: any) => {
195
+ if (isEmpty(value)) {
196
+ return Promise.reject(rule.message);
197
+ }
198
+ return Promise.resolve();
199
+ };
200
+ formRules.push(formRule);
201
+ }
202
+ if (componentProps.pattern) {
203
+ formRules.push({
204
+ pattern: componentProps.pattern,
205
+ message: componentProps.patternMessage
206
+ });
207
+ }
208
+ return formRules;
209
+ };
210
+ // #region 获取表单数据
211
+ /**
212
+ * @description: 获取数据
213
+ * @return
214
+ */
215
+ const getDatas = () => {
216
+ let data: Record<string, any> = removeEmptyAttribute(formData.value.datas);
217
+ let resultData: Record<string, any>[] = [];
218
+ let fileList: Record<string, any>[] = [];
219
+ const componentIDs = Object.keys(data);
220
+ const result = setLayoutChildrenResultData(formData.value.components, resultData, fileList, componentIDs, data);
221
+ return result;
222
+ };
223
+ /**
224
+ * @description: 设置布局组件子组件的返回值-递归
225
+ * @param components
226
+ * @param resultData
227
+ * @param fileList
228
+ * @param componentIDs
229
+ * @param data
230
+ * @param groupID
231
+ * @param pageID
232
+ * @return
233
+ */
234
+ const setLayoutChildrenResultData = (
235
+ components: Record<string, any>[],
236
+ resultData: Record<string, any>[],
237
+ fileList: Record<string, any>[],
238
+ componentIDs: string[],
239
+ data: Record<string, any>,
240
+ groupID?: string,
241
+ pageID?: string
242
+ ) => {
243
+ let newResultData = resultData;
244
+ let newFileList = fileList;
245
+ for (let i = 0; i < components.length; i++) {
246
+ const component = components[i];
247
+ if (componentIDs.includes(component.id)) {
248
+ const newPageID = component.props.pageID || pageID;
249
+ const result = setDataByComponentType(newResultData, newFileList, component.id, data, component, groupID, newPageID);
250
+ newResultData = result.resultData;
251
+ newFileList = result.fileList;
252
+ continue;
253
+ }
254
+ // 如果是布局组件并且有子组件
255
+ if (component.isLayout && component?.children?.length) {
256
+ const childGroupID = component.id;
257
+ // 递归设置子组件的数据
258
+ const result = setLayoutChildrenResultData(component.children, newResultData, newFileList, componentIDs, data, childGroupID, pageID);
259
+ newResultData = result.resultData;
260
+ newFileList = result.fileList;
261
+ }
262
+ }
263
+ return { resultData: newResultData, fileList: newFileList };
264
+ };
265
+ /**
266
+ * @description: 依据组件类型设置值
267
+ * @param resultData
268
+ * @param fileList
269
+ * @param componentID
270
+ * @param data
271
+ * @param component
272
+ * @param groupID
273
+ * @param pageID
274
+ */
275
+ const setDataByComponentType = (
276
+ resultData: Record<string, any>[],
277
+ fileList: Record<string, any>[],
278
+ componentID: string,
279
+ data: Record<string, any>,
280
+ component: Record<string, any>,
281
+ groupID?: string,
282
+ pageID?: string
283
+ ) => {
284
+ switch (component.type) {
285
+ case "radio":
286
+ resultData.push({
287
+ sourceType: component.itemSourceType,
288
+ pageID,
289
+ groupID,
290
+ parentID: componentID,
291
+ id: data[componentID],
292
+ // 可能包含手动录入的备注,通过拼接获取到录入的备注
293
+ value: data[`${componentID}||${data[componentID]}`] ?? ""
294
+ });
295
+ break;
296
+ case "checkbox":
297
+ data[componentID]?.forEach((value: any) => {
298
+ resultData.push({
299
+ sourceType: component.itemSourceType,
300
+ pageID,
301
+ groupID,
302
+ parentID: componentID,
303
+ id: value,
304
+ // 可能包含手动录入的备注,通过拼接获取到录入的备注
305
+ value: data[`${componentID}||${value}`] ?? ""
306
+ });
307
+ });
308
+ break;
309
+ case "upload":
310
+ case "uploadImage":
311
+ // 如果不是自动上传,这里需要处理
312
+ if (!props.uploadOptions?.autoUpload && data[componentID]?.length) {
313
+ // 将文件和ID传到后端 文件保存后 根据componentID反写业务明细表
314
+ let addFileList: Record<string, any>[] = [];
315
+ let fileIDs: string[] = [];
316
+ data[componentID].forEach((file: Record<string, any>) => {
317
+ file.id = componentID;
318
+ addFileList.push(file);
319
+ if (file.fileID) {
320
+ fileIDs.push(file.fileID);
321
+ }
322
+ });
323
+ fileList.push({
324
+ sourceType: component.itemSourceType,
325
+ id: componentID,
326
+ pageID: component.props.pageID,
327
+ groupID,
328
+ fileIDs: fileIDs,
329
+ files: addFileList
330
+ });
331
+ }
332
+ break;
333
+ default:
334
+ resultData.push({
335
+ sourceType: component.itemSourceType,
336
+ pageID,
337
+ groupID,
338
+ parentID: componentID,
339
+ id: componentID,
340
+ value: data[componentID]
341
+ });
342
+ break;
343
+ }
344
+ return { resultData, fileList };
345
+ };
346
+
347
+ // 判断是否需要实时回传数据
348
+ if (props.watching) {
349
+ watch(
350
+ () => formData.value.datas,
351
+ () => {
352
+ const resultData = getDatas();
353
+ emits("change", resultData.resultData, resultData.fileList);
354
+ },
355
+ { immediate: true, deep: true }
356
+ );
357
+ }
358
+ // #endregion
359
+
360
+ //#region 一页一项模式逻辑
361
+ const allComponentIdArray = ref<string[][]>([]);
362
+ const currentShowIDArray = ref<string[]>([]);
363
+ /**
364
+ * @description: 将所有组件转换位数组地址(嵌套)
365
+ * @param components 组件集合
366
+ * @return
367
+ */
368
+ const getComponentIDArray = (components: Record<string, any>[], parentPath?: string[] = []) => {
369
+ let result: string[][] = [];
370
+ components.forEach((component) => {
371
+ const currentPath = [...parentPath, component.id];
372
+ // 如果是叶子节点(没有子组件或不是布局组件)
373
+ if (!component.isLayout || !component.children?.length) {
374
+ result.push(currentPath);
375
+ }
376
+ // 递归处理子组件
377
+ if (component.isLayout && component.children?.length) {
378
+ const childPaths = getComponentIDArray(component.children, currentPath);
379
+ result = result.concat(childPaths);
380
+ }
381
+ });
382
+ return result;
383
+ };
384
+ const currentIndex = computed(() => allComponentIdArray.value.indexOf(currentShowIDArray.value));
385
+ /**
386
+ * @description: 一页一项模式下切换项目
387
+ * @param type
388
+ * @param id
389
+ * @return
390
+ */
391
+ const switchPageItem = (type: string, id?: string) => {
392
+ if (!onePageItemFlag.value || !currentShowIDArray.value.length) {
393
+ return;
394
+ }
395
+ if (type === "prev") {
396
+ if (currentIndex.value > 0) {
397
+ currentShowIDArray.value = allComponentIdArray.value[currentIndex.value - 1];
398
+ } else {
399
+ // showToast("已经是第一题了");
400
+ }
401
+ }
402
+ if (type === "next") {
403
+ if (currentIndex.value < allComponentIdArray.value.length - 1) {
404
+ currentShowIDArray.value = allComponentIdArray.value[currentIndex.value + 1];
405
+ } else {
406
+ // showToast("已经是最后一题了");
407
+ }
408
+ }
409
+ if (id) {
410
+ // 直接跳转到指定ID
411
+ const targetIDArray = allComponentIdArray.value.find((idArray) => idArray.includes(id));
412
+ if (targetIDArray) {
413
+ currentShowIDArray.value = targetIDArray;
414
+ }
415
+ }
416
+ // 抛出事件,告诉父组件切换了第几页
417
+ emits("switchPage", currentIndex.value + 1, allComponentIdArray.value.length);
418
+ return {
419
+ currentIndex: currentIndex.value + 1,
420
+ count: allComponentIdArray.value.length
421
+ };
422
+ };
423
+ //#endregion
424
+
425
+ const showAnswerSheetFlag = ref<boolean>(false);
426
+ const showCorrectOrNot = ref<boolean>(false);
427
+ /**
428
+ * @description: 显示答题卡
429
+ * @param showCorrectOrNot 显示正确与否
430
+ */
431
+ const showAnswerSheet = (showCorrectOrNotFlag?: boolean) => {
432
+ showCorrectOrNot.value = showCorrectOrNotFlag ?? false;
433
+ showAnswerSheetFlag.value = false;
434
+ nextTick(() => {
435
+ showAnswerSheetFlag.value = true;
436
+ });
437
+ };
438
+ //#region 滚动到指定的字段
439
+ let highlightStyle: HTMLStyleElement | null = null;
440
+ /**
441
+ * @description: 滚动到指定的DOM元素
442
+ * @param prop
443
+ * @return
444
+ */
445
+ const scrollToFormProp = (prop: string) => {
446
+ // 查找目标dom元素
447
+ const element = document.querySelector(`.form-prop-${prop}`);
448
+ if (element) {
449
+ element.scrollIntoView({ behavior: "smooth", block: "center" });
450
+ // 添加加高亮效果
451
+ element.classList.add("highlight");
452
+ // 如果高亮样式不存在,则添加
453
+ if (!highlightStyle || !document.head.contains(highlightStyle)) {
454
+ highlightStyle = document.createElement("style");
455
+ highlightStyle.innerHTML = `
456
+ .form-prop-${prop}.highlight {
457
+ border-color: red;
458
+ background-color: #ffe6e6;
459
+ }
460
+ .form-prop-${prop}.highlight * {
461
+ background-color: #ffe6e6;
462
+ }
463
+ `;
464
+ document.head.appendChild(highlightStyle);
465
+ }
466
+ // 2秒后移除高亮效果
467
+ setTimeout(() => {
468
+ if (highlightStyle && document.head.contains(highlightStyle)) {
469
+ document.head.removeChild(highlightStyle);
470
+ highlightStyle = null;
471
+ }
472
+ element.classList.remove("highlight");
473
+ }, 2000);
474
+ }
475
+ };
476
+ /**
477
+ * @description: 滚动到指定的字段
478
+ * @param prop
479
+ * @return
480
+ */
481
+ const scrollToField = (prop: string) => {
482
+ // 如果是一页已项模式,特殊处理
483
+ if (onePageItemFlag.value) {
484
+ switchPageItem("", prop);
485
+ return;
486
+ }
487
+ scrollToFormProp(prop);
488
+ if (showAnswerSheetFlag.value) {
489
+ showAnswerSheetFlag.value = false;
490
+ }
491
+ };
492
+ //#endregion
493
+ /**
494
+ * @description: 表单的验证
495
+ * @param prop 要验证的字段
496
+ * @return
497
+ */
498
+ const validate = async (prop?: string) => {
499
+ let result = await formRenderer.value?.validate(prop);
500
+ if (!result) {
501
+ return true;
502
+ }
503
+ // 验证不通过,
504
+ if (!result.valid) {
505
+ // 指定字段验证不通过,滚动到该字段
506
+ if (prop) {
507
+ scrollToField(prop);
508
+ return;
509
+ }
510
+ // 整个表单校验,滚动到第一个检核不通过的的字段
511
+ const firstErrorProp = result.errors.reduce((prev: any, curr: any) => {
512
+ const prevIndex = allComponentIdArray.value.findIndex((idArray) => idArray.includes(prev.prop));
513
+ const currIndex = allComponentIdArray.value.findIndex((idArray) => idArray.includes(curr.prop));
514
+ return currIndex < prevIndex ? curr : prev;
515
+ }, result.errors[0])?.prop;
516
+ scrollToField(firstErrorProp);
517
+ }
518
+ return result.valid;
519
+ };
520
+ /**
521
+ * 向父组件暴漏方法
522
+ */
523
+ defineExpose({
524
+ /**
525
+ * @description: 获取表单数据
526
+ * @param callback
527
+ * @return
528
+ */
529
+ getDatas,
530
+ /**
531
+ * @description: 对整个表单的内容进行验证
532
+ * @param callback
533
+ * @return
534
+ */
535
+ validate,
536
+ /**
537
+ * @description: 对该表单项进行重置,将其值重置为初始值并移除校验结果
538
+ * @param
539
+ * @return
540
+ */
541
+ resetFields() {
542
+ nextTick(() => formRenderer.value.reset());
543
+ },
544
+ /**
545
+ * @description: 滚动到指定的字段
546
+ * @param prop
547
+ * @return
548
+ */
549
+ scrollToField,
550
+ /**
551
+ * @description:显示/隐藏 项目说明/答案解析
552
+ */
553
+ toggleDescription() {
554
+ showDescription.value = !showDescription.value;
555
+ },
556
+ /**
557
+ * @description: 一页一项模式下切换项目
558
+ * @param type
559
+ * @return
560
+ */
561
+ switchPageItem,
562
+ /**
563
+ * @description: 显示答题卡
564
+ */
565
+ showAnswerSheet
566
+ });
567
+ </script>
@@ -0,0 +1,21 @@
1
+ /*
2
+ * FilePath : \src\components\dynamicForm\index.ts
3
+ * Author : 苏军志
4
+ * Date : 2024-08-25 16:37
5
+ * LastEditors : 苏军志
6
+ * LastEditTime : 2025-09-04 10:43
7
+ * Description : 动态表单组件导出
8
+ * CodeIterationRecord:
9
+ */
10
+ /* eslint-disable id-match */
11
+ import formRenderer from "./formRenderer.vue";
12
+ import type { dynamicFormData, formAttribute } from "./types/formAttribute";
13
+ import type { uploadOption } from "./types/uploadOption";
14
+ import type { dictionaryData, dictionaryItem } from "./types/formAttribute";
15
+ import type { documentView } from "./types/documentView";
16
+
17
+ formRenderer.install = function (app: any) {
18
+ app.component(formRenderer.name, formRenderer);
19
+ };
20
+ export type { dynamicFormData, formAttribute, dictionaryData, dictionaryItem, uploadOption, documentView };
21
+ export { formRenderer };
@@ -0,0 +1,35 @@
1
+ /*
2
+ * FilePath : \src\components\dynamicForm\types\componentAttribute\advanced\uploadAttribute.ts
3
+ * Author : 苏军志
4
+ * Date : 2024-06-14 11:25
5
+ * LastEditors : 苏军志
6
+ * LastEditTime : 2024-06-14 21:10
7
+ * Description : 文件、图片上传(upload、uploadImage)组件属性
8
+ * CodeIterationRecord:
9
+ */
10
+
11
+ /**
12
+ * @description: 文件、图片上传(upload、uploadImage)组件属性
13
+ */
14
+ export interface uploadAttribute {
15
+ /**
16
+ * fileLimit是否可以多选
17
+ */
18
+ multiple: boolean;
19
+ /**
20
+ * 文件数量限制
21
+ */
22
+ fileLimit: number;
23
+ /**
24
+ * 文件类型
25
+ */
26
+ fileType: string;
27
+ /**
28
+ * 文件大小限制
29
+ */
30
+ fileSize: number;
31
+ /**
32
+ * 文件大小单位
33
+ */
34
+ fileSizeUnit: string;
35
+ }
@@ -0,0 +1,42 @@
1
+ /*
2
+ * FilePath : \src\components\dynamicForm\types\componentAttribute\application\employeeAttribute.ts
3
+ * Author : 苏军志
4
+ * Date : 2024-03-04 18:54
5
+ * LastEditors : 苏军志
6
+ * LastEditTime : 2024-06-11 10:59
7
+ * Description : 人员(employee)组件属性
8
+ * CodeIterationRecord:
9
+ */
10
+ /**
11
+ * @description: 人员(employee)组件属性
12
+ */
13
+ export interface employeeAttribute {
14
+ /**
15
+ * 部门ID
16
+ */
17
+ departmentID: number;
18
+ /**
19
+ * 显示类型
20
+ */
21
+ type: "radio" | "checkbox" | "selector";
22
+ /**
23
+ * displayType!=selector时人员是否换行
24
+ */
25
+ newLine: boolean;
26
+ /**
27
+ * displayType=checkbox时最少选择个数
28
+ */
29
+ min?: number;
30
+ /**
31
+ * displayType=checkbox时最多选择个数
32
+ */
33
+ max?: number;
34
+ /**
35
+ * displayType=selector时是否可以多选
36
+ */
37
+ multiple?: boolean;
38
+ /**
39
+ * 人员列表
40
+ */
41
+ options: Record<string, any>[];
42
+ }