大家好!我叫戴向天
QQ群:602504799
如若有不理解的,可加QQ群进行咨询了解
<template>
<div :class="getClassName()" class="flex-sw-22">
<el-form
:label-width="labelWidth"
:model="params"
ref="form"
:rules="rules"
>
<div class="flex" v-for="(row, key) in forms" :key="key">
<el-form-item
:class="`flex-sw-${24 / row.length}`"
v-for="(single, s) in row"
:key="s"
:label="single.label"
v-show="!single.hide"
:prop="single.prop"
>
<template v-if="single.type === 'select'">
<div :class="getClassName('select')">
<el-select
v-bind="single.bind"
v-model="params[single.prop]"
@change="changeHandler(single)"
>
<el-option
v-for="(option, o) in single.options || []"
:key="o"
:label="option.label"
:value="option.value"
></el-option>
</el-select>
<el-button
@click="buttonHandler(single)"
v-if="'button' in single"
size="mini"
v-bind="single.button.bind || {}"
>{{ single.button.name }}</el-button
>
</div>
</template>
<template v-else-if="single.type === 'switch'">
<el-switch
v-model="params[single.prop]"
@change="changeHandler(single)"
v-bind="single.bind"
>
</el-switch>
</template>
<template v-else-if="single.type === 'checkbox'">
<el-checkbox-group
v-model="params[single.prop]"
@change="changeHandler(single)"
>
<el-checkbox
v-for="(option, o) in single.options"
:key="o"
v-bind="option.bind"
></el-checkbox>
</el-checkbox-group>
</template>
<template v-else-if="single.type === 'radio'">
<el-radio-group
v-model="params[single.prop]"
@change="changeHandler(single)"
v-bind="single.bind"
>
<el-radio
v-for="(option, o) in single.options"
:key="o"
:label="option.value"
>{{ option.label }}</el-radio
>
</el-radio-group>
<span
class="tips"
v-if="getOptionTips(params[single.prop], single.options)"
v-html="getOptionTips(params[single.prop], single.options)"
></span>
</template>
<template v-else-if="single.type === 'date'">
<el-date-picker
@change="changeHandler(single)"
v-model="params[single.prop]"
v-bind="single.bind"
>
</el-date-picker>
</template>
<template v-else-if="single.type === 'download'">
<a
:class="getClassName('download', ['a'])"
@click="downloadFile(single.bind.href)"
:download="single.bind.text"
>{{ single.bind.text }}</a
>
</template>
<template v-else-if="single.type === 'downloads'">
<div v-for="(file, f) in params[single.prop] || []" :key="f">
<a
:class="getClassName('download', ['a'])"
:href="getUrl(file.href)"
:download="file.name"
>{{ file.name }}</a
>
</div>
</template>
<template v-else-if="single.type === 'upload'">
<ul :class="getClassName('file', ['list'])">
<li v-for="(file, f) in params[single.prop] || []" :key="f">
{{ file.name
}}<i class="el-icon-close" @click="deleteFile(single, f)"></i>
</li>
</ul>
<div class="flex-center-y">
<template
v-if="
(params[single.prop] || []).length > 0
? (single.bind || {}).max
? single.bind.max > (params[single.prop] || []).length
: single.bind.multiple
: true
"
>
<UploadButton
v-bind="single.bind"
v-model="params[single.prop]"
></UploadButton>
<div class="tips" v-if="single.tips" v-html="single.tips"></div>
</template>
</div>
</template>
<template v-else-if="single.type === 'tips'">
<div v-bind="single.bind" v-html="single.html"></div>
</template>
<template v-else>
<el-input v-bind="single.bind" v-model.trim="params[single.prop]">
<el-button
slot="append"
@click="buttonHandler(single)"
v-bind="single.button.bind || {}"
v-if="single.button"
>{{ single.button.name }}</el-button
>
</el-input>
</template>
</el-form-item>
</div>
</el-form>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop, Watch } from "vue-property-decorator";
import UploadButton from "@/components/common/form/upload-button.vue";
import {
ITableForm,
ITableFormrSingle,
ISelectRequestOption,
IFillValue,
IFillValueOptions,
IEvent,
IOption,
} from "@/lib/interface/component/tableForm";
import downloadByUrl from "@/lib/methods/downloadByUrl";
@Component({
name: "TableForm",
components: { UploadButton },
})
export default class TableForm extends Vue {
@Prop({
default: "3rem",
})
private labelWidth?: string;
@Prop({
default: () => [],
})
private config: ITableForm;
private forms: ITableForm = [];
private params: any = {};
private rules: any = {};
@Watch("config", {
deep: true,
})
private configWatch() {
this.params = this.createParams(this.config, this.params);
this.forms = this.createForms(this.config);
// this.rules = {};
// this.$nextTick(() => {
// this.rules = this.createRule(this.config, this.params);
// });
}
private get getOptionTips() {
return (val: string, options: IOption[]) => {
const option = options.find((item: IOption) => item.value === val);
if (option && option.tips) {
return option.tips;
} else {
return null;
}
};
}
private createForms(config: ITableForm) {
return JSON.parse(JSON.stringify(config)).map(
(row: ITableFormrSingle[]) => {
return row.map((single: ITableFormrSingle) => {
delete single.follow;
single.value = "";
return single;
});
}
);
}
private get getUrl() {
return (url: string = "") => {
return url
.split("/")
.map((str) => (/[\u4e00-\u9fa5]+/g.test(str) ? encodeURI(str) : str))
.join("/");
};
}
/**
* 输入框的按钮点击处理
*/
private buttonHandler(item: ITableFormrSingle) {
if (item.button) {
this.$emit("eventHandler", item.button.emit, item);
this.$emit("buttonHandler", item.button.emit, item);
}
}
/**
* 创建出一级数组
*/
private toSingleArray(forms: ITableForm) {
return forms.reduce(
(frist: ITableFormrSingle[], next: ITableFormrSingle[]) =>
frist.concat(next),
[]
);
}
/**
* 创建参数信息
*/
private createParams(forms: ITableForm, originData: any) {
const arr = this.toSingleArray(forms);
return arr.reduce((frist: any, next: ITableFormrSingle) => {
frist[next.prop] = next.follow ? next.value : originData[next.prop];
return frist;
}, {});
}
/**
* 创建效验规则
*/
private createRule(forms: ITableForm, params: any = {}) {
const arr = this.toSingleArray(forms)
.filter(
(item: ITableFormrSingle) =>
item.required ||
(item.rules && Array.isArray(item.rules) && item.rules.length > 0)
)
.map((item: ITableFormrSingle) => ({
prop: item.prop,
message: (item.bind || {}).placeholder,
type: item.type,
rules: item.rules || [],
required: item.required || false,
}));
const rules = Object.keys(params).reduce((frist: any, next: string) => {
const single = arr.find((item: any) => item.prop === next);
frist[next] = [
{
required: single ? single.required : false,
trigger: "blur",
},
];
return frist;
}, {});
return Object.keys(rules).reduce((frist: any, next: string) => {
const single = arr.find((item: any) => item.prop === next);
if (single) {
if (single.type === "selecet") {
rules[next].push({
required: true,
trigger: "change",
});
}
rules[next] = single.rules.concat(
rules[next].map((rule: any) => {
return {
...rule,
message: single.message,
};
})
);
frist[next] = rules[next];
}
return frist;
}, {});
}
private downloadFile(url: string) {
downloadByUrl(url);
}
/**
* 获取参数
* @bool 是获取的时候进行效验
*/
private async getParams(bool?: boolean) {
if (bool) {
const res = await this.validate();
if (!res) {
return null;
}
}
return Object.keys(this.params).reduce((frist: any, key: string) => {
frist[key] = this.params[key] === undefined ? "" : this.params[key];
return frist;
}, {});
}
/**
* 通过prop字段获取options
*/
private getOptionsByProp(prop: string) {
if (prop) {
const single = this.toSingleArray(this.forms).find(
(item: ITableFormrSingle) => item.prop === prop
);
if (single) {
return single.options;
} else {
return [];
}
} else {
return [];
}
}
/**
* 获取select的选项
*/
private async getOptions(forms: ITableForm): Promise<ISelectRequestOption[]> {
const arr = this.toSingleArray(forms).filter(
(item: ITableFormrSingle) => item.type === "select" && item.source
);
const promiesArr = arr.map((item: ITableFormrSingle) => {
return new Promise((r, j) => {
if (item.source) {
const { server, mehotd, params, callBack } = item.source;
server[mehotd](params)
.then((res: any) => {
const data = callBack(res);
r({
prop: item.prop,
data,
});
})
.catch((error: string) => {
console.warn(error);
j(false);
});
}
});
});
const resuleArray = await Promise.all(promiesArr);
return resuleArray as ISelectRequestOption[];
}
private fullOption(optionsArray: ISelectRequestOption[], forms: ITableForm) {
return forms.map((row: ITableFormrSingle[]) => {
return row.map((single: ITableFormrSingle) => {
const option = optionsArray.find(
(options: ISelectRequestOption) => options.prop === single.prop
);
if (option) {
delete single.follow;
single.value = "";
single.options = option.data;
}
return single;
});
});
}
// 填充数据
private fillValue(data: IFillValue[]) {
this.forms = JSON.parse(JSON.stringify(this.forms)).map(
(row: ITableFormrSingle[]) => {
return row.map((single: ITableFormrSingle) => {
const item = data.find(
(option: IFillValueOptions) => option.prop === single.prop
);
if (item) {
single = {
...single,
follow: "follow" in item ? (item as any).follow : true,
...item,
bind: {
...single.bind,
...item.bind,
},
};
}
return single;
});
}
);
const values = JSON.parse(JSON.stringify(data)).reduce(
(frist: any, next: IFillValue) => {
if (next.value !== this.params[next.prop]) {
frist[next.prop] = next.value;
}
return frist;
},
{}
);
this.params = this.createParams(this.forms, this.params);
this.autoEventHandler(values, this.forms, this.params);
this.forms = this.createForms(this.forms);
}
// change事件报出
private changeHandler(single: ITableFormrSingle) {
if (single.event && single.event.length) {
const eventInfo = single.event.find(
(event: IEvent) => event.trigger === "change"
);
if (eventInfo) {
if (eventInfo.emit) {
this.$emit("eventHandler", eventInfo.emit, {
...single,
value: this.params[single.prop],
});
}
}
}
}
private async validate() {
return new Promise((r, j) => {
(this.$refs.form as any).validate((bool: boolean) => {
r(bool);
});
});
}
// 初始化的时候事件调用
private autoEventHandler(params: any, forms: ITableForm, oldParams: any) {
const singleArray = this.toSingleArray(forms);
Object.keys(JSON.parse(JSON.stringify(params))).forEach((key: string) => {
const item = singleArray.find(
(single: ITableFormrSingle) => single.prop === key
);
if (item && item.event) {
item.event.forEach((event: IEvent) => {
if (event.trigger === "change") {
this.changeHandler(item);
}
});
}
});
}
private async refreshSource(prop: string) {
const singleArray: ITableFormrSingle[] = this.toSingleArray(this.config);
const single = singleArray.find(
(item: any) => item.prop === prop && item.type === "select"
);
if (single && single.source) {
const { server, mehotd, params, callBack } = single.source;
const res = await server[mehotd](params);
const data = callBack(res) as IOption[];
const result: ISelectRequestOption[] = [
{
prop,
data,
},
];
this.forms = this.fullOption(result, this.forms);
} else {
console.warn(`刷新source失败,未找到${prop}字段,或者没有source配置参数`);
}
}
private deleteFile(item: ITableFormrSingle, index: number) {
if (item.event) {
const eventItem = item.event.find(
(event: IEvent) => event.trigger === "delete"
);
if (eventItem) {
this.$emit(
"eventHandler",
eventItem.emit,
this.params[item.prop][index],
(bool: boolean) => {
if (bool) {
console.log("确认删除");
this.params[item.prop].splice(index, 1);
} else {
console.log("不删除");
}
}
);
} else {
this.params[item.prop].splice(index, 1);
}
} else {
this.params[item.prop].splice(index, 1);
}
}
private async created() {
this.params = this.createParams(this.config, this.params);
this.forms = this.createForms(this.config);
this.rules = this.createRule(this.config, this.params);
const optionsArray = await this.getOptions(this.config);
this.forms = this.fullOption(optionsArray, this.forms);
this.autoEventHandler(this.params, this.forms, {});
}
}
</script>
<style lang="less" scoped>
.tableForm {
// padding-top: unit(30 / @fontSize, rem);
&-select {
display: flex;
border: unit(1 / @fontSize, rem) solid #dcdfe6;
border-radius: unit(4 / @fontSize, rem);
overflow: hidden;
/deep/ & > .el-select {
width: 100%;
.el-input__inner {
border: none;
}
}
/deep/& > .el-button {
border: none;
border-left: unit(1 / @fontSize, rem) solid #dcdfe6;
border-radius: 0;
}
}
&-download {
&__a {
color: @primary;
text-decoration: none;
cursor: pointer;
font-size: unit(18 / @fontSize, rem);
&:hover {
text-decoration: underline;
}
}
}
&-file {
&__list {
min-height: unit(7 / @fontSize, rem);
}
}
.tips {
color: #999;
margin-left: unit(30 / @fontSize, rem);
line-height: unit(20 / @fontSize, rem);
}
/deep/ .el-form-item.is-error {
.tableForm-select {
border-color: #f56c6c;
}
}
/deep/.el-date-editor {
width: 100% !important;
}
}
</style>