当前位置: 首页 > web >正文

vue element 封装表单

背景:

在前端系统开发中,系统页面涉及到的表单组件比较多,所以进行了简单的封装。封装的包括一些Form表单组件,如下:input输入框、select下拉框、等

实现效果:

理论知识:

表单组件官方链接:点击跳转

封装组件:

封装组件的思路:

 不封装element组件,每一个input组件绑定一个form对象,例如官网。

简单封装element组件,利用for循环生成form表单的每一项el-form-item。

进一步封装,利用判断表单组件的类型,是input、select或者其它表单组件。

终极封装,把el-form表单封装成一个独立的表单,自定义命名为AreaFormItem.vue;然后单页面vue调用AreaFormItem.vue传参以及表单数据回显等操作。

封装组件的过程: 

1.不封装表单组件,代码如下: 

实现代码:【表单组件未封装】

<el-form ref="ruleForm" :model="state.form" label-width="auto" :rules="rules" class="form-box"><el-row class="form-row"><el-col :span="5" :gutter="20" style="padding-left: 0px"><el-form-item label="航次" prop="voyageNo"><el-input v-model="state.form.voyageNo" placeholder="请输入航次" /></el-form-item></el-col><el-col :span="4" :gutter="20" style="padding-left: 10px"><el-form-item label="船名" prop="name"><el-input v-model="state.form.name" placeholder="请输入船名" /></el-form-item></el-col><el-col :span="4" :gutter="20" style="padding-left: 10px"><el-form-item label="装港" prop="loadName"><el-input v-model="state.form.loadName" placeholder="请输入装港" /></el-form-item></el-col><el-col :span="5" :gutter="20" style="padding-left: 10px"><el-form-item label="卸港" prop="dischargName"><el-input v-model="state.form.dischargName" placeholder="请输入卸港" /></el-form-item></el-col><el-col :span="5" :gutter="20" style="padding-left: 10px"><el-form-item label="卸港" prop="cargoType"><el-input v-model="state.form.cargoType" placeholder="请输入卸港" /></el-form-item></el-col></el-row></el-form>

备注:因为是横着的表单,这里加入了el-row和el-col 。

2.表单组件封装,for循环渲染表单组件input:

//html
<el-form ref="ruleForm" :model="state.form" label-width="auto" :rules="rules" class="form-box"><el-row class="form-row"><el-col :span="item.span || 5" :gutter="20" style="padding-right: 10px" v-for="(item, index) in formItems":key="index"><el-form-item :label="item.label" :prop="item.key"><el-input v-model="state.form[item.key]" :placeholder="item.placeholder || '请输入'" /></el-form-item></el-col></el-row></el-form>//涉及到的变量
const state = reactive({form: {name: '',voyageNo: '',loadName: '',dischargName: '',cargoType: ''},
})
const formItems = [{type: 'input',label: '船名',key: 'name',placeholder: '请输入或者选择船名'},{type: 'input',label: '航次',key: 'voyageNo',placeholder: '请输入或者选择航次'},{type: 'select',label: '装港',key: 'loadName',placeholder: '请输入或者选择装港'},{type: 'select',label: '卸港',key: 'dischargName',placeholder: '请输入或者选择始港'},{type: 'input',label: '货种',span : 4,key: 'cargoType',placeholder: '请输入或者选择货种',}
]

3.封装一个input或者select下拉列表,封装如下:

<el-form ref="ruleForm" :model="state.form" label-width="auto" :rules="rules" class="form-box"><el-row class="form-row"><el-col :span="item.span || 5" :gutter="20" style="padding-right: 10px"v-for="(item, index) in formItems" :key="index"><el-form-item :label="item.label" :prop="item.key"><el-input v-model="state.form[item.key]" :placeholder="item.placeholder || '请输入'"v-if="item.type === 'input'" /><template v-else-if="item.type === 'select'"><el-select v-model="state.form[item.key]" :clearable="true":loading="routeState.loading_startPoint" :multiple="false":remote-method="(query) => remoteMethod_Point(query, 'cargoType')"filterable placeholder="请输入或者选择货种" remote reserve-keyword@change="(value) => blur_Point(value, 'cargoType', '')"@clear="clear_select('cargoType')"><el-option v-for="item in routeState.options_startPoint" :key="item.value":label="item.label" :value="item.label" /></el-select></template></el-form-item></el-col></el-row></el-form>
//涉及到的数据
const routeState = reactive({loading_startPoint:false,options_startPoint:[]
})

4.终极封装,把上面的form表单封装成一个vue组件,自定义命名为 AreaFormItem.vue,

