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.
- package/.eslintignore +11 -0
- package/.eslintrc.cjs +253 -0
- package/.prettierignore +0 -0
- package/.prettierrc.json +9 -0
- package/.vscode/settings.json +131 -0
- package/index.html +39 -0
- package/package.json +4 -26
- package/shims-uni.d.ts +7 -0
- package/src/App.vue +24 -0
- package/src/components/dynamicForm/componentRenderer.vue +207 -0
- package/src/components/dynamicForm/components/advanced/index.ts +13 -0
- package/src/components/dynamicForm/components/advanced/upload.vue +108 -0
- package/src/components/dynamicForm/components/advanced/uploadImage.vue +107 -0
- package/src/components/dynamicForm/components/answerSheetPopup/answerSheetItem.vue +58 -0
- package/src/components/dynamicForm/components/answerSheetPopup/index.vue +111 -0
- package/src/components/dynamicForm/components/application/employee.vue +140 -0
- package/src/components/dynamicForm/components/application/grade.vue +183 -0
- package/src/components/dynamicForm/components/application/index.ts +14 -0
- package/src/components/dynamicForm/components/application/post.vue +136 -0
- package/src/components/dynamicForm/components/base/checkbox.vue +143 -0
- package/src/components/dynamicForm/components/base/index.ts +15 -0
- package/src/components/dynamicForm/components/base/input.vue +99 -0
- package/src/components/dynamicForm/components/base/label.vue +29 -0
- package/src/components/dynamicForm/components/base/radio.vue +155 -0
- package/src/components/dynamicForm/components/componentType.ts +16 -0
- package/src/components/dynamicForm/components/layout/groupLayout.vue +103 -0
- package/src/components/dynamicForm/components/layout/index.ts +12 -0
- package/src/components/dynamicForm/formRenderer.vue +567 -0
- package/src/components/dynamicForm/index.ts +21 -0
- package/src/components/dynamicForm/types/componentAttribute/advanced/uploadAttribute.ts +35 -0
- package/src/components/dynamicForm/types/componentAttribute/application/employeeAttribute.ts +42 -0
- package/src/components/dynamicForm/types/componentAttribute/application/gradeAttribute.ts +54 -0
- package/src/components/dynamicForm/types/componentAttribute/application/postAttribute.ts +42 -0
- package/src/components/dynamicForm/types/componentAttribute/base/checkboxAttribute.ts +38 -0
- package/src/components/dynamicForm/types/componentAttribute/base/inputAttribute.ts +31 -0
- package/src/components/dynamicForm/types/componentAttribute/base/radioAttribute.ts +30 -0
- package/src/components/dynamicForm/types/componentAttribute/baseAttribute.ts +110 -0
- package/src/components/dynamicForm/types/componentAttribute/editAttribute.ts +70 -0
- package/src/components/dynamicForm/types/componentAttribute/index.ts +37 -0
- package/src/components/dynamicForm/types/componentAttribute/layout/groupLayoutAttribute.ts +39 -0
- package/src/components/dynamicForm/types/documentView.ts +110 -0
- package/src/components/dynamicForm/types/enum.ts +109 -0
- package/src/components/dynamicForm/types/formAttribute.ts +93 -0
- package/src/components/dynamicForm/types/uploadOption.ts +31 -0
- package/src/env.d.ts +8 -0
- package/src/hooks/useMessage.ts +44 -0
- package/src/hooks/useToast.ts +29 -0
- package/src/hooks/useUtils.ts +201 -0
- package/src/index.ts +59 -0
- package/src/main.ts +19 -0
- package/src/manifest.json +72 -0
- package/src/pages/dynamicFormDemo.vue +1260 -0
- package/src/pages/dynamicFormExaminationDemo.vue +567 -0
- package/src/pages.json +58 -0
- package/src/shime-uni.d.ts +6 -0
- package/src/uni.scss +76 -0
- package/src/unocss/index.ts +20 -0
- package/src/unocss/rules.ts +139 -0
- package/src/unocss/shortcuts.ts +53 -0
- package/src/unocss/theme/index.ts +13 -0
- package/src/unocss/theme/themeOption/dark.ts +35 -0
- package/src/unocss/theme/themeOption/primary.ts +33 -0
- package/src/unocss/variants.ts +110 -0
- package/tsconfig.json +19 -0
- package/uno.config.ts +63 -0
- package/vite.config.ts +83 -0
- package/dist/build/h5/style.css +0 -1
- package/dist/build/h5/zhytech-ui-mobile.es.js +0 -19661
- package/dist/build/h5/zhytech-ui-mobile.umd.js +0 -1
- package/dist/dev/true/style.css +0 -455
- package/dist/dev/true/zhytech-ui-mobile.es.js +0 -3440
- package/dist/dev/true/zhytech-ui-mobile.umd.js +0 -3443
- /package/{dist/build/h5 → src}/static/iconfont/iconfont.css +0 -0
- /package/{dist/build/h5 → src}/static/iconfont/iconfont.ttf +0 -0
- /package/{dist/build/h5 → src}/static/iconfont/iconfont.woff +0 -0
- /package/{dist/build/h5 → src}/static/iconfont/iconfont.woff2 +0 -0
- /package/{dist/build/h5 → src}/static/scss/actionSheet.scss +0 -0
- /package/{dist/build/h5 → src}/static/scss/button.scss +0 -0
- /package/{dist/build/h5 → src}/static/scss/checkbox.scss +0 -0
- /package/{dist/build/h5 → src}/static/scss/form.scss +0 -0
- /package/{dist/build/h5 → src}/static/scss/index.scss +0 -0
- /package/{dist/build/h5 → src}/static/scss/input.scss +0 -0
- /package/{dist/build/h5 → src}/static/scss/picker.scss +0 -0
- /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
|
+
}
|