You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
370 lines
8.4 KiB
370 lines
8.4 KiB
<template>
|
|
<ElForm
|
|
:style="getStyle()"
|
|
:model="modelFormData"
|
|
:label-width="labelWidth"
|
|
class="FormConfig"
|
|
ref="ElFormRef"
|
|
size="mini"
|
|
>
|
|
<template v-for="(item, index) in formList">
|
|
<ElFormItem
|
|
class="formItem"
|
|
:rules="getRules(item)"
|
|
v-if="formItemVisible(item)"
|
|
:key="`${item.key}|${index}`"
|
|
:label="item.label"
|
|
:style="gridStyle(item, index)"
|
|
:prop="item.type !== 'MultipleLabelItem' ? item.key : void 0"
|
|
:required="item.required"
|
|
>
|
|
<slot :name="item.key" :data="item" :formData="modelFormData">
|
|
<ProxyCom
|
|
:value="getValue(item)"
|
|
:item="item"
|
|
@update:value="(data) => updateValue(item, data)"
|
|
/>
|
|
<!-- <component :is="getComponent(item.type)" v-bind="getBindData(item)" v-model="modelFormData[item.key]"
|
|
v-on="resolveListeners(item.ons)" /> -->
|
|
</slot>
|
|
</ElFormItem>
|
|
</template>
|
|
</ElForm>
|
|
</template>
|
|
|
|
<script>
|
|
import { resolveName, defaultComponentOptions, RegexpMap } from "./utils/index";
|
|
import { reduceDefaultValue } from "./utils/defaultValue";
|
|
import { set as pathSet, get as pathGet, cloneDeep } from "lodash";
|
|
|
|
import ProxyCom from "./Proxy.vue";
|
|
|
|
const files = require.context(
|
|
"./components",
|
|
true,
|
|
/^.\/[^/]+\/index\.vue$|^.\/[^/]+.vue$/
|
|
);
|
|
|
|
const components = files.keys().reduce(
|
|
(prev, key) => {
|
|
prev[key.match(/[^./]+/g)[0]] = files(key).default;
|
|
|
|
return prev;
|
|
},
|
|
{
|
|
Empty: "div",
|
|
}
|
|
);
|
|
|
|
export default {
|
|
name: "FormConfig",
|
|
components: {
|
|
ProxyCom,
|
|
},
|
|
provide() {
|
|
return {
|
|
getComponent: this.getComponent,
|
|
getBindData: this.getBindData,
|
|
getFormData: () => this.modelFormData,
|
|
getValue: this.getValue,
|
|
updateValue: this.updateValue,
|
|
parent: this,
|
|
getRules: this.getRules,
|
|
};
|
|
},
|
|
props: {
|
|
/**
|
|
* {
|
|
* label: String;
|
|
* key: String;
|
|
* type: 'input' | 'timePicker',
|
|
* gridArea?: "",
|
|
* gridColumn?: "",
|
|
* gridRow?: "",
|
|
* options?: {}
|
|
* }[]
|
|
*/
|
|
dFormData: {
|
|
type: Object,
|
|
default: () => {},
|
|
},
|
|
/**
|
|
* {
|
|
* label: String;
|
|
* key: String;
|
|
* type: 'input' | 'timePicker',
|
|
* gridArea?: "",
|
|
* gridColumn?: "",
|
|
* gridRow?: "",
|
|
* options?: {}
|
|
* }[]
|
|
*/
|
|
value: Object,
|
|
formList: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
labelWidth: {
|
|
type: String,
|
|
default: "auto",
|
|
},
|
|
rules: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
column: {
|
|
type: [String, Number],
|
|
default: "3",
|
|
},
|
|
},
|
|
model: {
|
|
prop: "value",
|
|
event: "update:value",
|
|
},
|
|
data() {
|
|
return {
|
|
formData: {},
|
|
};
|
|
},
|
|
watch: {
|
|
formList: {
|
|
immediate: true,
|
|
handler() {
|
|
this.reset(true);
|
|
},
|
|
},
|
|
},
|
|
// created() {
|
|
// this.reset(true);
|
|
// },
|
|
computed: {
|
|
modelFormData: {
|
|
get() {
|
|
return this.formData;
|
|
},
|
|
set(data) {
|
|
this.formData = data;
|
|
this.$emit("update:value", this.formData);
|
|
},
|
|
},
|
|
gridStyle() {
|
|
return (item, index) => ({
|
|
gridRow: `span ${item.gridRow || 1}`,
|
|
gridColumn: `span ${
|
|
item.gridColumn || (item.isAlone && this.column) || 1
|
|
}`,
|
|
});
|
|
},
|
|
formItemVisible() {
|
|
return (item) => {
|
|
const result =
|
|
item && item.visible ? item.visible(this.modelFormData) : true;
|
|
|
|
// if (!result) {
|
|
// delete this.formData[item.key];
|
|
// }
|
|
|
|
return result;
|
|
};
|
|
},
|
|
},
|
|
methods: {
|
|
getValue(item) {
|
|
return pathGet(this.formData, item.key);
|
|
},
|
|
updateValue(item, data) {
|
|
// 对于数组的修改无响应式 采用此方法
|
|
this.modelFormData = { ...pathSet(this.modelFormData, item.key, data) };
|
|
},
|
|
reset(isFirst) {
|
|
return (this.modelFormData = reduceDefaultValue(
|
|
this.formList,
|
|
isFirst ? this.value || this.dFormData : {}
|
|
));
|
|
},
|
|
getStyle() {
|
|
return {
|
|
gridTemplateColumns: `repeat(${this.column}, 1fr)`,
|
|
};
|
|
},
|
|
getBindData(item) {
|
|
let componentKey = resolveName(item.type || "input");
|
|
|
|
return {
|
|
placeholder: "请输入",
|
|
...defaultComponentOptions[componentKey],
|
|
...item.options,
|
|
};
|
|
},
|
|
getComponent(type) {
|
|
if (!type) type = "input";
|
|
|
|
const componentKey = resolveName(type);
|
|
|
|
const ElComponentKey = `El${componentKey}`;
|
|
|
|
return (
|
|
components[componentKey] || components[ElComponentKey] || ElComponentKey
|
|
);
|
|
},
|
|
getRules(item) {
|
|
// 自定义的没添加
|
|
|
|
/**
|
|
* validator 手动情况下 此方法需要改传参 ({
|
|
* // 当前总数据
|
|
* data,
|
|
* // 当前字段的值
|
|
* value,
|
|
* // 回调跟 el 一样
|
|
* callback,
|
|
* // 当前的item config
|
|
* config
|
|
* })
|
|
*/
|
|
const ruleMatch = (value) => {
|
|
/**
|
|
* @type {{ callback?: (value: any) => boolean; type?: "phone"; message: string; }[]}
|
|
*/
|
|
const rules = item.rules || [];
|
|
for (let index = 0; index < rules.length; index++) {
|
|
const rule = rules[index];
|
|
// 返回值 true 通过 false 异常
|
|
if (typeof rule.callback === "function") {
|
|
if (!rule.callback(value, this.modelFormData))
|
|
return new Error(rule.message || "内容错误");
|
|
} else if (RegexpMap[rule.type]) {
|
|
if (!RegexpMap[rule.type].reg.test(value))
|
|
return new Error(rule.message || RegexpMap[rule.type].message);
|
|
}
|
|
}
|
|
};
|
|
|
|
if (item.required)
|
|
return [
|
|
{
|
|
validator: (_, __, callback) => {
|
|
const value = pathGet(this.modelFormData, item.key);
|
|
if (
|
|
(!value && typeof value != "number") ||
|
|
(typeof value === "string" && !value.trim()) ||
|
|
(typeof value === "object" && value.length == 0)
|
|
)
|
|
return callback(
|
|
new Error(
|
|
`${item.options?.placeholder || `${item.label}不能为空`}`
|
|
)
|
|
);
|
|
|
|
const err = ruleMatch(value);
|
|
|
|
if (err) return callback(err);
|
|
|
|
callback();
|
|
},
|
|
trigger: ["blur", "change"],
|
|
},
|
|
];
|
|
else if (item.rules?.length)
|
|
return [
|
|
{
|
|
validator: (_, __, callback) => {
|
|
/**
|
|
* @type {{ callback?: (value: any) => boolean; type?: "phone"; message: string; }[]}
|
|
*/
|
|
|
|
const err = ruleMatch(pathGet(this.modelFormData, item.key));
|
|
|
|
if (err) return callback(err);
|
|
|
|
callback();
|
|
},
|
|
trigger: ["blur", "change"],
|
|
},
|
|
];
|
|
},
|
|
validate() {
|
|
return new Promise((resolve, reject) => {
|
|
this.$refs.ElFormRef.validate((bool) => {
|
|
if (bool) resolve(cloneDeep(this.modelFormData));
|
|
else reject("表单验证未通过");
|
|
});
|
|
});
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.list-item {
|
|
display: inline-block;
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.list-enter-active,
|
|
.list-leave-active {
|
|
transition: all 1s;
|
|
position: absolute;
|
|
}
|
|
|
|
.list-enter,
|
|
.list-leave-to {
|
|
opacity: 0;
|
|
transform: translateX(30px);
|
|
}
|
|
|
|
.FormConfig {
|
|
display: grid;
|
|
align-content: start;
|
|
width: 100%;
|
|
gap: 15px 15px;
|
|
// overflow-x: hidden;
|
|
padding-right: 9px;
|
|
|
|
.formItem {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
::v-deep {
|
|
.el-form-item {
|
|
align-items: center;
|
|
margin: 0;
|
|
height: 100%;
|
|
width: 100%;
|
|
|
|
&:first-child {
|
|
.el-form-item__label-wrap {
|
|
// padding-top: 9px;
|
|
margin: 0 !important;
|
|
}
|
|
}
|
|
|
|
.el-form-item__label-wrap {
|
|
width: fit-content;
|
|
}
|
|
|
|
.el-form-item__label {
|
|
height: 22px;
|
|
font-size: 15px;
|
|
// font-family: PingFang SC, PingFang SC;
|
|
font-weight: 400;
|
|
color: #3de8ff;
|
|
line-height: unset;
|
|
// -webkit-background-clip: text;
|
|
// -webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
.el-form-item__content {
|
|
margin: 0 !important;
|
|
flex: 1;
|
|
height: 100%;
|
|
|
|
.el-input__prefix {
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|