AreaFormItem.vue组件,封装如下:
<template><el-form :model="data.formInline" class="area-from" :inline="true" :label-position="'left'" ref="ruleAreaFormRef":rules="data.rules"><template v-for="(item, index) in props.formItems" :key="index"><template v-if="item.type &&!['location','medium','password','sexRadio','coordinate','radio','textarea','multipleSelect','uploadFile','uploadSingleImg',].includes(item.type)"><el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.key" :class="item.class" :style="{'max-width': item.width || '50%',width: item.specialWidth || item.width,}" v-if="item.linkageShow === undefined || item.linkageShow"><template v-if="item.type == 'select'"><el-select v-model="data.formInline[item.key]" placeholder="请选择":style="{ width: item.specialWidth || item.width || '10vw' }" :disabled="item.isEditAbled && data.isAbled":filterable="item.filterable" :filter-method="(filterVal) => {handleSelectEdit(filterVal, item);}" @blur="() => {item.filterable &&data.editSelectValue &&(data.formInline[item.key] = data.editSelectValue);}" @change="(val) => {handleSelectEdit(val, item);searchChange(item.linkageKey,item.optionsData,data.formInline[item.key],item.linkageDraw,item.autoPosition,item.dynamicLinkageKey,props.formItems,item.linkageKeyList,item);}"><template v-if="item.optionsData.length"><el-option v-for="(optionItem, index) in item.optionsData" :key="index" :label="optionItem.label":value="optionItem.value" /></template><template v-else><el-option v-for="(optionItem, index) in data.selectData" :key="index" :label="optionItem.label":value="optionItem.value" /></template></el-select></template><template v-if="item.type == 'selectTree'" style=".el-select {width: '10vw';}"><el-tree-select v-model="data.formInline[item.key]" :placeholder="item.placeholder || '请选择所属区域'":style="{ width: item.specialWidth || item.width || '10vw' }" :data="data.treeData":props="data.defaultProps" check-strictly clearable :multiple="item.multiple || false":disabled="item.disabled || false" @change="(value) => handlechange(value, item.limit, item.isJump || true)" :filterable="item.filterable || false"></el-tree-select></template><template v-if="item.type == 'treeMuiltSelect'" style=".el-select {width: '10vw';}"><el-tree-select v-model="data.formInline[item.key]" :placeholder="item.placeholder || '请选择渡口'":style="{ width: item.specialWidth || item.width || '10vw' }" :data="data.treeDataList":props="item.optionParam || data.defaultProps" :multiple="item.multiple || false" :collapse-tags="true":collapse-tags-tooltip="true" :max-collapse-tags="1" clearable :disabled="item.disabled || false"></el-tree-select></template><template v-if="item.type == 'treeLocationSelect'" style=".el-select {width: '10vw';}"><el-tree-select v-model="data.formInline[item.key]" :placeholder="item.placeholder || '请选择区域下的渡口'":style="{ width: item.specialWidth || item.width || '10vw' }" :data="data.treeLocationSelect":props="item.optionParam || data.defaultProps" :multiple="item.multiple || false" :collapse-tags="true":collapse-tags-tooltip="true" :max-collapse-tags="1" clearable :disabled="item.disabled || false" :check-strictly="false"default-expand-all @change="(value) => handleSelectchange(value, item)":filterable="item.filterable || false" :filter-method="(filterVal) => {filtertreeLocationSelect(filterVal, item, 'treeLocationSelect');}" @blur="blur(item, 'treeLocationSelect')"></el-tree-select></template><template v-if="item.type == 'remoteSelect'"><el-select v-model="data.formInline[item.key]" filterable remote :placeholder="item.placeholder"remote-show-suffix :remote-method="(query) => {remoteMethod(query, item);}" :loading="data.remoteSelectLoading" @change="(val) => {item.getSelectRemoteValue &&item.getSelectRemoteValue(val, data.remoteSelectData);}" :disabled="item.isEditAbled && data.isAbled"><el-option v-for="optionItem in data.remoteSelectData" :key="optionItem.value" :label="optionItem.label":value="optionItem.value" /></el-select></template><template v-if="item.type == 'input'"><el-input v-model.trim="data.formInline[item.key]" placeholder="请输入" clearable @clear="searchChange":disabled="(item.isEditAbled && data.isAbled) || item.disabled":style="{ width: item.inputWidth || '10vw' }" :readonly="item.isReadonly || false"><template #suffix v-if="item.suffix">{{ item.suffix }}</template></el-input></template><template v-if="item.type == 'checkBox'"><div class="warn-type"><el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="(state) => {handleCheckAllChange(state, item.key);}" v-if="item.optionCheckAll">全部</el-checkbox><el-checkbox-group v-model="data.formInline[item.key]" @change="handleCheckedChange"><el-checkbox v-for="checkItem in item.optionsData" :key="checkItem.value" :label="checkItem.value":value="checkItem.value">{{ checkItem.label }}</el-checkbox></el-checkbox-group></div></template><template v-if="item.type == 'inputNumber'"><el-input v-model.trim.number="data.formInline[item.key]" placeholder="请输入数字" clearable@clear="searchChange" :disabled="item.isEditAbled && data.isAbled" style="width: 10vw":readonly="item.isReadonly || false" oninput="value = value.replace(/[^\d.]/g,'')"><template #suffix v-if="item.suffix">{{ item.suffix }}</template></el-input></template><template v-if="item.type == 'inputPhoneNumber'"><el-input v-model.trim.number="data.formInline[item.key]" placeholder="请输入数字" clearable@clear="searchChange" :disabled="item.isEditAbled && data.isAbled":style="{ width: item.inputWidth || '10vw' }" :readonly="item.isReadonly || false"oninput="value = value.replace(/[^\d.]/g,'')"><template #suffix v-if="item.suffix">{{ item.suffix }}</template></el-input></template><template v-if="item.type == 'cascader'"><el-cascader v-model="data.formInline.cityValue" :options="data.citiesData" :props="data.cityDefalut"@change="citySelectChange" style="width: 10vw" /></template><template v-if="item.type == 'datePicker'"><el-date-picker v-model="data.formInline[item.key]" type="datetime" placeholder="请选择":default-time="item.defaultTime || defaultTime" value-format="YYYY-MM-DD HH:mm:ss":style="{ width: item.specialWidth || '10vw' }" :readonly="item.isReadonly || false":disabled-date="item.disabledDate" /></template><template v-if="item.type == 'timePicker'"><el-time-picker v-model="data.formInline[item.key]" placeholder="请选择时间":default-time="item.defaultTime || defaultTime" value-format="HH:mm:ss":style="{ width: item.specialWidth || '10vw' }" :readonly="item.isReadonly || false":disabled-date="item.disabledDate" /></template><template v-if="item.type == 'dateNoTimePicker'"><el-date-picker v-model="data.formInline[item.key]" type="date" placeholder="请选择":default-time="item.defaultTime || defaultTime" value-format="YYYY-MM-DD":style="{ width: item.specialWidth || '10vw' }" :readonly="item.isReadonly || false":disabled-date="item.disabledDate" /></template></el-form-item></template><template v-if="item.type == 'multipleSelect'"><div class="select-area"><el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.key"><el-select v-model="data.formInline[item.key]" multiple clearable collapse-tags:placeholder="item.placeholder" popper-class="custom-header" :max-collapse-tags="1"style="width: 100%; height: 50px" @change="(val) => {item.selectOptionChange(val, item);}"><template #header v-if="item.optionsData.length"><el-checkbox v-model="item.checkAll" :indeterminate="item.indeterminate" @change="(val) => {item.handleCheckAll(val, item, data.formInline);}">全部区域</el-checkbox></template><el-option v-for="(optionItem, index) in item.optionsData" :key="index" :label="optionItem.label":value="optionItem.value" /></el-select></el-form-item></div></template><template v-if="item.type == 'radio'"><div class="warning-level" v-if="item.editShow === undefined ? true : !data.isAbled && !item.editShow" :style="{display:(item.onlySingleSelect || item.onlySingleSelect) &&'inline-block',width: item.width || '100%',}"><el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.key"v-if="!item.onlyDrawType && !item.hiddenFormItem"><el-radio-group v-model="data.formInline[item.key]" @change="(val) =>item.linkageAction? finallyLinkageSolve(item): warningLevelChange(val)"><template v-if="item.onlySingleSelect"><el-radio v-for="(aitem, aindex) in item.optionsData" :key="aindex" :label="aitem.value">{{ aitem.label}}</el-radio></template><template v-else><el-radio :label="1">一级</el-radio><el-radio :label="2">二级</el-radio><el-radio :label="3">三级</el-radio></template></el-radio-group></el-form-item><el-form-item :label="'绘制类型:'" :label-width="item.labelWidth" :prop="'drawType'"v-if="!item.onlySingleSelect && data.showDraw"><el-select v-model="data.formInline.drawType" placeholder="请选择" @change="drawTypeChange"style="width: 10vw"><el-option v-for="(optionItem, index) in data.drawTypes" :key="index" :label="optionItem.label":value="optionItem.value" /></el-select></el-form-item></div></template><template v-if="item.isBufferArea && !item.hiddenFormItem"><div class="warning-distance"><template v-for="(bufferItem, bufferIndex) in data.bufferData" :key="bufferIndex"><template v-if="bufferItem.level <= data.formInline.level"><el-form-item :label="bufferItem.label" :label-width="bufferItem.labelWidth" :prop="bufferItem.key"><el-input v-model.trim="data.formInline[bufferItem.key]" style="width: 4.2vw" placeholder="请输入"clearable></el-input><span class="unit">m</span></el-form-item></template></template></div></template><template v-if="item.type && item.type == 'textarea'"><div class="area-remark" :style="{ 'margin-top': item.marginTop || '0px' }"v-show="!item.conditionShow || item.conditionShow(props.formItemsVal)"><el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.key" :class="item.class"><el-input v-model="data.formInline[item.key]" :placeholder="item.placeholder || '请输入备注信息'" type="textarea":disabled="item.isEditAbled && data.isAbled" /></el-form-item></div></template><template v-if="item.type && item.editShow === undefined? item.type == 'coordinate': item.type == 'coordinate' && !data.isAbled && !item.editShow"><div class="cordinate-container"><el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.key"><div class="wrapper"><div class="row-input-box" v-for="(columnItem, index) in tableCloumn" :key="index"><div class="row-name">{{ columnItem.label }}</div><div class="input-item" v-for="(inpItem, ipIndex) in columnItem.inpModelParam" :key="ipIndex"><el-tooltip placement="top" :manual="true" :content="inpItem.msg"><el-input v-model="data.coordinate[inpItem.value]"></el-input></el-tooltip><div class="inp-unit">{{ inpItem.label }}</div></div></div></div><div class="box"><img :src="getAssetsFile('buttons/select_point.png')" @click="drawArea" /></div></el-form-item></div></template><template v-if="item.type == 'sexRadio'"><el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.key"><el-radio-group v-model="data.formInline[item.key]"><el-radio :label="1">男</el-radio><el-radio :label="2">女</el-radio></el-radio-group></el-form-item></template><template v-if="item.editShow === undefined? item.type == 'password': item.type == 'password' && !data.isAbled && !item.editShow"><el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.key" :class="item.class"><el-input v-model="data.formInline[item.key]" placeholder="请输入" type="password" autocomplete="new-password"show-password style="width: 10vw" /></el-form-item></template><template v-if="item.type == 'uploadFile'"><el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.key"><el-input v-model="data.formInline[item.key]" :placeholder="item.placeholder || data.headerResult.placeholder || '请输入'" clearable style="width: 245px" @clear="(value) =>fileClear('fileClear', data.headerResult.fileName, item)"><template #append><div class="opration-box" v-if="item.option"><div v-for="oprationItem in item.option" :key="oprationItem.text"><el-tooltip class="box-item" effect="dark" :content="oprationItem.text" placement="top"><template v-if="oprationItem.info == 'upload'"><el-upload ref="uploadRef" class="upload-demo" :show-file-list="false" :auto-upload="true"accept=".docx,.pdf,.doc" :action="oprationItem.params.importUrl":data="oprationItem.uploadParames" :headers="{ Authorization: store.userInfo.token }":on-success="(info) => handleResult('success', info, oprationItem)" :on-error="(info) => handleResult('error', info)"><img :src="upIcon" style="padding: 0 7px" /></el-upload></template><template v-else><img :src="upIcon" style="padding: 0 7px" @click="handleOpenDialog(oprationItem.params, oprationItem)" /></template></el-tooltip></div></div></template></el-input></el-form-item></template><template v-if="item.type == 'uploadMuliteImg'"><el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.key" :style="{width: item.specialWidth || item.width || '10vw',maxWidth: item.specialWidth || item.width || '10vw',}"><el-upload :auto-upload="false" list-type="picture-card" :on-preview="handlePictureCardPreview":on-change="uploadSingleImg" :limit="5" :file-list="upImgState.fileList"><el-icon v-if="upImgState.fileList.length == 0"><Plus /></el-icon></el-upload><el-dialog v-model="upImgState.dialogVisible"><img :src="upImgState.dialogImageUrl" alt="Preview Image" style="width: 100%" /></el-dialog></el-form-item></template><template v-if="item.type == 'uploadSingleImg'"><el-form-item :label="item.label" :label-width="item.labelWidth" :prop="item.key" :style="{width: item.specialWidth || item.width || '10vw',maxWidth: item.specialWidth || item.width || '10vw',}"><el-upload list-type="picture-card" :limit="1" :file-list="data.formInline[item.key]" :style="{width: item.width || '10vw',height: item.width || '10vw',}" :action="item.option.importUrl" :data="item.option.uploadParames" :show-file-list="true":headers="{ Authorization: store.userInfo.token }":on-success="(info) => uploadSingleImg('success', info, item)":on-error="(info) => uploadSingleImg('error', info)" :before-upload="beforeUploadSingleImg" :before-remove="(info) => beforeRemoveSingleImg('remove', info, item)" :on-preview="handlePictureCardPreview" accept="image/jpeg,image/png" :disabled="data.formInline[item.key] && data.formInline[item.key].length? true: false"><el-icon class="avatar-uploader-icon"><Plus /></el-icon><template #file="{ file }"><div><img class="el-upload-list__item-thumbnail" :src="file.url" alt="" /><span class="el-upload-list__item-actions"><span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)"><el-icon><zoom-in /></el-icon></span><span class="el-upload-list__item-delete" @click="beforeRemoveSingleImg('remove', file, item)"><el-icon><Delete /></el-icon></span></span></div></template></el-upload><el-dialog v-model="upImgState.dialogVisible"><img :src="upImgState.dialogImageUrl" alt="Preview Image" style="width: 100%" /></el-dialog></el-form-item></template></template></el-form>
</template><script setup>
import { max, merge } from "lodash";
import { useAxios } from "@/utils/useAxios";
import axios from "axios";
import { transformObject, getAssetsFile, debounce } from "@/utils";
import { arrayToTreeRec } from "@/utils/utils.js";
import { mapDrawGraph } from "@/utils/map/mapDraw";
import { reactive, ref, watch } from "vue";
import useShoreBasedStore from "@/store/index";
import { BASEUrl } from "@/utils/request";
import { locationPoint } from "@/assets/js/commont";
import upIcon from "@/assets/images/table/upload.png";
const store = useShoreBasedStore();
const defaultTime = new Date();
const tableCloumn = [{label: "经度",prop: "lat",type: "lat",inpModelParam: [{ value: "jd", label: "度", msg: "大于等于0小于等于180的整数" },{ value: "jf", label: "分", msg: "大于等于0小于60的整数" },{ value: "jm", label: "秒", msg: "大于等于0小于60" },],},{label: "纬度",prop: "lon",type: "lon",inpModelParam: [{ value: "wd", label: "度", msg: "大于等于0小于等于90的整数" },{ value: "wf", label: "分", msg: "大于等于0小于60的整数" },{ value: "wm", label: "秒", msg: "大于等于0小于60" },],},
];let props = defineProps({dialogType: {type: String,default: "",},formItems: {type: Array,default: [],},formRules: Object,formItemsVal: Object,MapData: Object,/* tableData: {type: Array,default: [],}, */inputTableData: {type: Array,default: [],},
});
const ruleAreaFormRef = ref();const validateInteger = (rule, value, callback) => {if (Number(value) > 0) callback();else callback(new Error("请输入大于零的数字"));
};const data = reactive({formInline: {drawType: "3", //绘制类型,默认是面level: "2",tempObj: {}, //临时的选中lable},coordinate: {jd: "",jf: "",jm: "",wd: "",wf: "",wm: "",},selectData: [],remoteSelectData: [],count: 0, //下拉列表请求接口的  次数bufferData: [{ label: "一级缓冲距离:", level: 1, key: "buffDistance1" },{ label: "二级缓冲距离:", level: 2, key: "buffDistance2" },{ label: "三级缓冲距离:", level: 3, key: "buffDistance3" },],drawTypes: [{value: "1",label: "点",},{value: "2",label: "线",},{value: "3",label: "面",},],rules: {level: [{ required: true, trigger: "change" }],buffDistance1: [{ required: true, message: "一级缓冲距离不能为空", trigger: "blur" },{ validator: validateInteger, rigger: "blur" },],buffDistance2: [{ required: true, message: "二级缓冲距离不能为空", trigger: "blur" },{ validator: validateInteger, rigger: "blur" },],buffDistance3: [{ required: true, message: "三级缓冲距离不能为空", trigger: "blur" },{ validator: validateInteger, rigger: "blur" },],},cityDefalut: {label: "name",value: "code",children: "cities",},citiesData: [],isAbled: false,showDraw: true,clearFlag: null, //联动值切换时清空初始化标志editSelectValue: "",remoteSelectLoading: false, //可以输入搜索的下拉框headerApi: {},headerResult: {fileName: "",address: "",placeholder: "点击右侧按钮进行上传文件",},resTreeData: [],treeData: [],treeDataList: [],treeLocationSelect: [],storeTrseeData: [],defaultProps: {label: "name",value: "pid",},
});
const emit = defineEmits(["searchChange", "drawTypeChange", "controlDraw"]);
const searchChange = (linkageKey,optionsData,target,linkageDraw,autoPosition,dynamicLinkageKey,list,linkageKeyList,targetItem
) => {if (target?.length == 9) {data.formInline.tempObj = optionsData?.filter((item) => {return item.value === target;});}if (linkageDraw) {data.showDraw = linkageDraw(target);emit("controlDraw", data.showDraw);}if (linkageKey) {if (optionsData.length) {//联动下拉数据静态const result = optionsData.find((item) => {return item.value === target;});data.clearFlag = target;if (result?.url) {const buildData = {url: result.url,optionParam: result.optionParam,};getSelectData(buildData);} else {data.selectData = [{label: "假数据",value: "test",},];}} else {//联动下拉数据动态获取}}if (autoPosition) {let positionTaget = null;if (data.formInline.itemsString && data.formInline.itemsString === target) {positionTaget = JSON.parse(target).guid;} else {positionTaget = target;}mapData.MapData.location(mapData._layer.layers["windFarmArea"],positionTaget,12,mapData._layer);}if (dynamicLinkageKey) {const linkagedTaget = list.find((item) => item.key === dynamicLinkageKey);data.formInline[dynamicLinkageKey] = "";getSelectData(linkagedTaget, {[linkagedTaget.linkageMoreParamKey]: target,});}if (linkageKeyList) {finallyLinkageSolve(targetItem);}emit("searchChange", data.formInline);
};//船舶白名单预警类型的控制逻辑
const checkAll = ref(true);
const isIndeterminate = ref(false);
const handleCheckAllChange = (val, formItemKey) => {data.formInline[formItemKey] = val ? data.checkedAll : [];isIndeterminate.value = false;
};
const handleCheckedChange = (value) => {const checkedCount = value.length;checkAll.value = checkedCount === data.checkedAll.length;isIndeterminate.value =checkedCount > 0 && checkedCount < data.checkedAll.length;
};
// 最终联动逻辑解决方案
const finallyLinkageSolve = async (targetItem) => {if (!targetItem.linkageKeyList?.length) return;const targetLinkItems = props.formItems.filter((aitem) =>targetItem.linkageKeyList.includes(aitem.key));if (targetItem.linkageAction) {if (targetItem.linkOpenSetTimeout) {setTimeout(() => {targetItem.linkageAction(data.formInline[targetItem.key],targetLinkItems,targetItem.optionsData,data.formInline);}, 500);} else {let value =data.formInline[targetItem.key] || data.formInline[targetItem.key] == 0? data.formInline[targetItem.key]: props.formItemsVal[targetItem.key];targetItem.linkageAction(value, targetLinkItems, targetItem.optionsData);}}
};
const drawTypeChange = () => {emit("drawTypeChange", data.formInline.drawType);
};
const validateForm = async () => {let result = true;await ruleAreaFormRef.value.validate((valid, fields) => {const coordinateTarget = props.formItems.find((item) => item.type === "coordinate");if (coordinateTarget && fields) {const fieldsKey = Object.keys(fields);if (fieldsKey.length === 1 && fieldsKey[0] === coordinateTarget.key) {if (coordinateTarget.rule?.[0].required) {for (let coorItem in data.coordinate) {if (!data.coordinate[coorItem]) {return (result = false);}}return (result = true);}}}return (result = valid);});let formObj = Object.assign({}, data.formInline);return {isValidate: result,formData: transformObject(formObj, ["cityValue", "drawType", "BufferArea"]),coordinate: data.coordinate,};
};
const resetForm = () => {ruleAreaFormRef.value.resetFields();
};
const getFormItems = () => {const formItemsKeys = props.formItems.map((aitem) => aitem.key).filter(Boolean);return formItemsKeys;
};
const getFromData = () => data.formInline;//切换预警类型的选择
const warningLevelChange = (val) => {if (val == 1) {data.bufferData.forEach((el) => {if (el.level > val && data.formInline[el.key] != "") {data.formInline[el.key] = "";}});}
};const getSelectData = async (selectOption, moreparams) => {const { resData } = await useAxios({url: selectOption.url,method: "post",param: { pageNo: 1, pageSize: 0, ...moreparams },});if (resData.data && resData.data.length > 0) {let seletData = resData.data.map((item) => {return {label: selectOption.optionParam && item[selectOption.optionParam.label],value:selectOption.optionParam &&selectOption.optionParam.value !== "itemsString"? item[selectOption.optionParam.value]: JSON.stringify(item),};});selectOption.optionsData = seletData;//下拉框需要设置默认值的if (selectOption.default) {data.formInline[selectOption.key] = selectOption.optionsData[0].value;if (selectOption.cablckData) {selectOption.cablckData(selectOption.optionsData);}} else {data.selectData = selectOption.optionsData;if (selectOption.all)selectOption.optionsData.unshift({ label: "全部", value: "" });}} else {selectOption.optionsData = [{ label: "暂无数据", value: "" }];}
};
const getSelectTreeData = async (selectOption, moreparams) => {const { resData } = await useAxios({url: selectOption.url,method: selectOption.methed || "post",param: selectOption?.isNoGetParams? "": { pageNo: 1, pageSize: 10, ...moreparams },},selectOption.headers);const myModifyData = arrayToTreeRec({data: resData.data.filter((el) => el.type !== -1),pid: 0,idKey: "pid",pidKey: "parentId",});data.resTreeData = resData.data;data.treeData = myModifyData;
};
const getListData = async (selectOption, moreparams) => {const { resData } = await useAxios({url: selectOption.url,method: selectOption.methed || "post",param: selectOption?.isNoGetParams ? "" : { ...moreparams },},selectOption.headers);let _topData = [];const _temArr = resData.list ? resData.list : resData.data;let _data = _temArr.map((el) => {el.label = el.name;el.value = el.pid;el.parentId = 0;if (selectOption.isPinxixi) {if (!el.shipCnName) {el.nameAndShipName = el.name;_topData.push(el);} else {el.nameAndShipName = el.name + "【" + el.shipCnName + "】";}}return el;});const _data2 = _data.filter((el) => el.shipCnName);const _data3 = [..._topData, ..._data2];const myModifyData = arrayToTreeRec({data: selectOption.isPinxixi ? _data3 : _data,pid: 0,idKey: selectOption.isPinxixi ? "name" : "pid",pidKey: "parentId",});data.treeDataList = myModifyData;
};const gettreeLocationSelect = async (selectOption, moreparams) => {if (!store.areaList) {const { resData } = await useAxios({url: selectOption.url,method: selectOption.methed || "post",param: selectOption?.isNoGetParams ? "" : { ...moreparams },},selectOption.headers);const _data = resData.list.map((el) => {el.label = el.name;el.value = el.pid;el.parentId = 0;if (selectOption.isPinxixi) {el.nameAndShipName = el.name + "【" + el.shipCnName + "】";}return el;});const myModifyData = arrayToTreeRec({data: _data,pid: 0,idKey: "pid",pidKey: "parentId",});data.treeLocationSelect = myModifyData;} else {const myModifyData = arrayToTreeRec({data: store.areaList.filter((el) => el.type !== -1),pid: 0,idKey: "pid",pidKey: "parentId",});data.treeLocationSelect = myModifyData;data.storeTrseeData = myModifyData;}
};
//切换省市地址
const citySelectChange = (value) => {data.formInline["province"] = value[0];data.formInline["city"] = value[1];
};// 自定义el-select组件的filter-method使得其可以编辑内容
const handleSelectEdit = (filterVal, item) => {if (item.filterable) data.editSelectValue = filterVal;
};
const filtertreeLocationSelect = async (filterVal, item, type) => {if (!filterVal) return;data.remoteSelectLoading = true;if (item.type == 'treeLocationSelect') {const _keyParam = item.filterablePrama.keyParamconst moreparams = { [_keyParam]: filterVal, ...item.filterablePrama };delete moreparams.keyParamawait handleSubFilter(item, moreparams);data.remoteSelectLoading = false;}
};
const handleSubFilter = async (selectOption, moreparams) => {const { resData } = await useAxios({url: selectOption.url,method: selectOption.methed || "post",param: selectOption?.isNoGetParams ? "" : { ...moreparams },},selectOption.headers);let myModifyDataconst _listArr = resData.list ? resData.list : resData.data;if (selectOption.isTrss) {//四川省树状结构const _data = _listArr.map((el) => {el.label = el.name;el.value = el.pid;if (selectOption.isPinxixi) {el.nameAndShipName = el.name + "【" + el.shipCnName + "】";}return el;});myModifyData = arrayToTreeRec({data: _data.filter((el) => el.type !== -1),pid: 0,idKey: "pid",pidKey: "parentId",});} else {myModifyData = _listArr}data.treeLocationSelect = myModifyData;
};
// onMounted(() => {
//     getCtitysData();
//     mapData = props.MapData;
//     data.headerApi = { Authorization: store.userInfo.token }
// })
//获取区域所属省市
const getCtitysData = async () => {let requrl = "./json/city.json";let resData = await axios.get(requrl).then((res) => {return res.data.citiesData;});resData.map((item) => {item.name = item.province;return item;});data.citiesData = resData;
};
const setCheckBoxData = (item) => {setTimeout(() => {data.formInline[item.key] = item.optionCheckAll? ["0", ...item.optionsData.reduce((pre, cre) => [...pre, cre.value], [])]: [];data.checkedAll = data.formInline[item.key];}, 0);checkAll.value = true;isIndeterminate.value = false;
};//从服务器搜索下拉框内容
const remoteMethod = (query, selectItem) => {debounce(async () => {if (!query) return;data.remoteSelectLoading = true;const { resData } = await useAxios({url: selectItem.url,method: "post",param: { [selectItem.searchKey]: query },},selectItem.headers);const finalListData = selectItem.handleSpecialData? selectItem.handleSpecialData(resData.data): resData.data;// data.selectDataAllInfo = finalListData;if (finalListData.length) {data.remoteSelectData = finalListData.map((item) => {if (selectItem.labelMulti)return {label:item[selectItem.optionParam.label]?.trim() ||item[selectItem.optionParam.otherlable]?.trim(),value: item[selectItem.optionParam.value],itemInfo: {...selectItem.labelMulti,addDataInfo: item,},};return {label:item[selectItem.optionParam.label]?.trim() ||item[selectItem.optionParam.otherlable]?.trim(),value: item[selectItem.optionParam.value],itemInfo: JSON.stringify(item),};});} else {//避免重复插入多个let isAdd = data.remoteSelectData.filter((item) => item.value == query);if (!isAdd.length) {data.remoteSelectData.unshift({ label: query, value: query });}}data.remoteSelectLoading = false;setTimeout(() => {data.remoteSelectLoading = false;}, 1000);}, 500)();
};
const getRemoteSelect = async (selectOption, moreparams) => {if (props.dialogType == "edit") {selectOption.pageInfo = {[selectOption.searchKey]: props.formItemsVal[selectOption.searchKey],};}const { resData } = await useAxios({url: selectOption.url,method: "post",param: selectOption.pageInfo ?? { pageNo: 1, pageSize: 0, ...moreparams },},selectOption.headers);const finalListData = selectOption.handleSpecialData? selectOption.handleSpecialData(resData.data): resData.data;// data.selectDataAllInfo = finalListData;if (finalListData.length) {data.remoteSelectData = finalListData.map((item) => {if (selectOption.labelMulti)return {label:item[selectOption.optionParam.label]?.trim() ||item[selectOption.optionParam.otherlable]?.trim(),value: item[selectOption.optionParam.value],itemInfo: {...selectOption.labelMulti,addDataInfo: item,},};return {label:item[selectOption.optionParam.label]?.trim() ||item[selectOption.optionParam.otherlable]?.trim(),value: item[selectOption.optionParam.value],};});}
};const watchKeys = reactive([]);
watch(() => props.formItemsVal,(val) => {data.isAbled = false;if (val) {data.isAbled = true;data.formInline = {};data.formInline = Object.assign({}, val);}},{ immediate: true }
);
watch(() => props.formItems,(val) => {//构造需要动态显示的表单下拉项数据val.forEach((item) => {if (item.key) {//某些值是否需要监视if (item.watchChage) {watchKeys.push(item);}//!props.formItemsVal代表添加if (!props.formItemsVal) {data.formInline[item.key] =item.defaultValue !== undefined && item.defaultValue !== null? item.defaultValue: "";}if (item.type == "select") {if (item.optionsData.length == 0 && item.url) {// 动态获取下拉框内容getSelectData(item);} else if (item.defaultVal) {data.formInline[item.key] = item.defaultVal;} else if (item.defaultVal && !props.formItemsVal) {data.formInline[item.key] = item.defaultVal;}if (item.linkageKey) {if (props.formItemsVal) {data.formInline[item.key] = props.formItemsVal[item.key];searchChange(item.linkageKey,item.optionsData,data.formInline[item.key],item.linkageDraw);}}}if (item.type == "selectTree") {if (item.optionsData.length == 0 && item.url) {// 动态获取下拉框内容getSelectTreeData(item);}}if (item.type == "treeMuiltSelect") {if (item.optionsData.length == 0 && item.url) {data.tempObj = item;getListData(item, item.moreparams);} else {data.treeDataList = item.optionsData;}}if (item.type == "treeLocationSelect") {if (item.optionsData.length == 0 && item.url) {data.tempObj = item;gettreeLocationSelect(item, item.moreparams);}}if (item.type == "textarea" && item.defaultVal)props.dialogType !== "edit" &&(data.formInline[item.key] = item.defaultVal);if (item.type == "checkBox") {data.formInline[item.key] = [];//item.optionCheckAll==true,默认填充一个全部类型的值为0setCheckBoxData(item);}if (item.type == "remoteSelect") {getRemoteSelect(item);}if (item.rule) {merge(data.rules, { [item.key]: item.rule });}// 联动逻辑最终解决方案(优于之前的分散的联动逻辑)finallyLinkageSolve(item);}});},{immediate: true,}
);let instance = null; //绘制地理要素
let drawLayer = null; //绘制时临时创建的图层
let mapData = {};
//初始化绘制
const drawArea = () => {initDraw();drawLayer && drawLayer.getSource().clear();instance = mapDrawGraph(mapData.MapData,drawLayer,"Point",handlerDarwResult);
};
const initDraw = () => {let _Map = mapData;instance && _Map.MapData.GlobalMap.removeInteraction(instance);drawLayer = _Map._layer.creatTempLayer({layerName: "drawTempPoint",title: "绘制临时图层选点",zindex: 10,});
};
const handlerDarwResult = (drawResult) => {if (drawResult.coods.length !== 0) {let myCo = Object.assign({}, drawResult.coods);data.coordinate = myCo[0];for (let key in data.coordinate) {data.coordinate[key].indexOf("") >= 0 &&(data.coordinate[key] = data.coordinate[key].trim());}}instance = null;
};
const handleOpenDialog = (row, item) => {item.getRowInfo({ ...row, ...item, ...data.formInline });
};
const uploadRef = ref();
const handleResult = (state, info, item) => {if (state === "success") {const { data: resData } = info;const { address, fileName } = resData;const _fileName = !fileName? "附件名称" + resData.split("/")[resData.split("/").length - 1]: fileName;if (!fileName) {data.formInline[item.key] = resData;data.headerResult.fileName = resData;} else {data.formInline.fileName = _fileName;data.formInline.address = address;}item.getRowInfo({ ...resData, ...item, ...data.formInline });}ElMessage({type: state,message: `导入文件上传${state === "success" ? "成功" : "失败"}`,});
};
const fileClear = (state, info, item) => {if (state === "fileClear") {data.formInline[item.key] = "";const params = {fileName: info,fileType: item.option[0].uploadParames.fileType,};item.clearFunc(params);}
};
const uploadSingleImg = (state, info, item) => {if (state === "success") {const { data: resData } = info;item.getRowInfo(resData);upImgState.dialogImageUrl = BASEUrl + "/file/" + resData;const _fileList = data.formInline[item.key] || [];_fileList.unshift({ url: upImgState.dialogImageUrl });data.formInline[item.key] = _fileList;}ElMessage({type: state,message: `单个文件上传${state === "success" ? "成功" : "失败"}`,});
};
const beforeUploadSingleImg = (rawFile) => {if (!rawFile) return false;if (rawFile.type !== "image/jpeg" && rawFile.type !== "image/png") {ElMessage.error("文件类型须为图片格式!");return false;} else if (rawFile.size / 1024 / 1024 > 10) {ElMessage.error("文件大小须小于10MB!");return false;}return true;
};
const beforeRemoveSingleImg = (state, info, item) => {if (state === "remove") {let _fileName = "";if (!info.response) {_fileName = info.url.split("/")[info.url.split("/").length - 1];} else {_fileName =info.response.data.split("/")[info.response.data.split("/").length - 1];}const _fileType = item.option.uploadParames.fileType;item.removeImg({title: "删除图片",fileName: _fileName,fileType: _fileType,});data.formInline[item.key] = [];}return true;
};
const upImgState = reactive({dialogVisible: false, //预览弹框显示与否dialogImageUrl: "",fileList: [],
});
const handlePictureCardPreview = (file) => {upImgState.dialogImageUrl = file.url || BASEUrl + file.url;upImgState.dialogVisible = true;
};
const handlechange = (val, limit, isJump) => {if (isJump) {let _data = {};let _val = val;if (Array.isArray(val)) {_val = _val[_val.length - 1];}_data = data.resTreeData.find((item) => item.pid == _val);if (!_data) return;locationPoint(_data.geom);}if (!limit) return;if (limit == "有联动") {getListData(data.tempObj, {...data.tempObj.moreparams,areaIdCondition: val,});}if (limit == "有联动2") {getListData(data.tempObj, {organization: val,});}
};
const handleSelectchange = (val, item) => {const _findObj = store.areaList.find((el) => el.pid == val[val.length - 1]);if (_findObj) {const isOk =Array.isArray(_findObj.ferryPortAreaVos) &&_findObj.ferryPortAreaVos.length > 0;if (!isOk) {data.formInline[item.key] = data.formInline[item.key].filter((el) => {return el !== val[val.length - 1];});ElMessage.warning("该区域下暂无渡口!");}}
};
const blur = (item, type) => {if (item.type === "treeLocationSelect") {zeroSelectData();}
};
const zeroSelectData = () => {data.treeLocationSelect = data.storeTrseeData
}
defineExpose({validateForm,resetForm,getFormItems,getFromData,getListData,
});
</script><style lang="scss" scoped>
.area-from {display: flex;justify-content: space-between;flex-wrap: wrap;:deep(.el-form-item) {margin-right: 0px;// margin-bottom: 20px;max-width: 50%;.el-form-item__label {color: #fff;font-size: 16px;padding: 0px;padding-right: 10px;min-width: 100px;display: flex;justify-content: flex-end;align-items: center;line-height: 22px;}.el-form-item__error {white-space: nowrap;}.el-input__wrapper {background: rgba(26, 136, 198, 0.3);box-shadow: none;border: 1px solid #62a3ff;border-radius: unset;.el-input__inner {color: #fff;}.el-input__inner::placeholder {color: #d0eeff;}.el-input__suffix {color: #fff;}}.el-select__wrapper {background-color: #08387e;box-shadow: 0 0 0 1px #62a3ff inset;}.el-select__placeholder {color: #fff;}.el-radio__label {color: #fff;}.el-select .el-input__wrapper.is-focus {box-shadow: unset !important;}.el-input--suffix {color: #d0eeff !important;}.unit {color: #fff;margin-left: 5px;}.el-radio-group {min-width: 300px;}}.ship-white-warn {max-width: 100% !important;.warn-type {display: flex;align-items: center;:deep(.el-checkbox-group) {margin-left: 10px;}}}.select-area {width: 100%;>.el-form-item {width: 100%;max-width: 100%;margin-right: 0;}:deep(.select-trigger) {height: 100%;.el-input {height: 100%;}}:deep(.el-select__tags .el-tag--info) {background-color: rgb(16, 159, 204) !important;color: #ffffff;height: 30px;}:deep(.el-tag .el-tag__close) {color: #ffffff;}}.area-remark {// margin-top: 10px;width: 100%;>.el-form-item {width: 100%;max-width: 100%;margin-right: 0;.el-textarea {// width: 91%;:deep(.el-textarea__inner) {background-color: rgba(26, 136, 198, 0.3);border: 1px solid #62a3ff;border-radius: unset;outline: none;box-shadow: unset;color: #ffffff;&::placeholder {color: #d0eeff;}}}}}.warning-level {display: flex;justify-content: space-between;width: 100%;}.warning-distance {width: 100%;display: flex;justify-content: space-between;:deep(.el-form-item) {.el-form-item__content {flex-wrap: nowrap;}}}.cordinate-container {display: flex;align-items: center;:deep(.el-form-item) {max-width: 100%;margin-right: 0;.el-form-item__content {flex-wrap: nowrap;}}.wrapper {// width: 80%;background: rgba(26, 136, 198, 0.3) !important;border: 1px solid #62a3ff;padding: 6px 16px;display: flex;// flex-wrap: nowrap;.row-input-box {display: flex;align-items: center;&:first-child {margin-right: 22px;}.row-name {margin-right: 8px;white-space: nowrap;font-size: 14px;font-family: Microsoft YaHei-Regular, Microsoft YaHei;font-weight: 400;color: #d0eeff;line-height: 22px;}.input-item {display: flex;align-items: center;.inp-unit {margin: 0 6px;font-size: 12px;font-family: Microsoft YaHei-Regular, Microsoft YaHei;font-weight: 400;color: #52cbff;line-height: 19px;}.el-input {// width: 50px;:deep(.el-input__wrapper) {border: unset;border-radius: unset;background: #074665;padding: 1px;>input {text-align: center;}}}}}}.box {cursor: pointer;margin-left: 5px;display: inherit;align-items: inherit;}}.psw_home {padding-bottom: 15px;}.opration-box,.render-box {display: flex;justify-content: center;span {margin-right: 5px;}img {width: 16px;}}:deep(.el-input-group__append) {border-left: 0;border-top-left-radius: 0;border-bottom-left-radius: 0;box-shadow: 0 1px 0 0 #62a3ff inset, 0 -1px 0 0 #62a3ff inset,-1px 0 0 0 #62a3ff inset;background-color: #075480;padding: 0px 10px;background-color: #095680;}.avatar-uploader {width: 50px;height: 50px;display: flex;justify-content: center;align-items: center;border: 1px dashed #d9d9d9;img {width: 98%;height: 98%;object-fit: cover;}}
}
</style>
<style lang="scss">
.custom-header {.el-checkbox {display: flex;height: unset;}// .el-select-dropdown.is-multiple .el-select-dropdown__item.selected {//     color: #606266 !important;// }.el-select-dropdown.is-multiple .el-select-dropdown__item.selected::after {// background-color: rgb(47, 255, 127) !important;width: 25px;height: 25px;font-weight: bolder;font-size: 16px;}
}
</style>
调用组件AreaFormItem.vue的方式:
//调用组件
<template><AreaFormItemref="areaForm":formItems="formItems":formItemsVal="state.formItemsVal"/>
</template><script setup>
import { nextTick, onMounted, reactive, ref, watch } from "vue";
import AreaFormItem from "@/components/Form/AreaFormItem.vue";
const state = reactive({formItemsVal: {},
});
//备注:state.formItemsVal用于表单回显
const formItems = [{type: "datePicker",label: "签发日期",key: "dateOfIssue",rule: [{required: true,message: "签发日期不能为空!",trigger: "blur",},],},{type: "datePicker",label: "截止日期",key: "deadline",rule: [{required: true,message: "截止日期不能为空!",trigger: "blur",},],},{type: "select",label: "文件类型",key: "type",optionsData: [{label: "船舶经营许可证",value: 0,},{label: "船舶管理人许可证",value: 7,},],defaultVal: 0,rule: [{required: true,message: "文件类型不能为空!",trigger: "blur",},],},{type: "uploadSingleImg",label: "渡船证书",key: "file",option: {uploadParames: {fileType: 0, //渡船相关证书},img_bg: "table/upload.png",info: "upload",text: "上传",importUrl: BASEUrl + "/data/file/upload",},width: "100%",getRowInfo: (data) => {console.log("上传图片的结果", data);state.formItemsVal.file = data;},removeImg: (data) => {api.fileApi.deleteFile({ fileName: data.fileName, fileType: data.fileType }).then((res) => {if (res.status === 200 && res.data.code === 200) {ElMessage({type: "success",message: "删除成功",});}}).catch((err) => {console.log(err);});},rule: [{required: true,message: `${state.pageTheme}不能为空!`,trigger: "blur",},],},
];
<script>

 写到这儿就实现了表单组件的封装,根据调用组件传参的formItems变量的type类型渲染不同的表单组件例如:input、select等组件。。。最终随着项目的进行,表单组件的其他类型:

'location', 'medium','password', 'sexRadio', 'coordinate','radio','textarea','multipleSelect', 'uploadFile','uploadSingleImg',也会封装。

http://www.xdnf.cn/news/16640.html

相关文章:

  • STM32时钟源
  • GaussDB as的用法
  • 【氮化镓】GaN同质外延p-i-n二极管中星形与三角形扩展表面缺陷的电子特性
  • 力扣 hot100 Day58
  • LeetCode 2044.统计按位或能得到最大值的子集数目:二进制枚举/DFS回溯(剪枝)
  • 介绍一下static关键字
  • IP协议解析:从寻址到路由
  • MCP协议全景解析:从工业总线到AI智能体的连接革命
  • 【基础篇三】WebSocket:实时通信的革命
  • CDN架构全景图
  • 硕博电子大功率IO模块
  • opencv学习(轮廓检测)
  • 【论文阅读】Safety Alignment Should Be Made More Than Just a Few Tokens Deep
  • 微型化IMU如何突破无人机与机器人的性能边界?
  • 数据开源 | “白虎”数据集首批开源,迈出百万数据征途第一步
  • 医疗人工智能高质量数据集和语料库建设路径探析
  • linux安装zsh,oh-my-zsh,配置zsh主题及插件的方法
  • 3. Socket 编程 TCP
  • mp快速入门
  • 《LeetCode 热题 100》整整 100 题量大管饱题解套餐 中
  • 端到端的核心区别点
  • 【RH134 问答题】第 6 章 管理 SELinux 安全性
  • JSBridge原理与实现全解析
  • iOS WebView 调试实战,第三方脚本加载失败与内容安全策略冲突问题排查指南
  • Resilience4j 实战—使用方式及配置详解
  • Centos 7 命令:ip addr
  • MySQL复习
  • YOLOv8 基于RTSP流目标检测
  • OSPF路由协议
  • SpringCloud01——项目演变、微服务远程调用三种方式、springcloud介绍、nacos注册中心