vue3表单嵌套表格实现增删改查,并表单校验。

<template>
  <!-- 嵌套表单 -->
  <el-table
    :class="`_table_${popIndex}`"
    :data="modelValue"
    :row-key="_options.rowKey"
    :stripe="_options.stripe"
    :tooltipEffect="_options.tooltipEffect"
    :show-header="_options.showHeader"
    :row-style="_options.rowStyle"
    :border="_options.border"
    :size="_options.size"
    table-layout="auto"
    resizable
  >
    <el-table-column width="80" align="center">
      <template #default="scope">
        <div class="styleMove" :id="`el_row_${scope.column.rowSpan}`">
          <el-icon :size="24" color="rgb(182, 178, 178)"><Operation /></el-icon>
        </div>
      </template>
    </el-table-column>
    <el-table-column
      v-for="(
        { label, width, searchType, prop, defaultData, config }, index
      ) in rowFormColumns"
      :key="index"
      :prop="prop"
      :label="label"
      :width="width"
    >
      <template #default="scope">
        <el-form-item
          class="styleForm"
          :prop="`${propKey}.${popIndex}.${listKey}.${scope.$index}.${prop}`"
          :rules="
            modelValue[scope.$index]?.evaluateResultType ===
              'skill_evaluate_result_type_1' && searchType === 'number'
              ? {
                  required: false
                }
              : formRules[prop]
          "
        >
          <CommonSelect
            v-if="searchType === 'select'"
            v-model="modelValue[scope.$index]"
            :prop="prop"
            :lan="lan"
            :placeholder="validateMsg.select[lan]"
            :config="{ ...config?.selectConfig }"
          >
          </CommonSelect>

          <el-select
            v-if="searchType === 'newselect'"
            collapse-tags
            collapse-tags-tooltip
            v-model="modelValue[scope.$index][prop]"
            :placeholder="validateMsg.select[lan]"
            :multiple="config?.selectConfig?.multiple"
          >
            <el-option
              v-for="(item, vindex) in rowFormColumns[2].config?.selectConfig
                .options"
              :key="vindex"
              :label="item.text[lan]"
              :value="item.value"
            />
          </el-select>

          <ElInputNumber
            v-if="searchType === 'number'"
            :placeholder="validateMsg.input[lan]"
            v-model="modelValue[scope.$index][prop]"
            :min="config?.min || 0"
            :max="config?.max || 10"
            :disabled="
              modelValue[scope.$index]?.evaluateResultType ===
                'skill_evaluate_result_type_1' || config.selectConfig?.disabled
            "
            :step="1"
            step-strictly
          />
          <!-- <el-input
            v-if="searchType === 'number'"
            placeholder="请输入"
            :min="config?.min || 0"
            :max="config?.max || 10"
            type="number"
            :disabled="
              modelValue[scope.$index]?.evaluateResultType ===
                'skill_evaluate_result_type_1' || config.selectConfig?.disabled
            "
            oninput="value=value.replace(/[^\d]/g,'')"
            v-model.number="modelValue[scope.$index][prop]"
          ></el-input> -->

          <ElInput
            v-else-if="searchType === 'input' && !hasObj(defaultData)"
            v-model="modelValue[scope.$index][prop]"
            :placeholder="validateMsg.input[lan]"
            style="width: 100%"
            :type="config?.types"
            :rows="config?.rows"
            clearable
          />
          <ElInput
            v-else-if="searchType === 'input' && hasObj(defaultData)"
            v-model="modelValue[scope.$index][prop][lan]"
            :placeholder="validateMsg.input[lan]"
            style="width: 100%"
            :type="config?.types"
            :rows="config?.rows"
            clearable
          />
        </el-form-item>
      </template>
    </el-table-column>
    <!-- 自适应占位 -->
    <el-table-column
      fixed="right"
      width="100"
      v-if="options?.showDel"
      label="操作"
      align="center"
    >
      <template #default="{ $index }">
        <div class="styleDel">
          <ElButton
            :lan="commonStore.lan"
            type="danger"
            link
            size="small"
            @click="handelReduceRow($index)"
          >
            删除
          </ElButton>
        </div>
      </template>
    </el-table-column>
  </el-table>

  <ElRow v-if="options?.showAdd">
    <ElCol :span="24" justify="center" align="center">
      <div class="styleAdd" @click="handelAddRow">添加</div>
    </ElCol>
  </ElRow>
</template>

<script lang="ts" setup>
import { Operation } from '@element-plus/icons-vue';
import CommonSelect from '@common/components/CommonSelect.vue';
import { useCommonStore } from '@common/store';
import { validateMsg } from '@common/utils';

interface FormOptionConfigType {
  initAdd?: boolean;
  showAdd?: boolean;
  showDel?: boolean;
}
interface PropsType {
  modelValue: any;
  propKey?: string;
  popIndex: number;
  listKey: string;
  formRules: IObject;
  rowFormColumns: IObject;
  options?: FormOptionConfigType;
}
interface EmitEvent {
  (e: 'handelAddRow', vindex: number, obj: IObject): void;
}
const route = useRoute();
const emit = defineEmits<EmitEvent>();
const props = defineProps<PropsType>();
const {
  modelValue,
  propKey,
  popIndex,
  listKey,
  formRules,
  rowFormColumns,
  options
} = toRefs(props);
const commonStore = useCommonStore();
const { lan } = storeToRefs(commonStore);

// 二层嵌套配置
const _options = {
  rowKey: 'id',
  stripe: false,
  border: true,
  size: 'small',
  tooltipEffect: 'dark',
  showHeader: true,
  showPagination: true,
  rowStyle: () => ({}) // 行样式 cursor:pointer
};

// 行数据格式化和序列化
const initRowData: IObject = JSON.parse(
  JSON.stringify(rowFormColumns.value)
).reduce(
  (formObj: IObject, initObj: IObject) => (
    (formObj[initObj.prop] = initObj.defaultData), formObj
  ),
  {}
);
const hasObj = computed(
  () =>
    (val: LpkObjectType | any): boolean =>
      Object.prototype.toString.call(val) === '[object Object]'
);

const handelAddRow = () => {
  const obj = JSON.parse(JSON.stringify(initRowData));
  emit('handelAddRow', popIndex.value, obj);
};
if (!route.query.id) {
  handelAddRow();
}
const handelReduceRow = (index: number) => {
  modelValue.value.splice(index, 1);
};
</script>

<style lang="scss" scoped>
.styleForm {
  margin-top: 15px;
}

.styleDel {
  display: flex;
  justify-content: center;
  align-items: center;
}

.styleAdd {
  border: 1px dashed rgb(182, 178, 178);
  color: rgb(182, 178, 178);
  margin: 20px 0;
  padding: 4px 0;
  border-radius: 4px;
}

.styleMove:hover,
.styleAdd:hover {
  cursor: pointer;
}

.el-table {
  --el-table-row-hover-bg-color: none;
}
</style>

以上子组件。下面父组件

