| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |