<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>