<template>
  <ElForm
    ref="ruleFormRef"
    class="edit-form"
    :model="formData"
    :rules="formRules"
  >
    <ElRow class="rowItem">
      <el-card class="fromCard">
        <template #header>
          <div>
            <span class="title">基本信息</span>
          </div>
        </template>
        <ElCol :span="12">
          <ElFormItem
            label="评价表名称"
            :label-width="labelWidth"
            prop="evaluateName"
          >
            <ElInput
              clearable
              :placeholder="validateMsg.input[lan]"
              v-model="formData.evaluateName"
            />
          </ElFormItem>
        </ElCol>
        <ElCol :span="12">
          <ElFormItem
            label="适用公司"
            :label-width="labelWidth"
            prop="departmentId"
          >
            <CommonSelect
              v-model="formData"
              prop="departmentId"
              @change="departmenFun"
              :lan="lan"
              :config="{
                filterable: true,
                optionKV: {
                  labelKey: 'companyName',
                  valueKey: 'id'
                },
                opsQueryFunc: GlobalCommonApi.getAllCompanys
              }"
            />
          </ElFormItem>
        </ElCol>

        <ElCol :span="12">
          <ElFormItem
            label="适用岗位"
            :label-width="labelWidth"
            prop="stationIds"
          >
            <CommonSelect
              v-model="formData"
              prop="stationIds"
              :lan="lan"
              :config="{
                multiple: true,
                filterable: true,
                options: stationIdOps,
                optionKV: {
                  labelKey: 'actualPostCodeAndName',
                  valueKey: 'id'
                }
              }"
            />
          </ElFormItem>
        </ElCol>
        <ElCol :span="12">
          <ElFormItem
            label="L1鉴定方式"
            :label-width="labelWidth"
            prop="l1CheckMode"
          >
            <CommonSelect
              v-model="formData"
              prop="l1CheckMode"
              :lan="lan"
              :config="{
                filterable: true,
                fixedParam: {
                  dicType: 'gshr_skill_evaluate_l1_check_mode'
                },
                optionKV: {
                  labelKey: 'text',
                  valueKey: 'value'
                },
                opsQueryFunc: GlobalCommonApi.starLevelSelectDic
              }"
            />
          </ElFormItem>
        </ElCol>
        <ElCol :span="12">
          <ElFormItem label="状态" :label-width="labelWidth" prop="state">
            <CommonSelect
              v-model="formData"
              prop="state"
              :lan="lan"
              :config="{
                filterable: true,
                fixedParam: {
                  dicType: 'gshr_skill_config_state'
                },
                optionKV: {
                  labelKey: 'text',
                  valueKey: 'value'
                },
                opsQueryFunc: GlobalCommonApi.starLevelSelectDic
              }"
            />
          </ElFormItem>
        </ElCol>
        <ElCol :span="20">
          <ElFormItem label="填写指南" :label-width="labelWidth" prop="guide">
            <ElInput
              v-model="formData.guide"
              :placeholder="validateMsg.input[lan]"
              autocomplete="off"
              :rows="8"
              type="textarea"
              style="width: 100%"
            />
          </ElFormItem>
        </ElCol>
      </el-card>
    </ElRow>

    <el-card class="fromCardTop">
      <template #header>
        <div class="applicable">
          <span class="title">评价表设置</span>
          <div>
            适用场景:
            <CommonSelect
              style="width: 200px"
              v-model="formData"
              prop="applicable"
              @change="applicableFun"
              :lan="lan"
              :config="{
                filterable: true,
                fixedParam: {
                  dicType: 'gshr_skill_evaluate_scene'
                },
                optionKV: {
                  labelKey: 'text',
                  valueKey: 'value'
                },
                opsQueryFunc: GlobalCommonApi.starLevelSelectDic
              }"
            />
          </div>
        </div>
      </template>
      <div :span="24" v-for="(item, i) in formData.saveRequests" :key="`_${i}`">
        <p>{{ item.title }}</p>
        <CommenForm
          v-model="formData.saveRequests[i]['itemSaveRequests']"
          propKey="saveRequests"
          :popIndex="i"
          :listKey="item.key"
          :formRules="formRules"
          :rowFormColumns="getSearchColumns[i]"
          :options="formOptionConfig"
          @handelAddRow="handelAddRow"
        >
        </CommenForm>
      </div>
    </el-card>
    <div class="btnFooter">
      <span class="dialog-footer">
        <ElButton
          :loading="loding"
          @click="router.push({ path: '/sys-manage/eva-table-config' })"
          >{{ $lpk('Cancel', lan) }}</ElButton
        >
        <ElButton type="primary" @click="confirm" :loading="loding">
          {{ $lpk('Confirm', lan) }}
        </ElButton>
      </span>
    </div>
  </ElForm>
</template>
<script lang="ts" setup>
import CommonSelect from '@common/components/CommonSelect.vue';
import CommenForm from './CommonForm.vue';
import { useCommonStore } from '@common/store';
import type { FormInstance } from 'element-plus';
import { validateRequire, validateMsg } from '@common/utils';
import { GlobalCommonApi } from '@common/reqApi';
import { useStarlevelStore } from '@skills/store';
import { SkillsApi } from '@skills/reqApi';

interface IDialogState {
  formData: IObject;
  evaluateSceneOps: IObject[];
  stationIdOps: IObject[];
  oneData: IObject[];
  twoData: IObject[];
  oneNewData: IObject[];
  twoNewData: IObject[];
}
const starStore = useStarlevelStore();
const { contentOneOPs, contentTwoOPs, resultTypeOps } = storeToRefs(starStore);
const route = useRoute();
const ruleFormRef = ref<FormInstance>();
const commonStore = useCommonStore();
const { lan } = storeToRefs(commonStore);
const loding = ref(false);
const router = useRouter();
const labelWidth = '120px';
const formOptionConfig = {
  initAdd: true,
  showAdd: true,
  showDel: true
};

// form提交相关
const initData = {
  applicable: '',
  departmentId: '',
  stationIds: [],
  state: 'skill_config_state_1',
  l1CheckMode: '',
  guide: '',
  evaluateName: '',
  saveRequests: []
};

let state = reactive<IDialogState>({
  formData: JSON.parse(JSON.stringify(initData)),
  evaluateSceneOps: [],
  stationIdOps: [],
  oneData: [],
  twoData: [],
  oneNewData: [],
  twoNewData: []
});

