<template> <div class="by-form"> <el-form :model="formData" :label-width="formOption.labelWidth || '100px'" :inline="formOption.inline || false" :rules="rules" :labelPosition="formOption.labelPosition || 'right'" ref="byForm" :disabled="formOption.disabled || false"> <template v-for="i in formConfig" :key="i.model"> <el-form-item :label="i.label" :prop="i.prop" v-if="(Object.keys(i).length>0)&& (i.isShow || i.isShow == undefined)" :style=" (i.type == 'title'||i.type == 'title1') ? 'width:100%' : i.itemWidth ? 'width:' + i.itemWidth + '%' : formOption.itemWidth ? 'width:' + formOption.itemWidth + '%' : '100%' " :class=" (i.type == 'title'||i.type == 'title1')? 'formTitle' : '' "> <el-input v-if="i.type == 'input'" v-model="formData[i.prop]" :placeholder="i.placeholder || $t('common.pleaseEnter')" @input="(e) => commonsEmit(e, i)" @change="(e) => commonsEmitChange(e, i)" :type="i.itemType ? i.itemType : 'text'" :disabled="i.disabled ? i.disabled : false" :max="i.max" :min="i.min" :maxlength="i.maxlength" :readonly="i.readonly ? i.readonly : false" :style="i.style" /> <el-input v-if="i.type == 'selectInput'" v-model="formData[i.prop]" :placeholder="i.placeholder || $t('common.pleaseEnter')" @input="(e) => commonsEmit(e, i)" :type="i.itemType ? i.itemType : 'text'" :disabled="i.disabled ? i.disabled : false" :max="i.max" :min="i.min" :maxlength="i.maxlength" :readonly="i.readonly ? i.readonly : false"> <template #prepend> <el-select v-model="formData[i.selectProp]" :placeholder="i.selectPlaceholder || $t('common.pleaseSelect')" @change="(e) => commonsEmit(e, i)" :disabled="i.disabledSelect ? i.disabledSelect : false" :readonly="i.readonly ? i.readonly : false" style="width: 80px"> <el-option :label="j.dictValue || j.name || j.label" :value="j.dictKey||j.id || j.value" v-for="j in i.data" :key="j.id || j.dictKey || j.value"> </el-option> </el-select> </template> </el-input> <div v-else-if="i.type == 'text'"> {{formData[i.prop]}} </div> <el-select v-model="formData[i.prop]" :multiple="i.multiple || false" v-else-if="i.type == 'select'" :placeholder="i.placeholder || $t('common.pleaseSelect')" @change="(e) => commonsEmit(e, i)" :disabled="i.disabled ? i.disabled : false" :clearable="i.clearable ? i.clearable : false" :filterable="i.filterable ? true : true" :style="i.style?i.style:{width:'100%'}" :readonly="i.readonly ? i.readonly : false"> <el-option :label="j.dictValue || j.name || j.label" :value="j.dictKey||j.id || j.value" v-for="j in i.data" :key="j.id || j.dictKey || j.value" :disabled="j.disabled ?j.disabled:false "> </el-option> </el-select> <el-tree-select v-model="formData[i.prop]" :multiple="i.multiple || false" v-else-if="i.type == 'treeSelect'" :data="i.data" :readonly="i.readonly ? i.readonly : false" :props="{ value: i.propsTreeValue || 'id', label: i.propsTreeLabel || 'label', children: i.propsTreeChildren || 'children', disabled:i.propsTreeDisabled || 'disabled' }" value-key="id" :filterable="i.filterable ? true : true" :clearable="i.clearable ? i.clearable : false" :placeholder="i.placeholder || $t('common.pleaseSelect')" :disabled="i.disabled ? i.disabled : false" check-strictly :style="i.style?i.style:{width:'100%'}" default-expand-all @change="(e) => commonsEmit(e, i)" /> <el-date-picker v-model="formData[i.prop]" :readonly="i.readonly ? i.readonly : false" v-else-if="i.type == 'date'" :type="i.itemType" :placeholder="i.placeholder || $t('common.pleaseSelectTime')" @change="(e) => commonsEmit(e, i)" :disabled="i.disabled ? i.disabled : false" :format="i.format ? i.format : dateFormatInit(i.itemType)" :value-format="i.format ? i.format : dateFormatInit(i.itemType)" :style="i.style?i.style:{width:'100%'}" :disabled-date="i.disabledFn ? i.disabledFn :()=>false" /> <el-switch :disabled="i.disabled ? i.disabled : false" v-else-if="i.type == 'switch'" :readonly="i.readonly ? i.readonly : false" v-model="formData[i.prop]" /> <el-checkbox-group v-else-if="i.type == 'checkbox'" v-model="formData[i.prop]" :readonly="i.readonly ? i.readonly : false" :disabled="i.disabled ? i.disabled : false"> <el-checkbox v-for="j in i.data" :key="j.id || j.value" :label="j.id || j.value" name="type"> {{ j.name || j.label }} </el-checkbox> </el-checkbox-group> <el-radio-group v-else-if="i.type == 'radio'" v-model="formData[i.prop]" :readonly="i.readonly ? i.readonly : false" :disabled="i.disabled ? i.disabled : false" @change="(e) => commonsEmit(e, i)"> <el-radio :border="i.border ? i.border : false" v-for="j in i.data" :key="j.id || j.value ||j.dictKey" :label="j.id ||j.dictKey || j.value" name="type"> {{j.dictValue || j.name || j.label }} </el-radio> </el-radio-group> <el-input-number v-else-if="i.type == 'number'" v-model="formData[i.prop]" :readonly="i.readonly ? i.readonly : false" :placeholder="i.placeholder || $t('common.pleaseEnter')" @change="(e) => commonsEmit(e, i)" :disabled="i.disabled ? i.disabled : false" :min="i.min ? i.min : 0" :max="i.max ? i.max : 9999999999" :step="i.step ? i.step : 1" :precision="i.precision !== '' ? i.precision : 2" :controls="i.controls === false ? false : true" :style="i.style?i.style:{width:'100%'}" onmousewheel="return false;"> </el-input-number> <el-tree v-else-if="i.type == 'tree'" :data="i.data" :props="i.props" :readonly="i.readonly ? i.readonly : false" :show-checkbox="i.showCheckbox || true"> </el-tree> <el-cascader v-else-if="i.type == 'cascader'" :options="i.data" :props="i.props" :readonly="i.readonly ? i.readonly : false" :placeholder="i.placeholder || $t('common.pleaseSelect')" @change="(e) => commonsEmit(e, i)" :disabled="i.disabled ? i.disabled : false" :style="i.style"> </el-cascader> <!-- <div class="form-title" v-else-if="i.type == 'title'"> {{ i.title }} </div> --> <div v-else-if="i.type == 'title'" style="width:100%"> <div v-if="i.haveLine" style="width:calc(100% + 80px);margin-left:-45px;background:#F0F2F5;height:15px"></div> <div :style="{marginTop:i.haveLine?'15px':''}" style="margin-left:-15px;"> <TitleInfo :content="i.title"></TitleInfo> </div> </div> <div v-else-if="i.type == 'title1'" style="width:100%"> <div> <TitleInfo :content="i.title"></TitleInfo> </div> </div> <slot :name="i.slotName" v-else-if="i.type == 'slot'"> {{ i.slotName }}插槽占位符 </slot> <div class="upload" v-else-if="i.type == 'upload'"> <el-upload :file-list="formData[i.prop]?formData[i.prop]:[]" multiple :action="uploadUrl" :data="uploadData" :list-type="i.listType ? i.listType : 'text'" :accept="i.accept?i.accept :''" :limit="i.limit?i.limit:3" :before-upload="(file)=>handleBeforeUpload(file,i)" :on-success="()=>handleSuccess(i)" :on-remove="(file)=>handleRemove(file,i)" :on-exceed="()=>handleExceed(i)" :on-preview="onPreviewFile"> <el-icon v-if="i.listType=='picture-card'"> <Plus /> </el-icon> <span v-else-if="formOption.disabled" style="color:#409EFF">已上传:</span> <el-button type="primary" plain v-else>点击上传</el-button> </el-upload> </div> <div class="upload" v-else-if="i.type == 'uploadImg'"> <el-upload :action="uploadUrl" accept=".gif, .jpeg, .jpg, .png" :show-file-list="false" :data="uploadData" :before-upload="(file)=>handleBeforeUploadOne(file,i.prop,i.imgProp)" :on-success="()=>handleSuccessOne(i.imgProp)"> <div v-loading="imgLoading"> <img v-if="formData[i.imgProp]" :src="formData[i.imgProp]" class="picOne" /> <el-icon v-else class="avatar-uploader-icon"> <Plus /> </el-icon> </div> </el-upload> </div> <div v-else-if="i.type == 'table'" class="by-form-table" style="width: 100%"> <el-table :data="formData[i.prop]" style="width: 100%"> <el-table-column :prop="j.prop" :label="j.label" :width="i.width" v-for="(j, jindex) in i.column"> <template #default="scope" v-if="j.type"> <component @change="(e) => formTableChange(e, scope, j)" v-model="scope.row[j.prop]" :is="formTableObj[j.type]" :placeholder="j.placeholder || $t('common.pleaseEnter')" :type="j.type == 'number' ? 'number' : 'text'"> <el-option :label="n.title || n.name || n.label" :value="n.id || n.value" v-for="n in j.data" :key="n.id" v-if="j.type == 'select'"> </el-option> </component> </template> </el-table-column> </el-table> </div> <!-- <div v-else-if="i.type == 'json'"> <byForm :formConfig="i.json" :formOption="formOption" v-model="formData[i.prop]" ref="byform" :rules="rules"> </byForm> </div> --> </el-form-item> </template> </el-form> </div> </template> <script> export default { name: "byForm", }; </script> <script setup> import { set } from "@vueuse/shared"; import { reactive } from "vue"; import TitleInfo from "@/components/TitleInfo/index.vue"; defineProps({ modelValue: { type: Object, default: false, }, formConfig: { type: Array, default: false, }, disabled: { type: Boolean, default: false, }, formOption: { type: Object, default: false, }, rules: { type: Object, default: false, }, }); const formTableChange = (e, scope, column) => { if (column.fn) { column.fn(e, scope); } console.log(formData); console.log(e, scope); }; const formTableObj = { number: "el-input", text: "el-input", select: "el-select", input: "el-input", }; const fileList = ref([]); const fileListCopy = ref([]); const uploadData = ref({}); const fileData = ref({}); //文件上传相关方法 const handleSuccess = (item) => { if (fileData.value && fileData.value.fileUrl) { formData.value[item.prop].push({ id: fileData.value.id, fileName: fileData.value.fileName, name: fileData.value.fileName, url: fileData.value.fileUrl, fileUrl: fileData.value.fileUrl, }); } emit("update:modelValue", formData.value); }; const handleRemove = (file, item) => { let index = formData.value[item.prop].findIndex( (x) => x.id == file.id || x.id == file.raw.id ); if (index > -1) { formData.value[item.prop].splice(index, 1); } }; const handleBeforeUpload = async (file, item) => { fileData.value = {}; const res = await proxy.post("/fileInfo/getSing", { fileName: file.name }); file.id = res.id; file.fileUrl = res.fileUrl; uploadData.value = res.uploadBody; fileData.value = res; return true; }; const handleExceed = (item) => { let limit = item.limit || 3; return proxy.msgTip(`上传文件数量不可大于${limit}`, 2); }; const onPreviewFile = (file, a) => { if (file && file.fileUrl) { window.open(file.fileUrl, "_blank"); } else { window.open(file.raw.fileUrl, "_blank"); } }; const imgLoading = ref(false); const handleBeforeUploadOne = async (file, prop, imgProp) => { imgLoading.value = true; fileData.value = {}; const res = await proxy.post("/fileInfo/getSing", { fileName: file.name }); uploadData.value = res.uploadBody; formData.value[prop] = [ { id: res.id, fileName: res.fileName, fileUrl: res.fileUrl, }, ]; fileData.value = res; return true; }; const handleSuccessOne = (imgProp) => { if (fileData.value && fileData.value.fileUrl) { formData.value[imgProp] = fileData.value.fileUrl; emit("update:modelValue", formData.value); } setTimeout(() => { imgLoading.value = false; }, 400); }; const isInit = ref(false); const { proxy } = getCurrentInstance(); const emit = defineEmits(["update:modelValue"]); const formData = computed(() => { return proxy.modelValue; }); const formDataReset = ref({ ...proxy.modelValue }); const commonsEmit = (prop, item) => { if (item.type == "input" && item.itemType == "number") { console.log(formData.value); } if (item.fn) { item.fn(prop); } emit("update:modelValue", formData.value); }; const commonsEmitChange = (prop, item) => { if (item.type == "input") { formData.value[item.prop] = prop.trim(); } if (item.fn) { item.fn(prop); } emit("update:modelValue", formData.value); }; const loadInit = () => { const v = this; for (let i = 0; i < proxy.formConfig.length; i++) { const element = proxy.formConfig[i]; if (element.isLoad) { commonGetdata(element.isLoad, i); } } }; const dateFormatInit = (itemType) => { const formatObj = { year: "YYYY", month: "YYYY-MM", date: "YYYY-MM-DD", dates: "YYYY-MM-DD", datetime: "YYYY-MM-DD HH:mm:ss", monthrange: "YYYY-MM-DD HH:mm:ss", datetimerange: "YYYY-MM-DD HH:mm:ss", daterange: "YYYY-MM-DD HH:mm:ss", }; return formatObj[itemType]; }; //公用递归,保证key,val统一 const commonRecursive = (arr, labelKey, labelVal, childrenName) => { for (let i = 0; i < arr.length; i++) { if (labelKey == "stringArray") { arr[i] = { label: arr[i], value: arr[i], id: arr[i], title: arr[i], }; } else { arr[i].title = arr[i].label = arr[i][labelKey]; arr[i].id = arr[i].value = arr[i][labelVal]; } if (childrenName) { arr[i].children = arr[i][childrenName]; } arr[i].checked = false; typeof arr[i][labelVal] == String ? (arr[i].key = arr[i][labelVal]) : (arr[i].key = JSON.stringify(arr[i][labelVal])); if (childrenName) { this.commonRecursive( arr[i][childrenName], labelKey, labelVal, childrenName ); } } }; //请求form表单所需数据字典 const commonGetdata = (isLoad, i) => { proxy[isLoad.method](isLoad.url, isLoad.req).then((message) => { console.log(message); if (getFormat(isLoad.resUrl, message) == undefined) { console.log("请查看isLoad配置是否正确url:" + isLoad.url); return; } proxy.formConfig[i].data = getFormat(isLoad.resUrl, message); if (isLoad.labelKey) { commonRecursive( proxy.formConfig[i].data, isLoad.labelKey, isLoad.labelVal, isLoad.childrenName ); } console.log(proxy.formConfig[i].data); }); }; //根据resurl获取数据 const getFormat = (formatStr, props) => { if (!formatStr) return props; return formatStr .split(".") .reduce((total, cur) => (!total ? "" : total[cur]), props); }; //初始化所有表单 const formDataInit = () => { var map = { input: "", radio: null, select: null, checkbox: [], date: "", datetime: "", daterange: [], datetimerange: [], year: null, month: null, switch: false, inputNumber: 0, cascader: [], Solt: null, Transfer: [], Upload: { path: null, id: null, name: null }, password: "", treeSelect: "", json: {}, }; const formDataCopy = { ...formData.value }; for (let i = 0; i < proxy.formConfig.length; i++) { const element = proxy.formConfig[i]; if (formDataCopy[element.prop] || element.type === "slot") { continue; } if (map[element.itemType] != undefined) { formData.value[element.prop] = map[element.itemType]; } else { formData.value[element.prop] = element.multiple ? [] : map[element.type]; } } emit("update:modelValue", formData.value); }; const handleSubmit = async (onSubmit) => { try { const flag = await proxy.$refs["byForm"].validate(); if (flag) { const form = { ...formData.value }; proxy.formConfig.map((item) => { // if (item.type == "json") { // form[item.prop] = JSON.stringify(form[item.prop]); // } // if (item.type == 'input') { // form[item.prop] = form[item.prop].trim(); // } }); emit("update:modelValue", form); onSubmit(); return true; } else { } } catch (err) { setTimeout(() => { const errorDiv = document.getElementsByClassName("is-error"); if (errorDiv && errorDiv[0]) { errorDiv[0].scrollIntoView({ behavior: "smooth", block: "center", inline: "nearest", }); } }, 0); console.log("请检查表单!", err); return false; } }; const byform = ref(null); // 延迟使用,因为还没有返回跟挂载 onMounted(() => {}); defineExpose({ handleSubmit, }); formDataInit(); loadInit(); </script> <style scope> .form-title { font-size: 14px; font-weight: bold; margin-top: 22px; color: #333333; } .by-form .el-form--inline .el-form-item { margin-right: 0px; box-sizing: border-box; } .by-form .el-form--inline > .el-form-item { padding: 0 10px 0 0px; } .by-form .el-form--inline .formTitle { padding: 0px !important; } /* .el-form--inline.el-form--label-top{ justify-content: space-between; } */ .dn { display: none !important; } /* .by-form-json .by-form .el-form .el-form-item { margin-bottom: 18px; } */ /* .by-form-json { padding: 0px !important; } */ .picOne { object-fit: contain; width: 80px; height: 80px; cursor: pointer; vertical-align: middle; } .el-icon.avatar-uploader-icon { font-size: 20px; color: #8c939d; width: 80px; height: 80px; text-align: center; border: 1px dashed var(--el-border-color); } </style>