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,207 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* FilePath : \src\components\dynamicForm\componentRenderer.vue
|
|
3
|
+
* Author : 苏军志
|
|
4
|
+
* Date : 2024-03-04 20:56
|
|
5
|
+
* LastEditors : 苏军志
|
|
6
|
+
* LastEditTime : 2025-09-05 10:44
|
|
7
|
+
* Description : 组件渲染器
|
|
8
|
+
* CodeIterationRecord:
|
|
9
|
+
-->
|
|
10
|
+
<template>
|
|
11
|
+
<template v-for="(component, index) in components" :key="index">
|
|
12
|
+
<!-- 非隐藏项 -->
|
|
13
|
+
<view
|
|
14
|
+
v-if="
|
|
15
|
+
(getByDynamicCondition(component, 'show') && !onePageItemFlag) || (onePageItemFlag && currentShowIDArray.includes(component.id))
|
|
16
|
+
"
|
|
17
|
+
class="p-5"
|
|
18
|
+
>
|
|
19
|
+
<!-- 容器组件直接显示 且(非一项一页 或 一项一页的当前项)-->
|
|
20
|
+
<component
|
|
21
|
+
v-if="component.isLayout"
|
|
22
|
+
:is="(componentTypes as any)[component.type]"
|
|
23
|
+
v-model:component="components[index]"
|
|
24
|
+
:componentID="component.id"
|
|
25
|
+
:disabled="disabled"
|
|
26
|
+
:labelPosition="labelPosition"
|
|
27
|
+
:showDescription="showDescription"
|
|
28
|
+
:onePageItemFlag="onePageItemFlag"
|
|
29
|
+
:currentShowIDArray="currentShowIDArray"
|
|
30
|
+
@validate="(prop:string) => emits('validate', prop)"
|
|
31
|
+
/>
|
|
32
|
+
|
|
33
|
+
<wd-cell
|
|
34
|
+
v-else
|
|
35
|
+
:custom-class="`zhy form-item form-prop-${component.id}`"
|
|
36
|
+
:vertical="labelPosition === 'top' || component.props.labelNewLine"
|
|
37
|
+
:title-width="`${
|
|
38
|
+
component.props.showLabel && !(labelPosition === 'top' || component.props.labelNewLine) ? component.props.labelWidth : ''
|
|
39
|
+
}px`"
|
|
40
|
+
:prop="component.id"
|
|
41
|
+
:rules="component.rules"
|
|
42
|
+
>
|
|
43
|
+
<template #label>
|
|
44
|
+
<wd-text
|
|
45
|
+
custom-class="zhy"
|
|
46
|
+
:text="component.props.showLabel ? component.props.label + ':' : ''"
|
|
47
|
+
:size="`${onePageItemFlag ? 18 : component.props.fontSize > 14 ? component.props.fontSize : 14}px`"
|
|
48
|
+
:bold="component.props.isBold"
|
|
49
|
+
:color="component.props.color || '#000000'"
|
|
50
|
+
></wd-text>
|
|
51
|
+
</template>
|
|
52
|
+
<component
|
|
53
|
+
:class="{ 'sons-html-*:fs-18!': onePageItemFlag }"
|
|
54
|
+
:is="(componentTypes as any)[component.type]"
|
|
55
|
+
v-model:componentProps="component.props"
|
|
56
|
+
v-model:datas="formData.datas"
|
|
57
|
+
:componentID="component.id"
|
|
58
|
+
:disabled="disabled || getByDynamicCondition(component, 'disabled')"
|
|
59
|
+
:readonly="getByDynamicCondition(component, 'readonly')"
|
|
60
|
+
:showDescription="showDescription"
|
|
61
|
+
:key="setValidateWatch(component.id, formData.datas[component.id])"
|
|
62
|
+
/>
|
|
63
|
+
</wd-cell>
|
|
64
|
+
</view>
|
|
65
|
+
</template>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<script setup lang="ts">
|
|
69
|
+
import { baseComponent, advancedComponent, applicationComponent, layoutComponent } from "./components/componentType";
|
|
70
|
+
import type { dynamicFormData, formAttribute } from "./types/formAttribute";
|
|
71
|
+
const componentTypes: Record<string, any> = { ...baseComponent, ...advancedComponent, ...applicationComponent, ...layoutComponent };
|
|
72
|
+
const { computedExpression } = useUtils();
|
|
73
|
+
const props = defineProps({
|
|
74
|
+
/**
|
|
75
|
+
* 组件
|
|
76
|
+
*/
|
|
77
|
+
components: {
|
|
78
|
+
type: Array as PropType<Record<string, any>[]>,
|
|
79
|
+
required: true
|
|
80
|
+
},
|
|
81
|
+
/**
|
|
82
|
+
* 标题位置
|
|
83
|
+
*/
|
|
84
|
+
labelPosition: {
|
|
85
|
+
type: String,
|
|
86
|
+
default: "top"
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* 是否禁用
|
|
90
|
+
*/
|
|
91
|
+
disabled: {
|
|
92
|
+
type: Boolean,
|
|
93
|
+
default: false
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* 是否显示组件说明
|
|
97
|
+
*/
|
|
98
|
+
showDescription: {
|
|
99
|
+
type: Boolean,
|
|
100
|
+
default: false
|
|
101
|
+
},
|
|
102
|
+
/**
|
|
103
|
+
* 一页一项标记
|
|
104
|
+
*/
|
|
105
|
+
onePageItemFlag: {
|
|
106
|
+
type: Boolean,
|
|
107
|
+
default: false
|
|
108
|
+
},
|
|
109
|
+
/**
|
|
110
|
+
* 一页一项时的当前显示项目ID集合
|
|
111
|
+
*/
|
|
112
|
+
currentShowIDArray: {
|
|
113
|
+
type: Array<String>,
|
|
114
|
+
default: []
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
const { components, showDescription, onePageItemFlag, currentShowIDArray } = toRefs(props);
|
|
118
|
+
// 获取父组件中的变量
|
|
119
|
+
let formData = inject("formData") as Ref<dynamicFormData<formAttribute>>;
|
|
120
|
+
// 动态条件处理方式,后续迭代只需在此对象中添加属性即可
|
|
121
|
+
const dynamicConditionType: Record<string, Function> = {
|
|
122
|
+
// 显示动态条件
|
|
123
|
+
show: (componentProp: Record<string, any>) => {
|
|
124
|
+
if (componentProp.showFlag === undefined) {
|
|
125
|
+
return String(true);
|
|
126
|
+
}
|
|
127
|
+
let condition = "";
|
|
128
|
+
if (componentProp.showFlag) {
|
|
129
|
+
condition = componentProp.hiddenConditionExpression
|
|
130
|
+
? `!(${componentProp.hiddenConditionExpression})`
|
|
131
|
+
: String(componentProp.showFlag);
|
|
132
|
+
} else {
|
|
133
|
+
condition = componentProp.showConditionExpression || String(componentProp.showFlag);
|
|
134
|
+
}
|
|
135
|
+
return condition;
|
|
136
|
+
},
|
|
137
|
+
// 禁用动态条件
|
|
138
|
+
disabled: (componentProp: Record<string, any>) => {
|
|
139
|
+
return componentProp.disabledConditionExpression || String(componentProp.disabled === undefined ? false : componentProp.disabled);
|
|
140
|
+
},
|
|
141
|
+
// 只读动态条件
|
|
142
|
+
readonly: (componentProp: Record<string, any>) => {
|
|
143
|
+
return componentProp.readonlyConditionExpression || String(componentProp.readonly === undefined ? false : componentProp.readonly);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* @description: 动态条件计算
|
|
148
|
+
* @param component 组件
|
|
149
|
+
* @param propType 组件属性名称
|
|
150
|
+
* @return
|
|
151
|
+
*/
|
|
152
|
+
const getByDynamicCondition = (component: Record<string, any>, propType: string) => {
|
|
153
|
+
let condition = dynamicConditionType[propType](component.props);
|
|
154
|
+
if (!condition) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
if (!["true", "false"].includes(condition)) {
|
|
158
|
+
const idTemplates = condition.match(/{(.*?)}/g);
|
|
159
|
+
idTemplates.forEach((idTemplate: string) => {
|
|
160
|
+
const id = idTemplate.replace(/\{|}/g, "");
|
|
161
|
+
let value = formData.value.datas[id];
|
|
162
|
+
// 找到目标组件
|
|
163
|
+
const paramComponent = components.value.find((paramComponent) => paramComponent.id === id);
|
|
164
|
+
if (paramComponent?.type === "grade") {
|
|
165
|
+
value = formData.value.datas[id]?.score;
|
|
166
|
+
}
|
|
167
|
+
condition = condition.replace(idTemplate, value || "");
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const result = computedExpression(condition);
|
|
171
|
+
// 如果此组件满足隐藏条件,且有值,则清空值
|
|
172
|
+
if (propType === "show" && !result) {
|
|
173
|
+
let components: Record<string, any>[] = [];
|
|
174
|
+
// 如果是布局组件 循环子组件 删除值
|
|
175
|
+
if (component.isLayout && component.children?.length) {
|
|
176
|
+
components = component.children;
|
|
177
|
+
} else {
|
|
178
|
+
components = [component];
|
|
179
|
+
}
|
|
180
|
+
components.forEach((tempComponent) => {
|
|
181
|
+
if (!formData.value.datas[tempComponent.id]) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (tempComponent.type === "grade") {
|
|
185
|
+
delete formData.value.datas[tempComponent.id].score;
|
|
186
|
+
} else {
|
|
187
|
+
delete formData.value.datas[tempComponent.id];
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// #region form表单验证
|
|
195
|
+
const watchID = ref<string>();
|
|
196
|
+
const watchData = ref<any>();
|
|
197
|
+
const setValidateWatch = (id: string, data: any) => {
|
|
198
|
+
watchID.value = id;
|
|
199
|
+
watchData.value = data;
|
|
200
|
+
return id;
|
|
201
|
+
};
|
|
202
|
+
const emits = defineEmits(["validate"]);
|
|
203
|
+
watch(watchData, () => {
|
|
204
|
+
emits("validate", watchID.value);
|
|
205
|
+
});
|
|
206
|
+
// #endregion
|
|
207
|
+
</script>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* FilePath : \src\components\dynamicForm\components\advanced\index.ts
|
|
3
|
+
* Author : 苏军志
|
|
4
|
+
* Date : 2024-03-04 19:04
|
|
5
|
+
* LastEditors : 苏军志
|
|
6
|
+
* LastEditTime : 2025-08-24 19:30
|
|
7
|
+
* Description : 高级组件集合
|
|
8
|
+
* CodeIterationRecord:
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import upload from "./upload.vue";
|
|
12
|
+
import uploadImage from "./uploadImage.vue";
|
|
13
|
+
export { upload, uploadImage };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* FilePath : \src\components\dynamicForm\components\advanced\upload.vue
|
|
3
|
+
* Author : 苏军志
|
|
4
|
+
* Date : 2024-07-16 10:29
|
|
5
|
+
* LastEditors : 苏军志
|
|
6
|
+
* LastEditTime : 2025-09-05 08:48
|
|
7
|
+
* Description : 文件上传(upload)组件渲染器
|
|
8
|
+
* CodeIterationRecord:
|
|
9
|
+
-->
|
|
10
|
+
<template>
|
|
11
|
+
<div class="zhy-form-component-upload">
|
|
12
|
+
<wd-upload
|
|
13
|
+
ref="upload"
|
|
14
|
+
:file-list="fileList"
|
|
15
|
+
:action="uploadOptions.serverApi"
|
|
16
|
+
:multiple="componentProps.multiple"
|
|
17
|
+
:limit="componentProps.fileLimit"
|
|
18
|
+
:auto-upload="uploadOptions?.autoUpload"
|
|
19
|
+
:extension="componentProps.fileType?.split(',')"
|
|
20
|
+
accept="all"
|
|
21
|
+
:before-upload="beforeUpload"
|
|
22
|
+
:before-remove="beforeRemove"
|
|
23
|
+
>
|
|
24
|
+
<wd-button>上传文件</wd-button>
|
|
25
|
+
</wd-upload>
|
|
26
|
+
<view
|
|
27
|
+
v-if="showDescription && (formTypeEnum.Examination || componentProps.description)"
|
|
28
|
+
class="dynamic-form-item-description show-description"
|
|
29
|
+
>
|
|
30
|
+
<wd-icon custom-class="description-tip" class-prefix="iconfont zhy" name="tip" color="#ff0000" />
|
|
31
|
+
{{ `${formType === formTypeEnum.Form ? "项目说明" : "答案解析"}:${componentProps.description || "无"}` }}
|
|
32
|
+
</view>
|
|
33
|
+
<!-- message消息的挂载点。 -->
|
|
34
|
+
<wd-message-box></wd-message-box>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
import { formTypeEnum } from "../../types/enum";
|
|
39
|
+
import type { baseAttribute, uploadAttribute } from "../../types/componentAttribute/index";
|
|
40
|
+
import type { uploadOption } from "../../types/uploadOption";
|
|
41
|
+
type componentType = baseAttribute & uploadAttribute;
|
|
42
|
+
const props = defineProps({
|
|
43
|
+
/**
|
|
44
|
+
* 组件ID
|
|
45
|
+
*/
|
|
46
|
+
componentID: {
|
|
47
|
+
type: [String, Number],
|
|
48
|
+
required: true
|
|
49
|
+
},
|
|
50
|
+
/**
|
|
51
|
+
* 表单数据
|
|
52
|
+
*/
|
|
53
|
+
datas: {
|
|
54
|
+
type: Object as PropType<Record<string, any>>,
|
|
55
|
+
required: true
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* 组件属性
|
|
59
|
+
*/
|
|
60
|
+
componentProps: {
|
|
61
|
+
type: Object as PropType<componentType>,
|
|
62
|
+
required: true
|
|
63
|
+
},
|
|
64
|
+
/**
|
|
65
|
+
* 是否项目说明/答案解析
|
|
66
|
+
*/
|
|
67
|
+
showDescription: {
|
|
68
|
+
type: Boolean,
|
|
69
|
+
default: false
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// 表单类型
|
|
73
|
+
const formType = inject("formType") as ComputedRef<string>;
|
|
74
|
+
const { checkFile } = useUtils();
|
|
75
|
+
const upload = ref<any>();
|
|
76
|
+
// 获取父组件中的变量
|
|
77
|
+
const uploadOptions = inject("uploadOptions") as uploadOption;
|
|
78
|
+
const { datas, componentProps } = toRefs(props);
|
|
79
|
+
const fileList = ref<Record<string, any>[]>(datas.value[props.componentID] ?? []);
|
|
80
|
+
|
|
81
|
+
const { confirm } = useMessage();
|
|
82
|
+
// 因为wd-upload组件的change事件无法正确触发,使用before-remove和before-upload钩子做处理
|
|
83
|
+
const beforeRemove = ({ file, resolve }: any) => {
|
|
84
|
+
confirm("确定删除该文件吗?", "提示", (flag: boolean) => {
|
|
85
|
+
if (flag) {
|
|
86
|
+
datas.value[props.componentID] = datas.value[props.componentID].filter((item: any) => item.name !== file.name);
|
|
87
|
+
}
|
|
88
|
+
resolve(flag);
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
const beforeUpload = ({ files, fileList, resolve }: any) => {
|
|
92
|
+
let errorNumber = 0;
|
|
93
|
+
files.forEach((file: any) => {
|
|
94
|
+
const checkPass = checkFile(file, fileList, componentProps.value.fileSize, componentProps.value.fileSizeUnit, false);
|
|
95
|
+
if (!checkPass) {
|
|
96
|
+
errorNumber++;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
if (errorNumber === 0) {
|
|
100
|
+
if (!datas.value[props.componentID]) {
|
|
101
|
+
datas.value[props.componentID] = [];
|
|
102
|
+
}
|
|
103
|
+
datas.value[props.componentID] = [...datas.value[props.componentID], ...files];
|
|
104
|
+
resolve(true);
|
|
105
|
+
}
|
|
106
|
+
resolve(false);
|
|
107
|
+
};
|
|
108
|
+
</script>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* FilePath : \src\components\dynamicForm\components\advanced\uploadImage.vue
|
|
3
|
+
* Author : 苏军志
|
|
4
|
+
* Date : 2024-07-16 10:29
|
|
5
|
+
* LastEditors : 苏军志
|
|
6
|
+
* LastEditTime : 2025-09-03 17:01
|
|
7
|
+
* Description : 图片上传(uploadImage)组件渲染器
|
|
8
|
+
* CodeIterationRecord:
|
|
9
|
+
-->
|
|
10
|
+
<template>
|
|
11
|
+
<div class="zhy-form-component-upload-image">
|
|
12
|
+
<wd-upload
|
|
13
|
+
ref="upload"
|
|
14
|
+
:file-list="fileList"
|
|
15
|
+
:action="uploadOptions.serverApi"
|
|
16
|
+
:multiple="componentProps.multiple"
|
|
17
|
+
:limit="componentProps.fileLimit"
|
|
18
|
+
:auto-upload="uploadOptions?.autoUpload"
|
|
19
|
+
:extension="componentProps.fileType?.split(',')"
|
|
20
|
+
accept="media"
|
|
21
|
+
:before-upload="beforeUpload"
|
|
22
|
+
:before-remove="beforeRemove"
|
|
23
|
+
>
|
|
24
|
+
</wd-upload>
|
|
25
|
+
<view
|
|
26
|
+
v-if="showDescription && (formTypeEnum.Examination || componentProps.description)"
|
|
27
|
+
class="dynamic-form-item-description show-description"
|
|
28
|
+
>
|
|
29
|
+
<wd-icon custom-class="description-tip" class-prefix="iconfont zhy" name="tip" color="#ff0000" />
|
|
30
|
+
{{ `${formType === formTypeEnum.Form ? "项目说明" : "答案解析"}:${componentProps.description || "无"}` }}
|
|
31
|
+
</view>
|
|
32
|
+
<!-- message消息的挂载点。 -->
|
|
33
|
+
<wd-message-box></wd-message-box>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import { formTypeEnum } from "../../types/enum";
|
|
38
|
+
import type { baseAttribute, uploadAttribute } from "../../types/componentAttribute/index";
|
|
39
|
+
import type { uploadOption } from "../../types/uploadOption";
|
|
40
|
+
type componentType = baseAttribute & uploadAttribute;
|
|
41
|
+
const props = defineProps({
|
|
42
|
+
/**
|
|
43
|
+
* 组件ID
|
|
44
|
+
*/
|
|
45
|
+
componentID: {
|
|
46
|
+
type: [String, Number],
|
|
47
|
+
required: true
|
|
48
|
+
},
|
|
49
|
+
/**
|
|
50
|
+
* 表单数据
|
|
51
|
+
*/
|
|
52
|
+
datas: {
|
|
53
|
+
type: Object as PropType<Record<string, any>>,
|
|
54
|
+
required: true
|
|
55
|
+
},
|
|
56
|
+
/**
|
|
57
|
+
* 组件属性
|
|
58
|
+
*/
|
|
59
|
+
componentProps: {
|
|
60
|
+
type: Object as PropType<componentType>,
|
|
61
|
+
required: true
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* 是否项目说明/答案解析
|
|
65
|
+
*/
|
|
66
|
+
showDescription: {
|
|
67
|
+
type: Boolean,
|
|
68
|
+
default: false
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// 表单类型
|
|
72
|
+
const formType = inject("formType") as ComputedRef<string>;
|
|
73
|
+
const { checkFile } = useUtils();
|
|
74
|
+
const upload = ref<any>();
|
|
75
|
+
// 获取父组件中的变量
|
|
76
|
+
const uploadOptions = inject("uploadOptions") as uploadOption;
|
|
77
|
+
const { datas, componentProps } = toRefs(props);
|
|
78
|
+
const fileList = ref<Record<string, any>[]>(datas.value[props.componentID] ?? []);
|
|
79
|
+
|
|
80
|
+
const { confirm } = useMessage();
|
|
81
|
+
// 因为wd-upload组件的change事件无法正确触发,使用before-remove和before-upload钩子做处理
|
|
82
|
+
const beforeRemove = ({ file, resolve }: any) => {
|
|
83
|
+
confirm("确定删除该文件吗?", "提示", (flag: boolean) => {
|
|
84
|
+
if (flag) {
|
|
85
|
+
datas.value[props.componentID] = datas.value[props.componentID].filter((item: any) => item.name !== file.name);
|
|
86
|
+
}
|
|
87
|
+
resolve(flag);
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const beforeUpload = ({ files, fileList, resolve }: any) => {
|
|
91
|
+
let errorNumber = 0;
|
|
92
|
+
files.forEach((file: any) => {
|
|
93
|
+
const checkPass = checkFile(file, fileList, componentProps.value.fileSize, componentProps.value.fileSizeUnit, false);
|
|
94
|
+
if (!checkPass) {
|
|
95
|
+
errorNumber++;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
if (errorNumber === 0) {
|
|
99
|
+
if (!datas.value[props.componentID]) {
|
|
100
|
+
datas.value[props.componentID] = [];
|
|
101
|
+
}
|
|
102
|
+
datas.value[props.componentID] = [...datas.value[props.componentID], ...files];
|
|
103
|
+
resolve(true);
|
|
104
|
+
}
|
|
105
|
+
resolve(false);
|
|
106
|
+
};
|
|
107
|
+
</script>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* FilePath : \src\components\dynamicForm\components\answerSheetPopup\answerSheetItem.vue
|
|
3
|
+
* Author : 苏军志
|
|
4
|
+
* Date : 2025-05-15 09:30
|
|
5
|
+
* LastEditors : 苏军志
|
|
6
|
+
* LastEditTime : 2025-09-04 19:27
|
|
7
|
+
* Description : 答题卡项
|
|
8
|
+
* CodeIterationRecord:
|
|
9
|
+
-->
|
|
10
|
+
<template>
|
|
11
|
+
<view class="answer-sheet-item w-full">
|
|
12
|
+
<template v-for="(answerSheet, index) in answerSheetData" :key="index">
|
|
13
|
+
<view class="aline-left pl-10 my-15 b-1-#cccccc first:mt-5" v-if="answerSheet.children?.length">
|
|
14
|
+
<view class="w-full fs-16 mt-4">{{ answerSheet.name }}</view>
|
|
15
|
+
<template v-for="(item, index) in answerSheet.children" :key="index">
|
|
16
|
+
<answer-sheet-item
|
|
17
|
+
v-if="item.children?.length"
|
|
18
|
+
:answerSheetData="item.children"
|
|
19
|
+
:tagType="tagType"
|
|
20
|
+
@showItem="showItem"
|
|
21
|
+
></answer-sheet-item>
|
|
22
|
+
<template v-else>
|
|
23
|
+
<wd-tag
|
|
24
|
+
v-if="tagType[item.status]?.value"
|
|
25
|
+
custom-class="my-10! mx-5! py-2! px-18! fs-18!"
|
|
26
|
+
:type="tagType[item.status]?.value"
|
|
27
|
+
@click="showItem(item.id)"
|
|
28
|
+
>
|
|
29
|
+
{{ item.name }}
|
|
30
|
+
</wd-tag>
|
|
31
|
+
<wd-tag v-else custom-class="my-10! mx-5! py-2! px-18! fs-18!" @click="showItem(item.id)">
|
|
32
|
+
{{ item.name }}
|
|
33
|
+
</wd-tag>
|
|
34
|
+
</template>
|
|
35
|
+
</template>
|
|
36
|
+
</view>
|
|
37
|
+
</template>
|
|
38
|
+
</view>
|
|
39
|
+
</template>
|
|
40
|
+
<script setup lang="ts">
|
|
41
|
+
defineProps({
|
|
42
|
+
/**
|
|
43
|
+
* 组件
|
|
44
|
+
*/
|
|
45
|
+
answerSheetData: {
|
|
46
|
+
type: Array as PropType<Record<string, any>[]>,
|
|
47
|
+
required: true
|
|
48
|
+
},
|
|
49
|
+
tagType: {
|
|
50
|
+
type: Object as PropType<Record<number, any>>,
|
|
51
|
+
required: true
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const emits = defineEmits(["showItem"]);
|
|
55
|
+
const showItem = (id: string) => {
|
|
56
|
+
emits("showItem", id);
|
|
57
|
+
};
|
|
58
|
+
</script>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* FilePath : \src\components\dynamicForm\components\answerSheetPopup\index.vue
|
|
3
|
+
* Author : 苏军志
|
|
4
|
+
* Date : 2025-05-15 10:11
|
|
5
|
+
* LastEditors : 苏军志
|
|
6
|
+
* LastEditTime : 2025-09-04 19:47
|
|
7
|
+
* Description : 答题卡
|
|
8
|
+
* CodeIterationRecord:
|
|
9
|
+
-->
|
|
10
|
+
<template>
|
|
11
|
+
<wd-action-sheet custom-class="zhy answer-sheet-popup" v-model="show" title="答题卡">
|
|
12
|
+
<view class="y-aline-start overflow-y-auto h-[calc(100%-50px)]">
|
|
13
|
+
<view class="my-10 ml-20 mr-0 fs-16 fw-bold">
|
|
14
|
+
<text>说明:</text>
|
|
15
|
+
<template v-for="(type, index) in Object.values(tagType)" :key="index">
|
|
16
|
+
<wd-tag v-if="type.value" custom-class="zhy mr-10! py-4! px-10!" :type="type.value"> {{ type.label }} </wd-tag>
|
|
17
|
+
<wd-tag v-else custom-class="zhy mr-10! py-4! px-10!"> {{ type.label }} </wd-tag>
|
|
18
|
+
</template>
|
|
19
|
+
</view>
|
|
20
|
+
<view class="flex-auto h-full overflow-y-auto px-10 box-border">
|
|
21
|
+
<answer-sheet-item :answerSheetData="answerSheetData" :tagType="tagType" @showItem="showItem"></answer-sheet-item>
|
|
22
|
+
</view>
|
|
23
|
+
</view>
|
|
24
|
+
</wd-action-sheet>
|
|
25
|
+
</template>
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
import type { dynamicFormData, formAttribute } from "../../types/formAttribute";
|
|
28
|
+
import answerSheetItem from "./answerSheetItem.vue";
|
|
29
|
+
const props = defineProps({
|
|
30
|
+
/**
|
|
31
|
+
* @description: 表单数据,包含表单属性,组件集合、初始数据
|
|
32
|
+
*/
|
|
33
|
+
formData: {
|
|
34
|
+
type: Object as PropType<dynamicFormData<formAttribute>>,
|
|
35
|
+
required: true
|
|
36
|
+
},
|
|
37
|
+
/**
|
|
38
|
+
* @description: 是否显示正确与否
|
|
39
|
+
*/
|
|
40
|
+
showCorrectOrNot: {
|
|
41
|
+
type: Boolean,
|
|
42
|
+
default: true
|
|
43
|
+
},
|
|
44
|
+
/**
|
|
45
|
+
* @description: 弹出框高度
|
|
46
|
+
*/
|
|
47
|
+
height: {
|
|
48
|
+
type: String,
|
|
49
|
+
default: "360px"
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const show = ref<boolean>(true);
|
|
53
|
+
const answerSheetData = ref<Record<string, any>[]>([]);
|
|
54
|
+
const tagType = ref<Record<number, any>>({});
|
|
55
|
+
onMounted(() => {
|
|
56
|
+
tagType.value = {
|
|
57
|
+
1: { value: "", label: "未作答" },
|
|
58
|
+
2: { value: "success", label: "已作答" }
|
|
59
|
+
};
|
|
60
|
+
if (props.showCorrectOrNot) {
|
|
61
|
+
tagType.value[2].label = "正确";
|
|
62
|
+
tagType.value[3] = { value: "danger", label: "错误" };
|
|
63
|
+
}
|
|
64
|
+
answerSheetData.value = getAnswerSheetData(props.formData.components);
|
|
65
|
+
});
|
|
66
|
+
import { componentTypeEnum } from "../../types/enum";
|
|
67
|
+
/**
|
|
68
|
+
* @description: 获取答题卡数据
|
|
69
|
+
* @param components
|
|
70
|
+
* @param any
|
|
71
|
+
* @return
|
|
72
|
+
*/
|
|
73
|
+
const getAnswerSheetData = (components: Record<string, any>[]) => {
|
|
74
|
+
let result: Record<string, any>[] = [];
|
|
75
|
+
let index = 1;
|
|
76
|
+
components.forEach((component) => {
|
|
77
|
+
if (component.type === componentTypeEnum.LABEL) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
let status: number = 0;
|
|
81
|
+
if (props.showCorrectOrNot) {
|
|
82
|
+
status = !props.formData.datas[component.id] ? 1 : component.props.examinationStatus;
|
|
83
|
+
} else {
|
|
84
|
+
status = !props.formData.datas[component.id] ? 1 : 2;
|
|
85
|
+
}
|
|
86
|
+
const data: Record<string, any> = {
|
|
87
|
+
id: component.id,
|
|
88
|
+
name: index,
|
|
89
|
+
status
|
|
90
|
+
};
|
|
91
|
+
// 递归处理子组件
|
|
92
|
+
if (component.isLayout && component.children?.length) {
|
|
93
|
+
data.name = component.props.label.replace(/([^)]*)/g, "");
|
|
94
|
+
data.children = getAnswerSheetData(component.children);
|
|
95
|
+
}
|
|
96
|
+
result.push(data);
|
|
97
|
+
index++;
|
|
98
|
+
});
|
|
99
|
+
return result;
|
|
100
|
+
};
|
|
101
|
+
const emits = defineEmits(["showItem"]);
|
|
102
|
+
const showItem = (id: string) => {
|
|
103
|
+
show.value = false;
|
|
104
|
+
emits("showItem", id);
|
|
105
|
+
};
|
|
106
|
+
</script>
|
|
107
|
+
<style lang="scss">
|
|
108
|
+
:deep(.zhy.answer-sheet-popup) {
|
|
109
|
+
height: v-bind(height);
|
|
110
|
+
}
|
|
111
|
+
</style>
|