el-form-renderer-vue3 1.0.0

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 (50) hide show
  1. package/.vite/deps_temp_ce3ed5bf/package.json +3 -0
  2. package/.vscode/extensions.json +3 -0
  3. package/LICENSE +661 -0
  4. package/README.en.md +36 -0
  5. package/README.md +252 -0
  6. package/index.html +13 -0
  7. package/package.json +34 -0
  8. package/public/vite.svg +1 -0
  9. package/src/App.vue +59 -0
  10. package/src/ChildCom.vue +24 -0
  11. package/src/assets/vue.svg +1 -0
  12. package/src/components/femessage/components/render-form-group.vue +48 -0
  13. package/src/components/femessage/components/render-form-item.vue +328 -0
  14. package/src/components/femessage/el-form-renderer.vue +284 -0
  15. package/src/components/femessage/util/CustomComponent.js +28 -0
  16. package/src/components/femessage/util/VNode.js +9 -0
  17. package/src/components/femessage/util/enable-when.js +26 -0
  18. package/src/components/femessage/util/transform-content.js +48 -0
  19. package/src/components/femessage/util/utils.js +127 -0
  20. package/src/el-form-renderer.md +220 -0
  21. package/src/main.js +15 -0
  22. package/src/router/index.js +160 -0
  23. package/src/style.css +89 -0
  24. package/src/view/AboutView.vue +40 -0
  25. package/src/view/HomeView.vue +134 -0
  26. package/src/view/MyInput.vue +46 -0
  27. package/src/view/checkboxGroup.vue +29 -0
  28. package/src/view/content.vue +176 -0
  29. package/src/view/deprecated.vue +37 -0
  30. package/src/view/disabled.vue +104 -0
  31. package/src/view/el.vue +24 -0
  32. package/src/view/format.vue +63 -0
  33. package/src/view/getcomponent.vue +51 -0
  34. package/src/view/getform.vue +50 -0
  35. package/src/view/hidden.vue +51 -0
  36. package/src/view/label.vue +25 -0
  37. package/src/view/next.vue +55 -0
  38. package/src/view/picker.vue +27 -0
  39. package/src/view/radioGroup.vue +38 -0
  40. package/src/view/readonly.vue +144 -0
  41. package/src/view/remote.vue +115 -0
  42. package/src/view/rules.vue +46 -0
  43. package/src/view/rulesPlus.vue +34 -0
  44. package/src/view/setoptios.vue +50 -0
  45. package/src/view/slot.vue +37 -0
  46. package/src/view/testAttrs.vue +17 -0
  47. package/src/view/update.vue +64 -0
  48. package/src/view/vmodel.vue +137 -0
  49. package/src/your-component.vue +55 -0
  50. package/vite.config.js +7 -0
