<template>
|
<div>
|
<FormDialog
|
v-model="dialogFormVisible"
|
:operation-type="operationType"
|
:title="dialogTitle"
|
width="80%"
|
@close="closeDia"
|
@confirm="submitForm"
|
@cancel="closeDia"
|
>
|
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
|
<el-row :gutter="24">
|
<!-- 左侧:适用人员 -->
|
<el-col :span="8">
|
<el-form-item label="适用人员:" prop="deptIds">
|
<div class="dept-checkbox-wrap">
|
<el-checkbox-group
|
v-model="form.deptIds"
|
:disabled="isDetail"
|
>
|
<div
|
v-for="dept in deptList"
|
:key="dept.deptId"
|
class="dept-checkbox-item"
|
>
|
<el-checkbox :value="dept.deptId">
|
{{ dept.deptName }}
|
<span v-if="dept.personCount != null" class="dept-count"
|
>{{ dept.personCount }}人</span
|
>
|
</el-checkbox>
|
</div>
|
</el-checkbox-group>
|
</div>
|
</el-form-item>
|
</el-col>
|
<!-- 右侧:基础信息 + 保险类型 -->
|
<el-col :span="16">
|
<!-- 基础信息 -->
|
<el-card class="form-card" shadow="never">
|
<template #header>
|
<span class="card-title"><span class="card-title-line">|</span> 基础信息</span>
|
<el-icon class="card-collapse"><ArrowUp /></el-icon>
|
</template>
|
<el-form-item label="方案标题:" prop="title">
|
<el-input
|
v-model="form.title"
|
placeholder="请输入"
|
clearable
|
:disabled="isDetail"
|
/>
|
</el-form-item>
|
<el-form-item label="备注:" prop="remark">
|
<el-input
|
v-model="form.remark"
|
type="textarea"
|
:rows="2"
|
placeholder="请输入"
|
clearable
|
:disabled="isDetail"
|
/>
|
</el-form-item>
|
</el-card>
|
|
<!-- 保险类型 -->
|
<el-card class="form-card" shadow="never">
|
<template #header>
|
<span class="card-title"><span class="card-title-line">|</span> 保险类型</span>
|
<el-button
|
v-if="!isDetail"
|
type="primary"
|
size="small"
|
@click="addInsuranceBenefit"
|
>
|
添加保险福利
|
</el-button>
|
</template>
|
<el-row :gutter="16">
|
<el-col
|
v-for="(item, index) in form.insuranceBenefits"
|
:key="item._key"
|
:span="12"
|
>
|
<div class="insurance-benefit-card">
|
<div class="insurance-benefit-title">
|
保险福利{{ index + 1 }}
|
<el-button
|
v-if="!isDetail && form.insuranceBenefits.length > 1"
|
type="danger"
|
link
|
size="small"
|
class="card-delete-btn"
|
@click="removeInsuranceBenefit(index)"
|
>
|
删除
|
</el-button>
|
</div>
|
<el-form-item
|
:prop="'insuranceBenefits.' + index + '.insuranceType'"
|
label="保险类型:"
|
label-width="100px"
|
>
|
<el-select
|
v-model="item.insuranceType"
|
placeholder="请选择"
|
clearable
|
style="width: 100%"
|
:disabled="isDetail"
|
>
|
<el-option
|
v-for="opt in insuranceTypeOptions"
|
:key="opt.value"
|
:label="opt.label"
|
:value="opt.value"
|
/>
|
</el-select>
|
</el-form-item>
|
<el-form-item label="缴费基数:" label-width="100px">
|
<div class="base-salary-wrap">
|
<el-input
|
v-model="item.paymentBase"
|
placeholder="根据基本工资缴纳"
|
clearable
|
style="width: 120px"
|
type="number"
|
:disabled="isDetail || item.useBasicSalary"
|
@input="handlePaymentBaseInput(item)"
|
/>
|
<el-checkbox
|
v-model="item.useBasicSalary"
|
@change="handleUseBasicSalaryChange(item)"
|
:disabled="isDetail"
|
>
|
调用基本工资
|
</el-checkbox>
|
</div>
|
</el-form-item>
|
<el-form-item label="个人缴费比例:" label-width="100px">
|
<div class="personal-ratio-wrap">
|
<el-input
|
v-model="item.personalRatio"
|
placeholder="请输入"
|
clearable
|
style="width: 100px"
|
type="number"
|
:disabled="isDetail"
|
/>
|
<span class="ratio-unit">(%)</span>
|
<span class="ratio-plus">+</span>
|
<el-input
|
v-model="item.personalFixed"
|
placeholder="请输入"
|
clearable
|
style="width: 100px"
|
type="number"
|
:disabled="isDetail"
|
/>
|
</div>
|
</el-form-item>
|
</div>
|
</el-col>
|
</el-row>
|
</el-card>
|
</el-col>
|
</el-row>
|
</el-form>
|
</FormDialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, toRefs, getCurrentInstance, nextTick, computed } from "vue";
|
import FormDialog from "@/components/Dialog/FormDialog.vue";
|
import { ArrowUp } from "@element-plus/icons-vue";
|
import { listDept } from "@/api/system/dept.js";
|
import { socialSecurityAdd, socialSecurityUpdate } from "@/api/personnelManagement/socialSecuritySet.js";
|
|
const emit = defineEmits(["close"]);
|
const { proxy } = getCurrentInstance();
|
|
const dialogFormVisible = ref(false);
|
const operationType = ref("add");
|
const formRef = ref(null);
|
const deptList = ref([]);
|
|
const isDetail = computed(() => operationType.value === "detail");
|
|
const dialogTitle = () =>
|
operationType.value === "add"
|
? "新增方案"
|
: operationType.value === "edit"
|
? "编辑方案"
|
: "方案详情";
|
|
// 保险类型选项(可按字典替换)
|
const insuranceTypeOptions = [
|
{ label: "养老保险", value: "养老保险" },
|
{ label: "医疗保险", value: "医疗保险" },
|
{ label: "失业保险", value: "失业保险" },
|
{ label: "工伤保险", value: "工伤保险" },
|
{ label: "生育保险", value: "生育保险" },
|
{ label: "公积金", value: "公积金" },
|
];
|
|
const defaultBenefit = () => ({
|
_key: Math.random().toString(36).slice(2),
|
insuranceType: "",
|
paymentBase: "",
|
useBasicSalary: false,
|
personalRatio: "",
|
personalFixed: "",
|
});
|
|
const data = reactive({
|
form: {
|
id: undefined,
|
title: "",
|
remark: "",
|
deptIds: [],
|
insuranceBenefits: [defaultBenefit()],
|
},
|
rules: {
|
title: [{ required: true, message: "请输入方案标题", trigger: "blur" }],
|
deptIds: [
|
{
|
required: true,
|
type: "array",
|
min: 1,
|
message: "请至少选择一个适用部门",
|
trigger: "change",
|
},
|
],
|
},
|
});
|
const { form, rules } = toRefs(data);
|
|
function flattenDept(tree, list = []) {
|
if (!tree || !tree.length) return list;
|
tree.forEach((node) => {
|
list.push({
|
deptId: node.deptId,
|
deptName: node.deptName,
|
personCount: node.personCount ?? null,
|
});
|
if (node.children && node.children.length) {
|
flattenDept(node.children, list);
|
}
|
});
|
return list;
|
}
|
|
const loadDeptList = () => {
|
listDept().then((res) => {
|
const tree = res.data ?? [];
|
deptList.value = flattenDept(tree);
|
});
|
};
|
|
const addInsuranceBenefit = () => {
|
form.value.insuranceBenefits.push(defaultBenefit());
|
};
|
|
const removeInsuranceBenefit = (index) => {
|
form.value.insuranceBenefits.splice(index, 1);
|
};
|
|
const handleUseBasicSalaryChange = (item) => {
|
if (item.useBasicSalary) {
|
item.paymentBase = "";
|
}
|
};
|
|
const handlePaymentBaseInput = (item) => {
|
if (item.paymentBase !== "" && item.paymentBase != null) {
|
item.useBasicSalary = false;
|
}
|
};
|
|
const resetForm = () => {
|
form.value = {
|
id: undefined,
|
title: "",
|
remark: "",
|
deptIds: [],
|
insuranceBenefits: [defaultBenefit()],
|
};
|
};
|
|
const openDialog = (type, row) => {
|
operationType.value = type;
|
dialogFormVisible.value = true;
|
loadDeptList();
|
resetForm();
|
if ((type === "edit" || type === "detail") && row) {
|
const d = row || {};
|
form.value.id = d.id;
|
form.value.title = d.title;
|
form.value.remark = d.remark ?? "";
|
// deptIds 后端可能是逗号分隔字符串或数组,这里统一转为数组并尽量还原数值类型
|
if (d.deptIds) {
|
form.value.deptIds = String(d.deptIds)
|
.split(",")
|
.filter((v) => v !== "")
|
.map((v) => {
|
const num = Number(v);
|
return Number.isNaN(num) ? v : num;
|
});
|
} else {
|
form.value.deptIds = [];
|
}
|
const detailList = d.schemeInsuranceDetailList || [];
|
form.value.insuranceBenefits =
|
detailList.length > 0
|
? detailList.map((b) => ({
|
_key: Math.random().toString(36).slice(2),
|
insuranceType: b.insuranceType || "",
|
paymentBase: b.paymentBase ?? "",
|
useBasicSalary: b.useBasicSalary === 2,
|
personalRatio: b.personalRatio ?? "",
|
personalFixed: b.personalFixed ?? "",
|
}))
|
: [defaultBenefit()];
|
}
|
};
|
|
const submitForm = () => {
|
// 详情模式下不提交,只关闭弹窗
|
if (operationType.value === "detail") {
|
closeDia();
|
return;
|
}
|
formRef.value?.validate((valid) => {
|
if (!valid) return;
|
const deptIds =
|
Array.isArray(form.value.deptIds) && form.value.deptIds.length
|
? form.value.deptIds.join(",")
|
: "";
|
const schemeInsuranceDetailList = (form.value.insuranceBenefits || []).map(
|
({ _key, ...rest }) => ({
|
...rest,
|
useBasicSalary: rest.useBasicSalary ? 2 : 1,
|
})
|
);
|
const insuranceTypes = schemeInsuranceDetailList
|
.map((item) => item.insuranceType)
|
.filter((v) => v)
|
.join(",");
|
// 部门名称,多个使用逗号隔开(根据选中的 deptIds 与 deptList 计算)
|
const deptNames = (deptList.value || [])
|
.filter((d) =>
|
(form.value.deptIds || []).some(
|
(id) => String(id) === String(d.deptId)
|
)
|
)
|
.map((d) => d.deptName)
|
.join(",");
|
const submitData = {
|
id: form.value.id,
|
title: form.value.title,
|
remark: form.value.remark ?? "",
|
deptIds,
|
insuranceTypes,
|
deptNames,
|
schemeInsuranceDetailList,
|
};
|
if (operationType.value === "add") {
|
socialSecurityAdd(submitData).then(() => {
|
proxy.$modal.msgSuccess("新增成功");
|
closeDia();
|
});
|
} else {
|
socialSecurityUpdate(submitData).then(() => {
|
proxy.$modal.msgSuccess("修改成功");
|
closeDia();
|
});
|
}
|
});
|
};
|
|
const closeDia = () => {
|
proxy.resetForm?.("formRef");
|
dialogFormVisible.value = false;
|
emit("close");
|
};
|
|
defineExpose({ openDialog });
|
</script>
|
|
<style scoped>
|
.card-title-line {
|
color: #f56c6c;
|
margin-right: 4px;
|
}
|
.form-card {
|
margin-bottom: 16px;
|
}
|
.form-card :deep(.el-card__header) {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
padding: 12px 16px;
|
}
|
.card-title {
|
font-weight: 500;
|
}
|
.card-collapse {
|
color: #999;
|
cursor: pointer;
|
}
|
.dept-checkbox-wrap {
|
max-height: 320px;
|
overflow-y: auto;
|
padding: 8px 0;
|
border: 1px solid var(--el-border-color);
|
border-radius: 4px;
|
background: #fff;
|
}
|
.dept-checkbox-item {
|
padding: 6px 12px;
|
}
|
.dept-count {
|
color: #909399;
|
font-size: 12px;
|
margin-left: 4px;
|
}
|
.insurance-benefit-card {
|
border: 1px solid var(--el-border-color-lighter);
|
border-radius: 4px;
|
padding: 12px 16px;
|
margin-bottom: 12px;
|
background: #fafafa;
|
}
|
.insurance-benefit-title {
|
font-size: 14px;
|
margin-bottom: 12px;
|
font-weight: 500;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
}
|
.card-delete-btn {
|
margin-left: auto;
|
}
|
.checkbox-group-inline {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 16px;
|
}
|
.base-salary-wrap {
|
display: flex;
|
flex-wrap: wrap;
|
align-items: center;
|
gap: 8px;
|
}
|
.base-salary-text {
|
color: #606266;
|
font-size: 14px;
|
}
|
.personal-ratio-wrap {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
.ratio-unit,
|
.ratio-plus {
|
color: #606266;
|
font-size: 14px;
|
}
|
</style>
|