背景:

在前端系统开发中,系统页面涉及到的表单组件比较多,所以进行了简单的封装。封装的包括一些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.keyParam
    const moreparams = { [_keyParam]: filterVal, ...item.filterablePrama };
    delete moreparams.keyParam
    await 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 myModifyData
  const _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,默认填充一个全部类型的值为0
          setCheckBoxData(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>
    <AreaFormItem
      ref="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',也会封装。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