@@ -0,0 +1,328 @@
1
+ <template>
2
+ <div>
3
+ <!-- 绑定显示,校验匹配规则字段,label,rules校验规则,attrs(原生属性), -->
4
+ <el-form-item
5
+ v-if="_show"
6
+ :prop="prop"
7
+ :label="typeof data.label === 'string' ? data.label : ''"
8
+ :rules="!readonly && Array.isArray(data.rules) ? data.rules : undefined"
9
+ v-bind="data.attrs"
10
+ class="render-form-item"
11
+ >
12
+ <!-- label插槽 -->
13
+ <template #label>
14
+ <v-node
15
+ v-if="typeof data.label !== 'string'"
16
+ slot="label"
17
+ :content="data.label"
18
+ />
19
+ </template>
20
+ <!-- 处理之只读input select -->
21
+ <template v-if="readonly && hasReadonlyContent">
22
+ <el-input
23
+ v-if="data.type === 'input'"
24
+ v-bind="componentProps"
25
+ :modelValue="itemValue"
26
+ readonly
27
+ v-on="listeners"
28
+ />
29
+
30
+ <div v-else-if="data.type === 'select'">
31
+ {{ multipleValue }}
32
+ </div>
33
+ </template>
34
+ <!-- 处理 date-picker,cascader,动态渲染不显示文字 bug-->
35
+ <component
36
+ v-else-if="data.type === 'date-picker' || data.type === 'cascader'"
37
+ ref="customComponent"
38
+ v-bind:is="data.component || `el-${data.type || 'input'}`"
39
+ v-bind="componentProps"
40
+ :modelValue="itemValue"
41
+ :disabled="disabled || componentProps.disabled || readonly"
42
+ v-on="listeners"
43
+ :loading="loading"
44
+ :remote-method="data.remoteMethod || componentProps.remoteMethod || remoteMethod"
45
+ >
46
+ </component>
47
+ <!-- 绑定 模板引用 动态组件 props value值 是否禁用 事件 lodding 远端搜索方法 -->
48
+ <component
49
+ v-else
50
+ ref="customComponent"
51
+ v-bind:is="data.component || `el-${data.type || 'input'}`"
52
+ v-bind="componentProps"
53
+ :modelValue="itemValue"
54
+ :disabled="disabled || componentProps.disabled || readonly"
55
+ v-on="listeners"
56
+ :loading="loading"
57
+ :remote-method="data.remoteMethod || componentProps.remoteMethod || remoteMethod"
58
+ >
59
+ <!-- 插槽处理 选项-->
60
+ <template v-for="(opt, index) in options">
61
+ <el-option
62
+ v-if="data.type === 'select'"
63
+ :key="optionKey(opt) || index"
64
+ v-bind="opt"
65
+ />
66
+ <el-checkbox-button
67
+ v-if="data.type === 'checkbox-group' && data.style === 'button'"
68
+ :key="opt.value"
69
+ v-bind="opt"
70
+ :label="'value' in opt ? opt.value : opt.label"
71
+ >
72
+ {{ opt.label }}
73
+ </el-checkbox-button>
74
+ <el-checkbox
75
+ v-else-if="data.type === 'checkbox-group' && data.style !== 'button'"
76
+ :key="opt.value"
77
+ v-bind="opt"
78
+ :label="'value' in opt ? opt.value : opt.label"
79
+ >
80
+ {{ opt.label }}
81
+ </el-checkbox>
82
+ <el-radio-button
83
+ v-else-if="data.type === 'radio-group' && data.style === 'button'"
84
+ :key="opt.label"
85
+ v-bind="opt"
86
+ :label="'value' in opt ? opt.value : opt.label"
87
+ >{{ opt.label }}</el-radio-button
88
+ >
89
+ <el-radio
90
+ v-else-if="data.type === 'radio-group' && data.style !== 'button'"
91
+ :key="opt.label"
92
+ v-bind="opt"
93
+ :label="'value' in opt ? opt.value : opt.label"
94
+ >{{ opt.label }}</el-radio
95
+ >
96
+ </template>
97
+ </component>
98
+ </el-form-item>
99
+ </div>
100
+ </template>
101
+
102
+ <script setup>
103
+ import { computed, reactive, inject, nextTick, ref, watch } from "vue";
104
+ import { noop } from "../util/utils";
105
+ import getEnableWhenStatus from "../util/enable-when";
106
+ import _includes from "lodash.includes";
107
+ import _topairs from "lodash.topairs";
108
+ import _frompairs from "lodash.frompairs";
109
+ import _get from "lodash.get";
110
+ // 改用 动态组件 方便获取 ref
111
+ // import CustomComponent from "../util/CustomComponent";
112
+ import VNode from "../util/VNode";
113
+ import axios from "axios";
114
+ let customComponent = ref();
115
+
116
+ let props = defineProps({
117
+ data: Object,
118
+ prop: {
119
+ type: String,
120
+ default(rawProps) {
121
+ return rawProps.data.id;
122
+ },
123
+ },
124
+
125
+ itemValue: {},
126
+ value: Object,
127
+ disabled: Boolean,
128
+ readonly: Boolean,
129
+ options: Array,
130
+ });
131
+
132
+ // 更新表单方法
133
+ const emit = defineEmits(["updateValue"]);
134
+ let propsInner = reactive({});
135
+
136
+ const loading = ref(false);
137
+
138
+ let dataRef = ref(props.data);
139
+ // 注入一个由祖先组件或整个应用 (通过 app.provide()) 提供的值。
140
+ // 父组件提供 element ui的方法
141
+ let methods = inject("methods");
142
+ // 父组件提供的 更新 options的方法
143
+ let setOptions = inject("setOptions");
144
+ // 是否校验
145
+ const isBlurTrigger =
146
+ props.data.rules &&
147
+ props.data.rules.some((rule) => {
148
+ return rule.required && rule.trigger === "blur";
149
+ });
150
+
151
+ // 计算props
152
+ const componentProps = computed(() => ({ ...props.data.el, ...propsInner }));
153
+ // 计算是否为只读 input select
154
+ const hasReadonlyContent = computed(() => ["input", "select"].includes(props.data.type));
155
+ //执行传入的hidden
156
+ const hiddenStatus = computed(() => {
157
+ const hidden = props.data.hidden || (() => false);
158
+ return hidden(props.value, props.data);
159
+ });
160
+ // 弃用
161
+ const enableWhenStatus = computed(() =>
162
+ getEnableWhenStatus(props.data.enableWhen, props.data.value)
163
+ );
164
+ // 处理组件的显示与隐藏
165
+ const _show = computed(() => !hiddenStatus.value && enableWhenStatus.value);
166
+ // 处理事件监听 vue3实现 v-model props:modelValue event:update:modelValue
167
+ const listeners = computed(() => {
168
+ const data = props.data;
169
+ const id = data.id;
170
+ const atChange = data.atChange || noop;
171
+ const on = data.on || {};
172
+ const originOnInput = on.input || noop;
173
+ const originOnChange = on.change || noop;
174
+ const trim = data.trim !== undefined ? data.trim : true;
175
+
176
+ let updateForm = inject("updateForm");
177
+ return {
178
+ ..._frompairs(
179
+ _topairs(on).map(([eName, handler]) => [
180
+ eName,
181
+ (...args) => handler(args, updateForm),
182
+ ])
183
+ ),
184
+ // 手动更新表单数据
185
+ "update:modelValue": (value, ...rest) => {
186
+ if (typeof value === "string" && trim) value = value.trim();
187
+ emit("updateValue", { id, value });
188
+ // FIXME: rules 的 trigger 只写了 blur,依然会在 change 的时候触发校验!
189
+ triggerValidate(id);
190
+ },
191
+ };
192
+ });
193
+ // el-select 显示对应的 label;(只读)
194
+ const multipleValue = computed(() => {
195
+ const multipleSelectValue =
196
+ _get(props.data, "el.multiple") && Array.isArray(props.itemValue)
197
+ ? props.itemValue
198
+ : [props.itemValue];
199
+ return multipleSelectValue
200
+ .map((val) => (props.options.find((op) => op.value === val) || {}).label)
201
+ .join();
202
+ });
203
+ // 处理服务器获取 options
204
+ const makingRequest = (remoteConfig, query) => {
205
+ const isOptionsCase =
206
+ ["select", "checkbox-group", "radio-group"].indexOf(props.data.type) > -1;
207
+ const {
208
+ request, //request:用于发起远程请求的函数
209
+ prop = "options", // 处理响应数据时的属性名称,默认为 "options" 默认处理 el-cascader 的情况
210
+ dataPath = "", //用于指定响应数据中的路径,默认为空字符串
211
+ onResponse = (resp) => {
212
+ //响应成功时的回调函数,默认为一个函数,用于处理响应数据。
213
+ if (dataPath) resp = _get(resp, dataPath);
214
+ if (isOptionsCase) {
215
+ return resp?.map((item) => ({
216
+ label: item[label],
217
+ value: item[value],
218
+ }));
219
+ } else {
220
+ return resp;
221
+ }
222
+ },
223
+ onError = (error) => {
224
+ //发生错误时的回调函数,默认为一个函数,用于打印错误信息并将 loading.value 设置为 false
225
+ console.error(error.message);
226
+ loading.value = false;
227
+ },
228
+ label = "label", //用于选项中的标签属性,默认为 "label"。
229
+ value = "value", //用于选项中的值属性,默认为 "value"
230
+ } = remoteConfig;
231
+ //,表示开始加载数据。
232
+ loading.value = true;
233
+ //Promise.resolve(request(query)):这里使用 Promise.resolve 包装了 request(query),以确保始终返回一个 Promise 对象。
234
+ Promise.resolve(request(query))
235
+ // 当请求成功时执行 onResponse 函数,当请求失败时执行 onError 函数。
236
+ .then(onResponse, onError)
237
+ // .then((resp) => { ... }):在请求完成后,无论成功或失败,都会执行这个 .then 块。在这里,根据 isOptionsCase 的值,对响应数据 resp 进行不同的处理。
238
+ .then((resp) => {
239
+ // 如果 isOptionsCase 为 true,则将响应数据中的每个元素映射为包含 "label" 和 "value" 属性的对象,并将结果传递给 setOptions 函数(如果存在)。
240
+ if (isOptionsCase) {
241
+ setOptions && setOptions(props.prop, resp);
242
+ } else {
243
+ // 如果 isOptionsCase 为 false,则将响应数据存储在 propsInner 中,属性名为 prop。
244
+ propsInner = Object.assign(propsInner, { [prop]: resp });
245
+ }
246
+ // ,表示加载完成。
247
+ loading.value = false;
248
+ });
249
+ };
250
+ // 监听 是否有 data
251
+ watch(dataRef, (data) => {
252
+ if (!data) {
253
+ throw new Error("data must be an Object.");
254
+ } else if (!data.id) {
255
+ throw new Error("`id` is unvalidated.");
256
+ } else if (!data.type && !data.component) {
257
+ throw new Error("`type` and `component` cannot both be null.");
258
+ }
259
+ });
260
+ // 处理服务器获取options
261
+ watch(
262
+ /**
263
+ * 这里其实用 remote 处理了两件事。有机会是可以拆分的
264
+ * 1. 基本用法,配置 url 后即可从远程获取某个 prop 注入到组件
265
+ * 2. 针对 select、checkbox-group & radio-group 组件,会直接将 resp 作为 options 处理;label & value 也是直接为这个场景而生的
266
+ */
267
+ () => props.data.remote?.request,
268
+ (newValue, oldValue) => {
269
+ // 不应该用 watch data.remote,因为对象引用是同一个 https://cn.vuejs.org/v2/api/#vm-watch (估计当初这样写是为了方便)
270
+ // 现改写成:分开处理 remote.request,remote.url
271
+ // 至于为什么判断新旧值相同则返回,是因为 form 的 content 是响应式的,防止用户直接修改 content 其他内容时,导致 remote.request 重新发请求
272
+
273
+ if (!newValue || typeof newValue !== "function" || newValue === oldValue) return;
274
+
275
+ makingRequest(props.data.remote);
276
+ },
277
+ { immediate: true }
278
+ );
279
+ // 处理服务器获取options
280
+ watch(
281
+ () => props.data.remote?.url,
282
+ (url, oldV) => {
283
+ // 第三个判断条件:防止 url 与 request 同时存在时,发送两次请求
284
+ if (!url || url === oldV || (!oldV && props.data.remote.request)) return;
285
+ const request =
286
+ props.data.remote.request || (() => axios.get(url).then((resp) => resp.data));
287
+ makingRequest(Object.assign({}, props.data.remote, { request }));
288
+ },
289
+ { immediate: true }
290
+ );
291
+
292
+ // 校验表单项目
293
+ const triggerValidate = async (id) => {
294
+ try {
295
+ if (!props.data.rules || !props.data.rules.length) return;
296
+ if (isBlurTrigger) return;
297
+ await nextTick();
298
+
299
+ (await methods) && methods.validateField(id);
300
+ } catch (error) {
301
+ console.log(error);
302
+ }
303
+ };
304
+ // 远端搜索方法
305
+ const remoteMethod = (query) => {
306
+ if (
307
+ _get(props.data, "type") === "select" &&
308
+ _get(props.data, "el.filterable") &&
309
+ _get(props.data, "el.remote")
310
+ ) {
311
+ makingRequest(props.data.remote, query);
312
+ }
313
+ };
314
+ // 初始化 optios key
315
+ const optionKey = (opt) => {
316
+ if (opt.value instanceof Object) {
317
+ if (!props.data.el || !props.data.el.valueKey) {
318
+ return;
319
+ }
320
+
321
+ return opt.value[props.data.el.valueKey];
322
+ } else {
323
+ return opt.value;
324
+ }
325
+ };
326
+ // 暴露 element ui 模版引用
327
+ defineExpose({ customComponent });
328
+ </script>
@@ -0,0 +1,284 @@
1
+ <template>
2
+ <div>
3
+ <el-form ref="myelForm" v-bind="$attrs" :model="value" class="el-form-renderer">
4
+ <template v-for="item in innerContent" :key="item.id">
5
+ <slot :name="`id:${item.id}`" />
6
+ <slot :name="`$id:${item.id}`" />
7
+
8
+ <component
9
+ :is="item.type === GROUP ? RenderFormGroup : RenderFormItem"
10
+ :ref="
11
+ (el) => {
12
+ customComponent[item.id] = el;
13
+ }
14
+ "
15
+ :data="item"
16
+ :value="value"
17
+ :item-value="value[item.id]"
18
+ :disabled="
19
+ disabled ||
20
+ (typeof item.disabled === 'function' ? item.disabled(value) : item.disabled)
21
+ "
22
+ :readonly="readonly || item.readonly"
23
+ :options="options[item.id]"
24
+ @updateValue="updateValue"
25
+ />
26
+ </template>
27
+ <slot />
28
+ </el-form>
29
+ </div>
30
+ </template>
31
+
32
+ <script setup>
33
+ import RenderFormGroup from "./components/render-form-group.vue";
34
+ import RenderFormItem from "./components/render-form-item.vue";
35
+ import {
36
+ reactive,
37
+ computed,
38
+ ref,
39
+ watch,
40
+ onMounted,
41
+ nextTick,
42
+ provide,
43
+ getCurrentInstance,
44
+ } from "vue";
45
+ import transformContent from "./util/transform-content";
46
+ import _set from "lodash.set";
47
+ import _isequal from "lodash.isequal";
48
+ import _clonedeep from "lodash.clonedeep";
49
+ import {
50
+ collect,
51
+ mergeValue,
52
+ transformOutputValue,
53
+ transformInputValue,
54
+ correctValue,
55
+ } from "./util/utils";
56
+ let GROUP = "group";
57
+ /**
58
+ * inputFormat 让整个输入机制复杂了很多。value 有以下输入路径:
59
+ * 1. 传入的 form => inputFormat 处理
60
+ * 2. updateForm => inputFormat 处理
61
+ * 3. 但 content 中的 default 没法经过 inputFormat 处理,因为 inputFormat 要接受整个 value 作为参数
62
+ * 4. 组件内部更新 value,不需要走 inputFormat
63
+ */
64
+ let value = reactive({}); // 表单数据对象
65
+ let options = reactive({});
66
+ let initValue = reactive({});
67
+ let myelForm = ref();
68
+ let methods = {};
69
+ const customComponent = ref([]);
70
+ let emit = defineEmits(["update:FormData"]);
71
+ // 注入 element ui form 方法
72
+ /**
73
+ * 与 element 相同,在 mounted 阶段存储 initValue
74
+ * @see https://github.com/ElemeFE/element/blob/6ec5f8e900ff698cf30e9479d692784af836a108/packages/form/src/form-item.vue#L304
75
+ */
76
+ onMounted(async () => {
77
+ initValue = _clonedeep(value);
78
+ await nextTick();
79
+ // 检查 myelForm 是否已经初始化
80
+ if (myelForm && myelForm.value) {
81
+ Object.keys(myelForm.value).forEach((item) => {
82
+ // 检查属性是否存在于 methods 对象中
83
+ if (myelForm.value[item] && !(item in methods)) {
84
+ methods[item] = myelForm.value[item];
85
+ }
86
+ });
87
+ }
88
+ /**
89
+ * 有些组件会 created 阶段更新初始值为合法值,这会触发 validate。目前已知的情况有:
90
+ * - el-select 开启 multiple 时,会更新初始值 undefined 为 []
91
+ * @hack
92
+ */
93
+ methods.clearValidate();
94
+ });
95
+
96
+ let props = defineProps({
97
+ //表单项
98
+ content: {
99
+ type: Array,
100
+ required: true,
101
+ },
102
+ // 禁用
103
+ disabled: {
104
+ type: [Boolean, Function],
105
+ default: false,
106
+ },
107
+ //只读
108
+ readonly: {
109
+ type: Boolean,
110
+ default: false,
111
+ },
112
+ /**
113
+ * v-model 的值。传入后会优先使用
114
+ */
115
+ FormData: {
116
+ type: Object,
117
+ default: undefined,
118
+ },
119
+ });
120
+ //兼容处理
121
+ let innerContent = computed(() => transformContent(props.content));
122
+ // 初始化默认值
123
+ let setValueFromModel = () => {
124
+ if (innerContent.length) return;
125
+ /**
126
+ * 没使用 v-model 时才从 default 采集数据
127
+ * default 值没法考虑 inputFormat
128
+ * 参考 value-format.md 的案例。那种情况下,default 该传什么?
129
+ */
130
+ let newValue = props.FormData
131
+ ? transformInputValue(props.FormData, innerContent.value)
132
+ : collect(innerContent.value, "default");
133
+ correctValue(newValue, innerContent.value);
134
+ if (!_isequal(value, newValue)) value = Object.assign(value, newValue);
135
+ };
136
+ // v-model初始化默认数据
137
+ watch(
138
+ () => props.FormData,
139
+ (newForm) => {
140
+ if (!newForm) return;
141
+ setValueFromModel();
142
+ },
143
+ { immediate: true, deep: true }
144
+ );
145
+ // 初始化默认数据
146
+ watch(
147
+ innerContent,
148
+ (newContent) => {
149
+ try {
150
+ if (!newContent) return;
151
+
152
+ // 如果 content 没有变动 remote 的部分,这里需要保留之前 remote 注入的 options
153
+ Object.assign(options, collect(newContent, "options"));
154
+ setValueFromModel();
155
+ } catch (error) {
156
+ console.log(error);
157
+ }
158
+ },
159
+ { immediate: true }
160
+ );
161
+
162
+ // v-model 传递值
163
+ watch(value, (newValue, oldValue) => {
164
+ try {
165
+ if (!newValue) return;
166
+ if (props.FormData) {
167
+ let data = Object.assign(
168
+ props.FormData,
169
+ transformOutputValue(newValue, innerContent)
170
+ );
171
+ emit("update:FormData", data);
172
+ }
173
+ } catch (error) {
174
+ console.log(error, "-----");
175
+ }
176
+ // deep: true, // updateValue 是全量更新,所以不用
177
+ });
178
+
179
+ /**
180
+ * 更新表单数据
181
+ * @param {String} options.id 表单ID
182
+ * @param {All} options.value 表单数据
183
+ */
184
+ let updateValue = ({ id, value: v }) => {
185
+ value[id] = v;
186
+ };
187
+ /**
188
+ * 重置表单为初始值
189
+ *
190
+ * @public
191
+ */
192
+ let resetFields = async () => {
193
+ /**
194
+ * 之所以不用 el-form 的 resetFields 机制,有以下原因:
195
+ * - el-form 的 resetFields 无视 el-form-renderer 的自定义组件
196
+ * - el-form 的 resetFields 不会触发 input & change 事件,无法监听
197
+ * - bug1: https://github.com/FEMessage/el-data-table/issues/176#issuecomment-587280825
198
+ * - bug2:
199
+ * 0. 建议先在监听器 watch.value 里 console.log(v.name, oldV.name)
200
+ * 1. 打开 basic 示例
201
+ * 2. 在 label 为 name 的输入框里输入 1,此时 log:'1' ''
202
+ * 3. 点击 reset 按钮,此时 log 两条数据: '1' '1', '' ''
203
+ * 4. 因为 _isequal(v, oldV),所以没有触发 v-model 更新
204
+ */
205
+ value = _clonedeep(initValue);
206
+ await nextTick();
207
+ methods.clearValidate();
208
+ };
209
+ /**
210
+ * 当 strict 为 true 时,只返回设置的表单项的值, 过滤掉冗余字段, 更多请看 update-form 示例
211
+ * @param {{strict: Boolean}} 默认 false
212
+ * @return {object} key is item's id, value is item's value
213
+ * @public
214
+ */
215
+ let getFormValue = ({ strict = false } = {}) => {
216
+ return transformOutputValue(value, innerContent, { strict });
217
+ };
218
+ /**
219
+ * update form values
220
+ * @param {object} newValue - key is item's id, value is the new value
221
+ * @public
222
+ */
223
+ let updateForm = (newValue) => {
224
+ newValue = transformInputValue(newValue, innerContent);
225
+ mergeValue(value, newValue, innerContent);
226
+ };
227
+ /**
228
+ * update select options
229
+ * @param {string} id<br>
230
+ * @param {array} options
231
+ * @public
232
+ */
233
+ let setOptions = (id, O) => {
234
+ _set(options, id, O);
235
+ options = Object.assign(options); // 设置之前不存在的 options 时需要重新设置响应式更新
236
+ };
237
+
238
+ /**
239
+ * get custom component
240
+ * @param {string} id<br>
241
+ * @public
242
+ */
243
+ const getComponentById = (id) => {
244
+ let content = [];
245
+ props.content.forEach((item) => {
246
+ if (item.type === GROUP) {
247
+ const items = item.items.map((formItem) => {
248
+ formItem.groupId = item.id;
249
+ return formItem;
250
+ });
251
+ content.push(...items);
252
+ } else {
253
+ content.push(item);
254
+ }
255
+ });
256
+ const itemContent = content.find((item) => item.id === id);
257
+ if (!itemContent) {
258
+ return undefined;
259
+ }
260
+ if (!itemContent.groupId) {
261
+ return customComponent.value[id].customComponent;
262
+ } else {
263
+ const componentRef = customComponent.value[itemContent.groupId].customComponent;
264
+ return componentRef[`formItem-${id}`].customComponent;
265
+ }
266
+ };
267
+ provide("methods", methods);
268
+ provide("updateForm", updateForm);
269
+ provide("setOptions", setOptions);
270
+ defineExpose({
271
+ updateValue,
272
+ resetFields,
273
+ getFormValue,
274
+ updateForm,
275
+ setOptions,
276
+ methods,
277
+ getComponentById,
278
+ });
279
+ </script>
280
+ <script>
281
+ export default {
282
+ name: "ElFormRenderer",
283
+ };
284
+ </script>
@@ -0,0 +1,28 @@
1
+ import { h, resolveComponent } from "vue";
2
+
3
+ // Vue3 中函数式组件需要提供一个渲染函数
4
+ const CustomComponent = (props, context) => {
5
+ let dom =
6
+ typeof props.component == "string"
7
+ ? resolveComponent(hyphenToPascal(props.component))
8
+ : "Input";
9
+
10
+ // 返回一个渲染函数,可以使用 h 函数创建虚拟节点
11
+ return h(dom, props, context.slots);
12
+ };
13
+
14
+ function hyphenToPascal(str) {
15
+ let arr = str.split("-");
16
+
17
+ let resStr = arr.reduce(function (prev, cur) {
18
+ let str = prev + cur.slice(0, 1).toUpperCase() + cur.slice(1);
19
+ return str;
20
+ });
21
+
22
+ // 转小驼峰这一行不需要
23
+ resStr = resStr.slice(0, 1).toUpperCase() + resStr.slice(1);
24
+
25
+ return resStr;
26
+ }
27
+
28
+ export default CustomComponent;
@@ -0,0 +1,9 @@
1
+
2
+
3
+ // Vue3 中函数式组件需要提供一个渲染函数
4
+ const VNode = (props, content) => {
5
+
6
+ return (props.content)
7
+ };
8
+
9
+ export default VNode;
@@ -0,0 +1,26 @@
1
+ import _get from "lodash.get";
2
+ import _has from "lodash.has";
3
+
4
+ /**
5
+ * 处理 enableWhen
6
+ *
7
+ * 与条件: 简单依赖关系存在2种情况:简单对象 || 字符串
8
+ * 或条件: 即使用 [] 包裹所有与条件 enableWhen: [{ a: 1 }, { a: 2 }]
9
+ */
10
+ export default function getEnableWhenStatus(enableWhen, value) {
11
+ if (!enableWhen) return true;
12
+ // 处理一个与条件
13
+ const handleCondition = (condition) => {
14
+ // 简单字符串(ID), 只要有值即为true
15
+ if (typeof condition === "string") return _has(value, condition);
16
+ // 简单对象判断: 是否所有依赖条件都通过
17
+ return Object.keys(condition).every((path) => {
18
+ const v = _get(value, path);
19
+ return v !== undefined && v === condition[path];
20
+ });
21
+ };
22
+
23
+ return Array.isArray(enableWhen)
24
+ ? enableWhen.some(handleCondition)
25
+ : handleCondition(enableWhen);
26
+ }