const {
  formData,
  evaluateSceneOps,
  stationIdOps,
  oneData,
  twoData,
  oneNewData,
  twoNewData
} = toRefs(state);
GlobalCommonApi.starLevelSelectDic({
  dicType: 'gshr_skill_evaluate_scene'
}).then(res => {
  evaluateSceneOps.value = res.data;
});
const handelAddRow = (vindex: number, obj: IObject) => {
  formData.value.applicable = '';
  applicableFun({ val: '' });
  if (vindex === 0) {
    oneData.value.push(obj);
  } else {
    twoData.value.push(obj);
  }
};
const applicableFun = (data: IObject) => {
  oneNewData.value = [];
  twoNewData.value = [];
  oneData.value.forEach((element: IObject) => {
    const flagIndex = element.evaluateScenes.findIndex((value: string) => {
      return value === data.val;
    });
    if (flagIndex !== -1) {
      oneNewData.value.push(element);
    }
  });
  twoData.value.forEach((element: IObject) => {
    const flagIndex = element.evaluateScenes.findIndex((value: string) => {
      return value === data.val;
    });
    if (flagIndex !== -1) {
      twoNewData.value.push(element);
    }
  });
  formData.value = {
    ...formData.value,
    saveRequests: [
      {
        title: '项目一:掌握应知的理论知识',
        key: 'itemSaveRequests',
        evaluateItem: 'skill_evaluate_item_1',
        itemSaveRequests: data.val ? oneNewData.value : oneData.value
      },
      {
        title: '项目二:按质量要求安全操作',
        key: 'itemSaveRequests',
        evaluateItem: 'skill_evaluate_item_2',
        itemSaveRequests: data.val ? twoNewData.value : twoData.value
      }
    ]
  };
};

if (route.query.id) {
  SkillsApi.getConfigEvaluate({ id: route.query.id }).then(async res => {
    const {
      departmentId,
      stationIds,
      state,
      l1CheckMode,
      guide,
      evaluateName,
      saveRequests
    } = res.data;
    stationIdOps.value = res.data?.stationMessage;
    await saveRequests.forEach((element: IObject) => {
      if (element.evaluateItem === 'skill_evaluate_item_1') {
        oneData.value.push(...element.itemSaveRequests);
      } else {
        twoData.value.push(...element.itemSaveRequests);
      }
    });
    formData.value = {
      departmentId,
      stationIds,
      state,
      l1CheckMode,
      guide,
      evaluateName,
      saveRequests: [
        {
          title: '项目一:掌握应知的理论知识',
          key: 'itemSaveRequests',
          evaluateItem: 'skill_evaluate_item_1',
          itemSaveRequests: oneData.value
        },
        {
          title: '项目二:按质量要求安全操作',
          key: 'itemSaveRequests',
          evaluateItem: 'skill_evaluate_item_2',
          itemSaveRequests: twoData.value
        }
      ]
    };
  });
} else {
  formData.value = {
    ...formData.value,
    saveRequests: [
      {
        title: '项目一:掌握应知的理论知识',
        key: 'itemSaveRequests',
        evaluateItem: 'skill_evaluate_item_1',
        itemSaveRequests: oneData.value
      },
      {
        title: '项目二:按质量要求安全操作',
        key: 'itemSaveRequests',
        evaluateItem: 'skill_evaluate_item_2',
        itemSaveRequests: twoData.value
      }
    ]
  };
}
const departmenFun = (data: IObject) => {
  formData.value.stationIds = [];
  const item: IObject = data?.ops?.find(
    (item: IObject) => item.id == data?.val
  );
  GlobalCommonApi.queryAllActualPost({
    companyCode: item?.departmentCode
  }).then((res: IObject) => {
    stationIdOps.value = res.data;
  });
};
const formRules: IObject = computed(() => {
  return {
    evaluateName: [validateRequire(lan.value, 'input')],
    departmentId: [validateRequire(lan.value, 'select')],
    stationIds: [validateRequire(lan.value, 'select')],
    state: [validateRequire(lan.value, 'select')],
    l1CheckMode: [validateRequire(lan.value, 'select')],
    // 内层嵌套字段
    evaluateContent: [validateRequire(lan.value, 'select')],
    evaluateStandard: [validateRequire(lan.value, 'input')],
    evaluateScenes: [validateRequire(lan.value, 'select')],
    evaluateResultType: [validateRequire(lan.value, 'select')],
    score: [validateRequire(lan.value, 'input')]
  };
});

const confirm = () => {
  formData.value.applicable = '';
  applicableFun({ val: '' });
  ruleFormRef?.value?.validate((valid, fields) => {
    if (valid) {
      loding.value = true;
      if (route.query.id) {
        SkillsApi.getConfigEvaluateEdit({
          ...formData.value,
          id: route.query.id
        })
          .then(() => {
            ElMessage.success('编辑成功!');
            router.push({ path: '/sys-manage/eva-table-config' });
          })
          .finally(() => {
            loding.value = false;
          });
      } else {
        SkillsApi.getConfigEvaluateAdd(formData.value)
          .then(() => {
            ElMessage.success('添加成功!');
            router.push({ path: '/sys-manage/eva-table-config' });
          })
          .finally(() => {
            loding.value = false;
          });
      }
    } else {
      console.log('error submit!', fields);
    }
  });
};

const getSearchColumns = computed(() => {
  let newEvaluateSceneOps = [...evaluateSceneOps.value];
  if (formData.value.l1CheckMode === 'skill_evaluate_l1_check_mode_1') {
    newEvaluateSceneOps = evaluateSceneOps.value.filter(item => {
      return item.value !== 'skill_evaluate_scene_1';
    });
  }
  return [
    [
      {
        prop: 'evaluateContent',
        width: '250',
        defaultData: '',
        label: '内容',
        searchType: 'select',
        config: {
          selectConfig: {
            filterable: true,
            options: contentOneOPs.value,
            optionKV: {
              labelKey: 'name',
              valueKey: 'code'
            }
          }
        }
      },
      {
        prop: 'evaluateStandard',
        width: '250',
        defaultData: '',
        label: '评价基准',
        searchType: 'input',
        config: {}
      },
      {
        prop: 'evaluateScenes',
        width: '250',
        defaultData: [],
        label: '适用场景',
        searchType: 'newselect',
        config: {
          selectConfig: {
            multiple: true,
            filterable: true,
            options: newEvaluateSceneOps,
            optionKV: {
              labelKey: 'text',
              valueKey: 'value'
            }
          }
        }
      },
      {
        prop: 'evaluateResultType',
        width: '250',
        defaultData: '',
        label: '评价结果类型',
        searchType: 'select',
        config: {
          selectConfig: {
            filterable: true,
            optionKV: {
              labelKey: 'text',
              valueKey: 'value'
            },
            options: resultTypeOps.value
          }
        }
      },
      {
        prop: 'score',
        width: '250',
        defaultData: 0,
        label: '分值',
        searchType: 'number',
        config: {
          max: 100,
          min: 0
        }
      }
    ],
    [
      {
        prop: 'evaluateContent',
        width: '250',
        defaultData: '',
        label: '内容',
        searchType: 'select',
        config: {
          selectConfig: {
            filterable: true,
            optionKV: {
              labelKey: 'name',
              valueKey: 'code'
            },
            options: contentTwoOPs.value
          }
        }
      },
      {
        prop: 'evaluateStandard',
        width: '250',
        defaultData: '',
        label: '评价基准',
        searchType: 'input',
        config: {}
      },
      {
        prop: 'evaluateScenes',
        width: '250',
        defaultData: [],
        label: '适用场景',
        searchType: 'newselect',
        config: {
          selectConfig: {
            multiple: true,
            filterable: true,
            options: newEvaluateSceneOps,
            optionKV: {
              labelKey: 'text',
              valueKey: 'value'
            }
          }
        }
      },
      {
        prop: 'evaluateResultType',
        width: '250',
        defaultData: '',
        label: '评价结果类型',
        searchType: 'select',
        config: {
          selectConfig: {
            filterable: true,
            optionKV: {
              labelKey: 'text',
              valueKey: 'value'
            },
            options: resultTypeOps.value
          }
        }
      },
      {
        prop: 'score',
        width: '250',
        defaultData: 0,
        label: '分值',
        searchType: 'number',
        config: {
          max: 100,
          min: 0
        }
      }
    ]
  ];
});
</script>

<style lang="scss" scoped>
.fromCard {
  width: 99%;
}
.fromCardTop {
  height: auto;
  margin-top: 30px;
}
.title {
  font-weight: 600;
}
.btnFooter {
  margin: 15px 0 25px;
  float: right;
  .dialog-footer {
    clear: both;
  }
}

.el-divider--horizontal {
  margin: 15px 0;
}
.applicable {
  display: flex;
  justify-content: space-between;
}
</style>

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值