进销存升级
1.添加社会保险设置页面,开发与联调
2.丰富新增入职所填字段字段并于用户管理关联
3.修改人员薪资页面样式和逻辑
已添加3个文件
已修改12个文件
1395 ■■■■ 文件已修改
src/api/personnelManagement/bank.js 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/socialSecuritySet.js 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/staffSalaryMain.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Dialog/FormDialog.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/BasicInfoSection.vue 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/EducationWorkSection.vue 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/EmergencyAndAttachmentSection.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/JobInfoSection.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/index.vue 67 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/components/bankSettingDia.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/components/formDia.vue 186 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/index.vue 266 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/socialSecuritySet/components/formDia.vue 178 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/socialSecuritySet/index.vue 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/bank.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
import request from "@/utils/request";
// é“¶è¡Œç®¡ç†
export function bankList() {
  return request({
    url: "/bank/list",
    method: "get",
  });
}
export function bankAdd(data) {
  return request({
    url: "/bank/add",
    method: "post",
    data,
  });
}
export function bankUpdate(data) {
  return request({
    url: "/bank/update",
    method: "post",
    data,
  });
}
export function bankDelete(ids) {
  return request({
    url: "/bank/delete",
    method: "delete",
    data: ids,
  });
}
src/api/personnelManagement/socialSecuritySet.js
@@ -4,7 +4,7 @@
// åˆ†é¡µæŸ¥è¯¢åˆ—表
export function socialSecurityListPage(query) {
  return request({
    url: "/socialSecurity/plan/listPage",
    url: "/schemeApplicableStaff/listPage",
    method: "get",
    params: query,
  });
@@ -13,7 +13,7 @@
// æŸ¥è¯¢è¯¦æƒ…
export function socialSecurityInfo(id) {
  return request({
    url: "/socialSecurity/plan/" + id,
    url: "/schemeApplicableStaff/" + id,
    method: "get",
  });
}
@@ -21,7 +21,7 @@
// æ–°å¢ž
export function socialSecurityAdd(data) {
  return request({
    url: "/socialSecurity/plan/add",
    url: "/schemeApplicableStaff/add",
    method: "post",
    data,
  });
@@ -30,7 +30,7 @@
// ä¿®æ”¹
export function socialSecurityUpdate(data) {
  return request({
    url: "/socialSecurity/plan/update",
    url: "/schemeApplicableStaff/updateSchemeApplicableStaff",
    method: "post",
    data,
  });
@@ -39,7 +39,7 @@
// åˆ é™¤
export function socialSecurityDelete(ids) {
  return request({
    url: "/socialSecurity/plan/delete",
    url: "/schemeApplicableStaff/delete",
    method: "delete",
    data: ids,
  });
src/api/personnelManagement/staffSalaryMain.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
import request from "@/utils/request";
// å‘˜å·¥å·¥èµ„主表
export function staffSalaryMainListPage(params) {
  return request({
    url: "/staffSalaryMain/listPage",
    method: "get",
    params,
  });
}
export function staffSalaryMainCalculateSalary(ids) {
  return request({
    url: "/staffSalaryMain/calculateSalary",
    method: "post",
    data: ids,
  });
}
export function staffSalaryMainAdd(data) {
  return request({
    url: "/staffSalaryMain/add",
    method: "post",
    data,
  });
}
export function staffSalaryMainUpdate(data) {
  return request({
    url: "/staffSalaryMain/update",
    method: "post",
    data,
  });
}
export function staffSalaryMainDelete(ids) {
  return request({
    url: "/staffSalaryMain/delete",
    method: "delete",
    data: ids,
  });
}
src/components/Dialog/FormDialog.vue
@@ -8,7 +8,13 @@
    <slot></slot>
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" @click="handleConfirm">确认</el-button>
        <el-button
          v-if="showConfirm"
          type="primary"
          @click="handleConfirm"
        >
          ç¡®è®¤
        </el-button>
        <el-button @click="handleCancel">取消</el-button>
      </div>
    </template>
@@ -44,6 +50,9 @@
  set: (val) => emit('update:modelValue', val)
})
// è¯¦æƒ…模式不展示“确认”按钮,其它类型正常显示
const showConfirm = computed(() => props.operationType !== 'detail')
const computedTitle = computed(() => {
  if (typeof props.title === 'function') {
    return props.title(props.operationType)
src/views/personnelManagement/employeeRecord/components/BasicInfoSection.vue
@@ -32,9 +32,9 @@
        </el-form-item>
      </el-col>
      <el-col :span="5">
        <el-form-item label="别名" prop="aliasName">
        <el-form-item label="别名" prop="alias">
          <el-input
            v-model="form.aliasName"
            v-model="form.alias"
            placeholder="请输入"
            clearable
            maxlength="50"
@@ -135,10 +135,9 @@
    <el-row :gutter="24">
      <el-col :span="10">
        <el-form-item label="角色" prop="roleIds">
        <el-form-item label="角色" prop="roleId">
          <el-select
            v-model="form.roleIds"
            multiple
            v-model="form.roleId"
            placeholder="请选择"
            clearable
            style="width: 100%"
src/views/personnelManagement/employeeRecord/components/EducationWorkSection.vue
@@ -8,11 +8,11 @@
          æ•™è‚²ç»åކ
        </span>
      </template>
      <el-table :data="form.educationList" border>
        <el-table-column label="学历" prop="degree" width="120">
      <el-table :data="form.staffEducationList" border>
        <el-table-column label="学历" prop="education" width="120">
          <template #default="{ row }">
            <el-select
              v-model="row.degree"
              v-model="row.education"
              placeholder="请选择"
              clearable
              style="width: 100%"
@@ -25,10 +25,10 @@
            </el-select>
          </template>
        </el-table-column>
        <el-table-column label="毕业院校" prop="school" min-width="160">
        <el-table-column label="毕业院校" prop="schoolName" min-width="160">
          <template #default="{ row }">
            <el-input
              v-model="row.school"
              v-model="row.schoolName"
              placeholder="请输入"
              clearable
              maxlength="30"
@@ -36,10 +36,10 @@
            />
          </template>
        </el-table-column>
        <el-table-column label="入学时间" prop="admissionDate" width="150">
        <el-table-column label="入学时间" prop="enrollTime" width="150">
          <template #default="{ row }">
            <el-date-picker
              v-model="row.admissionDate"
              v-model="row.enrollTime"
              type="date"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
@@ -49,10 +49,10 @@
            />
          </template>
        </el-table-column>
        <el-table-column label="毕业时间" prop="graduationDate" width="150">
        <el-table-column label="毕业时间" prop="graduateTime" width="150">
          <template #default="{ row }">
            <el-date-picker
              v-model="row.graduationDate"
              v-model="row.graduateTime"
              type="date"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
@@ -73,10 +73,10 @@
            />
          </template>
        </el-table-column>
        <el-table-column label="学位" prop="academicDegree" width="140">
        <el-table-column label="学位" prop="degree" width="140">
          <template #default="{ row }">
            <el-input
              v-model="row.academicDegree"
              v-model="row.degree"
              placeholder="请输入"
              clearable
              maxlength="20"
@@ -87,7 +87,7 @@
        <el-table-column label="操作" width="80" align="center">
          <template #default="scope">
            <el-button
              v-if="form.educationList.length > 1"
              v-if="form.staffEducationList.length > 1"
              type="primary"
              link
              @click="removeEducationRow(scope.$index)"
@@ -108,11 +108,11 @@
          å·¥ä½œç»åކ
        </span>
      </template>
      <el-table :data="form.workExperienceList" border>
        <el-table-column label="前公司" prop="company" min-width="180">
      <el-table :data="form.staffWorkExperienceList" border>
        <el-table-column label="前公司" prop="formerCompany" min-width="180">
          <template #default="{ row }">
            <el-input
              v-model="row.company"
              v-model="row.formerCompany"
              placeholder="请输入"
              clearable
              maxlength="30"
@@ -120,10 +120,10 @@
            />
          </template>
        </el-table-column>
        <el-table-column label="前公司部门" prop="department" min-width="140">
        <el-table-column label="前公司部门" prop="formerDept" min-width="140">
          <template #default="{ row }">
            <el-input
              v-model="row.department"
              v-model="row.formerDept"
              placeholder="请输入"
              clearable
              maxlength="20"
@@ -131,10 +131,10 @@
            />
          </template>
        </el-table-column>
        <el-table-column label="前公司职位" prop="position" min-width="140">
        <el-table-column label="前公司职位" prop="formerPosition" min-width="140">
          <template #default="{ row }">
            <el-input
              v-model="row.position"
              v-model="row.formerPosition"
              placeholder="请输入"
              clearable
              maxlength="20"
@@ -168,10 +168,10 @@
            />
          </template>
        </el-table-column>
        <el-table-column label="工作描述" prop="description" min-width="220">
        <el-table-column label="工作描述" prop="workDesc" min-width="220">
          <template #default="{ row }">
            <el-input
              v-model="row.description"
              v-model="row.workDesc"
              type="textarea"
              :rows="2"
              placeholder="请输入"
@@ -184,7 +184,7 @@
        <el-table-column label="操作" width="80" align="center">
          <template #default="scope">
            <el-button
              v-if="form.workExperienceList.length > 1"
              v-if="form.staffWorkExperienceList.length > 1"
              type="primary"
              link
              @click="removeWorkRow(scope.$index)"
@@ -211,35 +211,35 @@
const { form } = toRefs(props);
const addEducationRow = () => {
  form.value.educationList.push({
    degree: "",
    school: "",
    admissionDate: "",
    graduationDate: "",
  form.value.staffEducationList.push({
    education: "",
    schoolName: "",
    enrollTime: "",
    graduateTime: "",
    major: "",
    academicDegree: "",
    degree: "",
  });
};
const removeEducationRow = (index) => {
  if (form.value.educationList.length <= 1) return;
  form.value.educationList.splice(index, 1);
  if (form.value.staffEducationList.length <= 1) return;
  form.value.staffEducationList.splice(index, 1);
};
const addWorkRow = () => {
  form.value.workExperienceList.push({
    company: "",
    department: "",
    position: "",
  form.value.staffWorkExperienceList.push({
    formerCompany: "",
    formerDept: "",
    formerPosition: "",
    startDate: "",
    endDate: "",
    description: "",
    workDesc: "",
  });
};
const removeWorkRow = (index) => {
  if (form.value.workExperienceList.length <= 1) return;
  form.value.workExperienceList.splice(index, 1);
  if (form.value.staffWorkExperienceList.length <= 1) return;
  form.value.staffWorkExperienceList.splice(index, 1);
};
</script>
src/views/personnelManagement/employeeRecord/components/EmergencyAndAttachmentSection.vue
@@ -8,11 +8,11 @@
          ç´§æ€¥è”系人
        </span>
      </template>
      <el-table :data="form.emergencyContacts" border>
        <el-table-column label="紧急联系人姓名" prop="name" min-width="160">
      <el-table :data="form.staffEmergencyContactList" border>
        <el-table-column label="紧急联系人姓名" prop="contactName" min-width="160">
          <template #default="{ row }">
            <el-input
              v-model="row.name"
              v-model="row.contactName"
              placeholder="请输入"
              clearable
              maxlength="50"
@@ -20,10 +20,10 @@
            />
          </template>
        </el-table-column>
        <el-table-column label="紧急联系人关系" prop="relation" min-width="140">
        <el-table-column label="紧急联系人关系" prop="contactRelation" min-width="140">
          <template #default="{ row }">
            <el-input
              v-model="row.relation"
              v-model="row.contactRelation"
              placeholder="请输入"
              clearable
              maxlength="20"
@@ -31,10 +31,10 @@
            />
          </template>
        </el-table-column>
        <el-table-column label="紧急联系人手机" prop="phone" width="160">
        <el-table-column label="紧急联系人手机" prop="contactPhone" width="160">
          <template #default="{ row }">
            <el-input
              v-model="row.phone"
              v-model="row.contactPhone"
              placeholder="请输入"
              clearable
              maxlength="11"
@@ -42,10 +42,10 @@
            />
          </template>
        </el-table-column>
        <el-table-column label="紧急联系人住址" prop="address" min-width="220">
        <el-table-column label="紧急联系人住址" prop="contactAddress" min-width="220">
          <template #default="{ row }">
            <el-input
              v-model="row.address"
              v-model="row.contactAddress"
              placeholder="请输入"
              clearable
              maxlength="50"
@@ -56,7 +56,7 @@
        <el-table-column label="操作" width="80" align="center">
          <template #default="scope">
            <el-button
              v-if="form.emergencyContacts.length > 1"
              v-if="form.staffEmergencyContactList.length > 1"
              type="primary"
              link
              @click="removeEmergencyRow(scope.$index)"
@@ -108,17 +108,17 @@
const { form } = toRefs(props);
const addEmergencyRow = () => {
  form.value.emergencyContacts.push({
    name: "",
    relation: "",
    phone: "",
    address: "",
  form.value.staffEmergencyContactList.push({
    contactName: "",
    contactRelation: "",
    contactPhone: "",
    contactAddress: "",
  });
};
const removeEmergencyRow = (index) => {
  if (form.value.emergencyContacts.length <= 1) return;
  form.value.emergencyContacts.splice(index, 1);
  if (form.value.staffEmergencyContactList.length <= 1) return;
  form.value.staffEmergencyContactList.splice(index, 1);
};
</script>
src/views/personnelManagement/employeeRecord/components/JobInfoSection.vue
@@ -7,38 +7,12 @@
      </span>
    </template>
    <!-- ç¬¬ä¸€è¡Œï¼šåˆåŒå¼€å§‹ / åˆåŒç»“束 / è¯•用期 / è½¬æ­£ -->
    <el-row :gutter="24">
      <el-col :span="5">
        <el-form-item label="工号" prop="jobNo">
          <el-input
            v-model="form.jobNo"
            placeholder="请输入"
            clearable
            maxlength="20"
            show-word-limit
          />
        </el-form-item>
      </el-col>
      <el-col :span="5">
        <el-form-item label="员工类型" prop="staffType">
          <el-select
            v-model="form.staffType"
            placeholder="请选择"
            clearable
            style="width: 100%"
          >
            <el-option label="正式员工" value="official" />
            <el-option label="试用员工" value="probation" />
            <el-option label="实习生" value="intern" />
            <el-option label="兼职" value="part_time" />
            <el-option label="劳务/外包" value="outsourcing" />
          </el-select>
        </el-form-item>
      </el-col>
      <el-col :span="5">
        <el-form-item label="入职日期" prop="entryDate">
      <el-col :span="6">
        <el-form-item label="入职日期" prop="contractStartTime">
          <el-date-picker
            v-model="form.entryDate"
            v-model="form.contractStartTime"
            type="date"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
@@ -48,10 +22,34 @@
          />
        </el-form-item>
      </el-col>
      <el-col :span="5">
      <el-col :span="6">
        <el-form-item
          label="合同结束日期"
          prop="contractEndTime"
          required
          :rules="[
            {
              required: true,
              message: '请选择合同结束日期',
              trigger: 'change',
            },
          ]"
        >
          <el-date-picker
            v-model="form.contractEndTime"
            type="date"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
            placeholder="请选择"
            style="width: 100%"
            clearable
          />
        </el-form-item>
      </el-col>
      <el-col :span="6">
        <el-form-item label="试用期(月)" prop="probationPeriod">
          <el-input-number
            v-model="form.probationPeriod"
            v-model="form.proTerm"
            :min="0"
            :max="24"
            :precision="0"
@@ -60,10 +58,10 @@
          />
        </el-form-item>
      </el-col>
      <el-col :span="4">
        <el-form-item label="转正日期" prop="regularDate">
      <el-col :span="6">
        <el-form-item label="转正日期" prop="positiveDate">
          <el-date-picker
            v-model="form.regularDate"
            v-model="form.positiveDate"
            type="date"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
@@ -75,6 +73,7 @@
      </el-col>
    </el-row>
    <!-- ç¬¬äºŒè¡Œï¼šéƒ¨é—¨ / å²—位 / åŸºæœ¬å·¥èµ„ -->
    <el-row :gutter="24">
      <el-col :span="8">
        <el-form-item label="部门" prop="sysDeptId">
@@ -85,17 +84,6 @@
            :render-after-expand="false"
            placeholder="请选择"
            style="width: 100%"
          />
        </el-form-item>
      </el-col>
      <el-col :span="8">
        <el-form-item label="直接上级" prop="directLeader">
          <el-input
            v-model="form.directLeader"
            placeholder="请输入"
            clearable
            maxlength="50"
            show-word-limit
          />
        </el-form-item>
      </el-col>
@@ -117,21 +105,7 @@
          </el-select>
        </el-form-item>
      </el-col>
    </el-row>
    <el-row :gutter="24">
      <el-col :span="5">
        <el-form-item label="职级" prop="jobLevel">
          <el-input
            v-model="form.jobLevel"
            placeholder="请输入"
            clearable
            maxlength="10"
            show-word-limit
          />
        </el-form-item>
      </el-col>
      <el-col :span="4">
      <el-col :span="8">
        <el-form-item label="基本工资" prop="basicSalary">
          <el-input-number
            v-model="form.basicSalary"
src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue
@@ -67,28 +67,28 @@
  operationType.value === "add" ? "新增入职" : "编辑人员";
const createEmptyEducation = () => ({
  degree: "",
  school: "",
  admissionDate: "",
  graduationDate: "",
  education: "",
  schoolName: "",
  enrollTime: "",
  graduateTime: "",
  major: "",
  academicDegree: "",
  degree: "",
});
const createEmptyWork = () => ({
  company: "",
  department: "",
  position: "",
  formerCompany: "",
  formerDept: "",
  formerPosition: "",
  startDate: "",
  endDate: "",
  description: "",
  workDesc: "",
});
const createEmptyEmergency = () => ({
  name: "",
  relation: "",
  phone: "",
  address: "",
  contactName: "",
  contactRelation: "",
  contactPhone: "",
  contactAddress: "",
});
const createDefaultForm = () => ({
@@ -96,7 +96,7 @@
  // åŸºæœ¬ä¿¡æ¯
  staffNo: "",
  staffName: "",
  aliasName: "",
  alias: "",
  phone: "",
  sex: "",
  birthDate: "",
@@ -112,29 +112,24 @@
  email: "",
  currentAddress: "",
  // åœ¨èŒä¿¡æ¯
  jobNo: "",
  staffType: "",
  entryDate: "",
  probationPeriod: undefined,
  regularDate: "",
  contractStartTime: "",
  contractEndTime: "",
  proTerm: undefined,
  positiveDate: "",
  sysDeptId: undefined,
  directLeader: "",
  sysPostId: undefined,
  jobLevel: "",
  basicSalary: undefined,
  // é“¶è¡Œå¡ä¿¡æ¯
  bankName: "",
  bankCardNo: "",
  // æ•™è‚²ç»åކ
  educationList: [createEmptyEducation()],
  staffEducationList: [createEmptyEducation()],
  // å·¥ä½œç»åކ
  workExperienceList: [createEmptyWork()],
  staffWorkExperienceList: [createEmptyWork()],
  // ç´§æ€¥è”系人
  emergencyContacts: [createEmptyEmergency()],
  emergencyContact: "",
  emergencyContactPhone: "",
  // è§’色
  roleIds: [],
  staffEmergencyContactList: [createEmptyEmergency()],
  // è§’色(单选)
  roleId: undefined,
  // ææ–™é™„件(仅前端展示)
  attachments: [],
});
@@ -149,25 +144,16 @@
    birthDate: [
      { required: true, message: "请选择出生日期", trigger: "change" },
    ],
    jobNo: [{ required: true, message: "请输入工号", trigger: "blur" }],
    staffType: [
      { required: true, message: "请选择员工类型", trigger: "change" },
    ],
    entryDate: [
    contractStartTime: [
      { required: true, message: "请选择入职日期", trigger: "change" },
    ],
    contractEndTime: [
      { required: true, message: "请选择合同结束日期", trigger: "change" },
    ],
    sysDeptId: [
      { required: true, message: "请选择部门", trigger: "change" },
    ],
    roleIds: [
      {
        required: true,
        type: "array",
        min: 1,
        message: "请至少选择一个角色",
        trigger: "change",
      },
    ],
    roleId: [{ required: true, message: "请选择角色", trigger: "change" }],
  },
  postOptions: [],
  deptOptions: [],
@@ -215,12 +201,6 @@
  });
}
const syncEmergencyToLegacyField = () => {
  const first = form.value.emergencyContacts?.[0];
  form.value.emergencyContact = first?.name || "";
  form.value.emergencyContactPhone = first?.phone || "";
};
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
@@ -236,20 +216,23 @@
        ...form.value,
        ...d,
      });
      if (!Array.isArray(form.value.educationList) || !form.value.educationList.length) {
        form.value.educationList = [createEmptyEducation()];
      if (
        !Array.isArray(form.value.staffEducationList) ||
        !form.value.staffEducationList.length
      ) {
        form.value.staffEducationList = [createEmptyEducation()];
      }
      if (
        !Array.isArray(form.value.workExperienceList) ||
        !form.value.workExperienceList.length
        !Array.isArray(form.value.staffWorkExperienceList) ||
        !form.value.staffWorkExperienceList.length
      ) {
        form.value.workExperienceList = [createEmptyWork()];
        form.value.staffWorkExperienceList = [createEmptyWork()];
      }
      if (
        !Array.isArray(form.value.emergencyContacts) ||
        !form.value.emergencyContacts.length
        !Array.isArray(form.value.staffEmergencyContactList) ||
        !form.value.staffEmergencyContactList.length
      ) {
        form.value.emergencyContacts = [createEmptyEmergency()];
        form.value.staffEmergencyContactList = [createEmptyEmergency()];
      }
      if (form.value.sysPostId === 0) {
        form.value.sysPostId = undefined;
@@ -273,7 +256,8 @@
  if (!form.value.sysDeptId) {
    form.value.sysDeptId = undefined;
  }
  syncEmergencyToLegacyField();
  // å…¼å®¹åŽç«¯å¯èƒ½ä»ä½¿ç”¨ roleIds æ•°ç»„
  form.value.roleIds = form.value.roleId ? [form.value.roleId] : [];
  formRef.value?.validate((valid) => {
    if (valid) {
      if (operationType.value === "add") {
src/views/personnelManagement/employeeRecord/index.vue
@@ -102,67 +102,40 @@
    prop: "staffName",
  },
  {
    label: "别名",
    prop: "alias",
  },
  {
    label: "手机",
    prop: "phone",
    width: 150,
  },
  {
    label: "性别",
    prop: "sex",
  },
  {
    label: "户籍住址",
    prop: "nativePlace",
  },
  {
    label: "部门",
    prop: "deptName",
  },
  {
    label: "岗位",
    prop: "postJob",
  },
  {
    label: "现住址",
    prop: "adress",
    width:200
  },
  {
    label: "第一学历",
    prop: "firstStudy",
  },
  {
    label: "专业",
    prop: "profession",
    width:100
    label: "出生日期",
    prop: "birthDate",
    width: 120,
  },
  {
    label: "年龄",
    prop: "age",
  },
  {
    label: "联系电话",
    prop: "phone",
    width:150
    label: "籍贯",
    prop: "nativePlace",
  },
  {
    label: "紧急联系人",
    prop: "emergencyContact",
    width: 120
    label: "民族",
    prop: "nation",
    width: 100,
  },
  {
    label: "紧急联系人电话",
    prop: "emergencyContactPhone",
    width:150
  },
  // {
  //   label: "合同年限",
  //   prop: "contractTerm",
  // },
  // {
  //   label: "合同开始日期",
  //   prop: "contractStartTime",
  //   width: 120
  // },
  {
    label: "合同结束日期",
    prop: "contractExpireTime",
    width: 120
    label: "婚姻状况",
    prop: "maritalStatus",
    width: 100,
  },
  {
    dataType: "action",
src/views/personnelManagement/monthlyStatistics/components/bankSettingDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,188 @@
<template>
  <FormDialog
    v-model="dialogVisible"
    operation-type="edit"
    title="设置发放银行下拉数据"
    width="640px"
    @close="handleClose"
    @confirm="handleConfirm"
    @cancel="handleCancel"
  >
    <el-form ref="formRef" :model="form" label-position="top">
      <el-row :gutter="16">
        <el-col :span="24" style="display: flex; justify-content: end; gap: 10px;margin-bottom: 10px">
          <el-button type="primary" @click="addBank">新增银行</el-button>
          <el-button @click="resetToEmpty">清空</el-button>
        </el-col>
      </el-row>
      <el-table :data="form.banks" border style="width: 100%">
        <el-table-column label="银行名称" min-width="260">
          <template #default="{ row }">
            <el-input
              v-model="row.bankName"
              placeholder="例如:中国工商银行"
              clearable
              maxlength="50"
              show-word-limit
            />
          </template>
        </el-table-column>
        <el-table-column label="操作" width="90" align="center">
          <template #default="{ $index }">
            <el-button type="danger" link @click="removeBank($index)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <div style="margin-top: 10px; color: #909399; font-size: 12px">
        æç¤ºï¼šè¿™é‡Œç»´æŠ¤çš„æ˜¯â€œå‘放银行”下拉框选项数据;保存后在新建/编辑工资表中可选择。
      </div>
    </el-form>
  </FormDialog>
</template>
<script setup>
import { computed, reactive, ref, toRefs, watch, getCurrentInstance } from "vue";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { bankAdd, bankDelete, bankList, bankUpdate } from "@/api/personnelManagement/bank.js";
const emit = defineEmits(["update:modelValue", "close", "saved"]);
const props = defineProps({
  modelValue: { type: Boolean, default: false },
});
const { proxy } = getCurrentInstance();
const dialogVisible = computed({
  get: () => props.modelValue,
  set: (val) => emit("update:modelValue", val),
});
const formRef = ref(null);
const data = reactive({
  form: {
    banks: [],
  },
});
const { form } = toRefs(data);
function newKey() {
  return Math.random().toString(36).slice(2);
}
const addBank = () => {
  form.value.banks.push({
    _key: newKey(),
    id: undefined,
    bankName: "",
    _originBankName: "",
  });
};
const removeBank = (index) => {
  const row = form.value.banks?.[index];
  if (!row) return;
  // æœªè½åº“的行:直接移除
  if (!row.id) {
    form.value.banks.splice(index, 1);
    return;
  }
  // å·²è½åº“:调用后端删除
  bankDelete([row.id]).then(() => {
    proxy?.$modal?.msgSuccess?.("删除成功");
    form.value.banks.splice(index, 1);
    emit("saved");
  });
};
const resetToEmpty = () => {
  if (!form.value.banks?.length) return;
  const ids = form.value.banks.map((b) => b?.id).filter(Boolean);
  // è‹¥å…¨éƒ¨æ˜¯æœªä¿å­˜è¡Œï¼Œåˆ™ä»…清空本地
  if (!ids.length) {
    form.value.banks = [];
    return;
  }
  proxy?.$modal
    ?.confirm?.("确定清空所有银行吗?")
    .then(() => bankDelete(ids))
    .then(() => {
      proxy?.$modal?.msgSuccess?.("清空成功");
      form.value.banks = [];
      emit("saved");
    })
    .catch(() => {});
};
const loadSetting = () => {
  return bankList().then((res) => {
    const list = Array.isArray(res?.data) ? res.data : [];
    form.value.banks = list.map((b) => ({
      _key: newKey(),
      id: b?.id,
      bankName: b?.bankName ?? "",
      _originBankName: b?.bankName ?? "",
    }));
  });
};
const openDialog = () => {
  loadSetting();
};
watch(
  () => dialogVisible.value,
  (val) => {
    if (val) openDialog();
  }
);
const handleConfirm = () => {
  const names = (form.value.banks || [])
    .map((b) => (b?.bankName == null ? "" : String(b.bankName).trim()))
    .filter((n) => n !== "");
  const unique = Array.from(new Set(names));
  if (!unique.length) {
    proxy?.$modal?.msgWarning?.("请至少新增一个银行选项");
    return;
  }
  if (unique.length !== names.length) {
    proxy?.$modal?.msgWarning?.("银行名称不可重复");
    return;
  }
  const rows = form.value.banks.map((b) => ({
    ...b,
    bankName: b?.bankName == null ? "" : String(b.bankName).trim(),
  }));
  const toAdd = rows.filter((b) => !b.id && b.bankName);
  const toUpdate = rows.filter((b) => b.id && b.bankName && b.bankName !== (b._originBankName ?? ""));
  Promise.all([
    ...toAdd.map((b) => bankAdd({ bankName: b.bankName })),
    ...toUpdate.map((b) => bankUpdate({ id: b.id, bankName: b.bankName })),
  ])
    .then(() => loadSetting())
    .then(() => {
      proxy?.$modal?.msgSuccess?.("保存成功");
      dialogVisible.value = false;
      emit("saved", { options: unique });
    });
};
const handleCancel = () => {
  dialogVisible.value = false;
};
const handleClose = () => {
  emit("close");
};
defineExpose({ openDialog });
</script>
<style scoped></style>
src/views/personnelManagement/monthlyStatistics/components/formDia.vue
@@ -17,9 +17,9 @@
        <el-form ref="formRef" :model="form" :rules="rules" label-position="top">
          <el-row :gutter="24">
            <el-col :span="6">
              <el-form-item label="工资主题" prop="title">
              <el-form-item label="工资主题" prop="salaryTitle">
                <el-input
                  v-model="form.title"
                  v-model="form.salaryTitle"
                  placeholder="请输入"
                  clearable
                  maxlength="20"
@@ -45,9 +45,9 @@
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="选择工资月份" prop="payMonth">
              <el-form-item label="选择工资月份" prop="salaryMonth">
                <el-date-picker
                  v-model="form.payMonth"
                  v-model="form.salaryMonth"
                  type="month"
                  value-format="YYYY-MM"
                  format="YYYY-MM"
@@ -64,6 +64,26 @@
                  placeholder="请输入"
                  clearable
                />
              </el-form-item>
            </el-col>
          </el-row>
          <el-row :gutter="24">
            <el-col :span="6">
              <el-form-item label="支付银行" prop="payBank">
                <el-select
                  v-model="form.payBank"
                  placeholder="请选择"
                  clearable
                  filterable
                  style="width: 100%"
                >
                  <el-option
                    v-for="b in bankOptions"
                    :key="b"
                    :label="b"
                    :value="b"
                  />
                </el-select>
              </el-form-item>
            </el-col>
          </el-row>
@@ -92,7 +112,6 @@
        >
          <el-table-column type="selection" width="55" align="center" />
          <el-table-column label="员工姓名" prop="staffName" minWidth="100" />
          <el-table-column label="角色" prop="roleName" minWidth="100" />
          <el-table-column label="部门" prop="deptName" minWidth="100" />
          <el-table-column label="基本工资" minWidth="110">
            <template #default="{ row }">
@@ -108,11 +127,11 @@
          <el-table-column label="计件工资" minWidth="110">
            <template #default="{ row }">
              <el-input
                v-model.number="row.pieceworkSalary"
                v-model.number="row.pieceSalary"
                type="number"
                placeholder="0"
                size="small"
                @input="row.pieceworkSalary = parseNum(row.pieceworkSalary)"
                @input="row.pieceSalary = parseNum(row.pieceSalary)"
              />
            </template>
          </el-table-column>
@@ -141,22 +160,22 @@
          <el-table-column label="社保个人" minWidth="110">
            <template #default="{ row }">
              <el-input
                v-model.number="row.socialSecurityIndividuals"
                v-model.number="row.socialPersonal"
                type="number"
                placeholder="0"
                size="small"
                @input="row.socialSecurityIndividuals = parseNum(row.socialSecurityIndividuals)"
                @input="row.socialPersonal = parseNum(row.socialPersonal)"
              />
            </template>
          </el-table-column>
          <el-table-column label="公积金个人" minWidth="120">
            <template #default="{ row }">
              <el-input
                v-model.number="row.providentFundIndividuals"
                v-model.number="row.fundPersonal"
                type="number"
                placeholder="0"
                size="small"
                @input="row.providentFundIndividuals = parseNum(row.providentFundIndividuals)"
                @input="row.fundPersonal = parseNum(row.fundPersonal)"
              />
            </template>
          </el-table-column>
@@ -238,11 +257,12 @@
import { ArrowUp } from "@element-plus/icons-vue";
import { listDept } from "@/api/system/dept.js";
import { staffOnJobList } from "@/api/personnelManagement/monthlyStatistics.js";
import { bankList } from "@/api/personnelManagement/bank.js";
import {
  monthlyStatisticsAdd,
  monthlyStatisticsUpdate,
  monthlyStatisticsGet,
} from "@/api/personnelManagement/monthlyStatistics.js";
  staffSalaryMainAdd,
  staffSalaryMainUpdate,
  staffSalaryMainCalculateSalary,
} from "@/api/personnelManagement/staffSalaryMain.js";
const emit = defineEmits(["update:modelValue", "close"]);
const props = defineProps({
@@ -267,6 +287,7 @@
const deptStaffTree = ref([]);
const employeeList = ref([]);
const selectedEmployees = ref([]);
const bankOptions = ref([]);
const taxTableData = ref([
  { level: 1, range: "不超过36000元", rate: 3, quickDeduction: 0 },
  { level: 2, range: "超过36000-144000元", rate: 10, quickDeduction: 2520 },
@@ -287,18 +308,28 @@
const data = reactive({
  form: {
    id: undefined,
    title: "",
    salaryTitle: "",
    deptId: undefined,
    payMonth: "",
    salaryMonth: "",
    remark: "",
    payBank: "",
  },
  rules: {
    title: [{ required: true, message: "请输入工资主题", trigger: "blur" }],
    salaryTitle: [{ required: true, message: "请输入工资主题", trigger: "blur" }],
    deptId: [{ required: true, message: "请选择部门", trigger: "change" }],
    payMonth: [{ required: true, message: "请选择工资月份", trigger: "change" }],
    salaryMonth: [{ required: true, message: "请选择工资月份", trigger: "change" }],
  },
});
const { form, rules } = toRefs(data);
const loadBankOptions = () => {
  return bankList().then((res) => {
    const list = Array.isArray(res?.data) ? res.data : [];
    bankOptions.value = list
      .map((b) => (b?.bankName == null ? "" : String(b.bankName).trim()))
      .filter((v) => v !== "");
  });
};
// æ‰å¹³åŒ–部门树供下拉使用
function flattenDept(tree, list = []) {
@@ -358,32 +389,25 @@
const openDialog = (type, row) => {
  nextTick(() => {
    loadDeptOptions();
    loadBankOptions();
    employeeList.value = [];
    Object.assign(form.value, {
      id: undefined,
      title: "",
      salaryTitle: "",
      deptId: undefined,
      payMonth: "",
      salaryMonth: "",
      remark: "",
      payBank: "",
    });
    // ç¼–辑:列表页已返回主表字段;这里只做回显(明细由“生成工资表/计算工资”得到)
    if (type === "edit" && row?.id) {
      monthlyStatisticsGet(row.id).then((res) => {
        const d = res.data || {};
        form.value.id = d.id;
        form.value.title = d.title ?? d.payDateStr ?? "";
        form.value.deptId = d.deptId;
        form.value.payMonth = d.payMonth ?? d.payDate ?? d.payDateStr ?? "";
        form.value.remark = d.remark ?? "";
        employeeList.value = (d.detailList || d.employeeList || []).map((e) => ({
          ...e,
          basicSalary: parseNum(e.basicSalary),
          pieceworkSalary: parseNum(e.pieceworkSalary),
          hourlySalary: parseNum(e.hourlySalary),
          otherIncome: parseNum(e.otherIncome),
          socialSecurityIndividuals: parseNum(e.socialSecurityIndividuals),
          providentFundIndividuals: parseNum(e.providentFundIndividuals),
        }));
      });
      form.value.id = row.id;
      form.value.salaryTitle = row.salaryTitle ?? "";
      // deptIds åŽç«¯æ˜¯å­—符串(多个用逗号分隔);当前表单仍是单选 deptId
      form.value.deptId = row.deptIds ? Number(String(row.deptIds).split(",")[0]) : undefined;
      form.value.salaryMonth = row.salaryMonth ?? "";
      form.value.remark = row.remark ?? "";
      form.value.payBank = row.payBank ?? "";
    }
  });
};
@@ -412,17 +436,23 @@
    if (existIds.has(id)) return;
    existIds.add(id);
    employeeList.value.push({
      staffId: id,
      id: id,
      staffOnJobId: id,
      id,
      staffName: node.label,
      roleName: node.roleName ?? node.role ?? "",
      postName: node.postName ?? node.post ?? "",
      deptName: node.deptName ?? "",
      basicSalary: 0,
      pieceworkSalary: 0,
      pieceSalary: 0,
      hourlySalary: 0,
      otherIncome: 0,
      socialSecurityIndividuals: 0,
      providentFundIndividuals: 0,
      socialPersonal: 0,
      fundPersonal: 0,
      otherDeduct: 0,
      salaryTax: 0,
      grossSalary: 0,
      deductSalary: 0,
      netSalary: 0,
      remark: "",
    });
  });
  addPersonVisible.value = false;
@@ -430,7 +460,7 @@
const removeEmployee = (row) => {
  employeeList.value = employeeList.value.filter(
    (e) => (e.staffId || e.id) !== (row.staffId || row.id)
    (e) => (e.staffOnJobId || e.id) !== (row.staffOnJobId || row.id)
  );
};
@@ -443,14 +473,55 @@
    proxy.$modal.msgWarning("请先勾选要删除的员工");
    return;
  }
  const ids = new Set(selectedEmployees.value.map((e) => e.staffId || e.id));
  const ids = new Set(selectedEmployees.value.map((e) => e.staffOnJobId || e.id));
  employeeList.value = employeeList.value.filter(
    (e) => !ids.has(e.staffId || e.id)
    (e) => !ids.has(e.staffOnJobId || e.id)
  );
};
const handleGenerate = () => {
  proxy.$modal.msgInfo("生成工资表功能需对接后端");
  if (!form.value.deptId) {
    proxy.$modal.msgWarning("请先选择部门");
    return;
  }
  if (!form.value.salaryMonth) {
    proxy.$modal.msgWarning("请先选择工资月份");
    return;
  }
  if (!employeeList.value?.length) {
    proxy.$modal.msgWarning("请先新增人员");
    return;
  }
  const ids = employeeList.value
    .map((e) => e.staffOnJobId ?? e.staffId ?? e.id)
    .filter(Boolean);
  staffSalaryMainCalculateSalary(ids).then((res) => {
    const list = Array.isArray(res?.data) ? res.data : [];
    if (!list.length) {
      proxy.$modal.msgWarning("未计算到工资数据");
      return;
    }
    employeeList.value = list.map((e) => ({
      ...e,
      staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id,
      staffName: e.staffName,
      postName: e.postName,
      deptName: e.deptName,
      basicSalary: parseNum(e.basicSalary),
      pieceSalary: parseNum(e.pieceSalary),
      hourlySalary: parseNum(e.hourlySalary),
      otherIncome: parseNum(e.otherIncome),
      socialPersonal: parseNum(e.socialPersonal),
      fundPersonal: parseNum(e.fundPersonal),
      otherDeduct: parseNum(e.otherDeduct),
      salaryTax: parseNum(e.salaryTax),
      grossSalary: parseNum(e.grossSalary),
      deductSalary: parseNum(e.deductSalary),
      netSalary: parseNum(e.netSalary),
      remark: e.remark ?? "",
    }));
    proxy.$modal.msgSuccess("生成成功");
  });
};
const handleExport = () => {
@@ -476,24 +547,31 @@
    if (!valid) return;
    const payload = {
      ...form.value,
      deptIds: form.value.deptId ? String(form.value.deptId) : "",
      detailList: employeeList.value.map((e) => ({
        staffId: e.staffId ?? e.id,
        staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id,
        staffName: e.staffName,
        basicSalary: parseNum(e.basicSalary),
        pieceworkSalary: parseNum(e.pieceworkSalary),
        pieceSalary: parseNum(e.pieceSalary),
        hourlySalary: parseNum(e.hourlySalary),
        otherIncome: parseNum(e.otherIncome),
        socialSecurityIndividuals: parseNum(e.socialSecurityIndividuals),
        providentFundIndividuals: parseNum(e.providentFundIndividuals),
        socialPersonal: parseNum(e.socialPersonal),
        fundPersonal: parseNum(e.fundPersonal),
        otherDeduct: parseNum(e.otherDeduct),
        salaryTax: parseNum(e.salaryTax),
        grossSalary: parseNum(e.grossSalary),
        deductSalary: parseNum(e.deductSalary),
        netSalary: parseNum(e.netSalary),
        remark: e.remark ?? "",
      })),
    };
    if (props.operationType === "add") {
      monthlyStatisticsAdd(payload).then(() => {
      staffSalaryMainAdd({ ...payload, status: 1 }).then(() => {
        proxy.$modal.msgSuccess("新增成功");
        closeDia();
      });
    } else {
      monthlyStatisticsUpdate(payload).then(() => {
      staffSalaryMainUpdate(payload).then(() => {
        proxy.$modal.msgSuccess("修改成功");
        closeDia();
      });
src/views/personnelManagement/monthlyStatistics/index.vue
@@ -10,20 +10,17 @@
          clearable
          @keyup.enter="handleQuery"
        />
        <span class="search_title ml10">单据状态:</span>
        <el-select
          v-model="searchForm.documentStatus"
          placeholder="请选择单据状态"
          clearable
          style="width: 180px"
        >
          <el-option label="草稿" value="draft" />
          <el-option label="已提交" value="submitted" />
          <el-option label="已审核" value="approved" />
        <span class="search_title ml10">状态:</span>
        <el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 180px">
          <el-option label="草稿" :value="1" />
          <el-option label="审核未通过" :value="2" />
          <el-option label="待审核" :value="3" />
          <el-option label="待发放" :value="4" />
          <el-option label="已发放" :value="5" />
        </el-select>
        <span class="search_title ml10">工资月份:</span>
        <el-date-picker
          v-model="searchForm.payMonth"
          v-model="searchForm.salaryMonth"
          type="month"
          value-format="YYYY-MM"
          format="YYYY-MM"
@@ -32,17 +29,6 @@
          clearable
          @change="handleQuery"
        />
        <span class="search_title ml10">审核状态:</span>
        <el-select
          v-model="searchForm.approvalStatus"
          placeholder="请选择审核状态"
          clearable
          style="width: 180px"
        >
          <el-option label="待审核" value="pending" />
          <el-option label="已通过" value="passed" />
          <el-option label="已驳回" value="rejected" />
        </el-select>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
          æœç´¢
        </el-button>
@@ -53,6 +39,7 @@
      <div style="margin-bottom: 10px">
        <el-button type="primary" @click="openForm('add')">新建工资表</el-button>
        <el-button @click="handleDelete">删除</el-button>
        <el-button @click="openBankSetting">设置银行</el-button>
        <el-button @click="handleExport">导出</el-button>
      </div>
      <PIMTable
@@ -74,12 +61,55 @@
      ref="formDiaRef"
      @close="handleQuery"
    />
    <bank-setting-dia
      v-model="bankDialogVisible"
      ref="bankDiaRef"
      @saved="handleBankSaved"
    />
    <el-dialog v-model="issueDialogVisible" title="工资发放" width="720px">
      <el-form label-position="top">
        <el-form-item label="发放银行" required>
          <el-select
            v-model="issueForm.bank"
            placeholder="请选择发放银行"
            clearable
            filterable
            style="width: 100%"
          >
            <el-option v-for="b in issueBankOptions" :key="b" :label="b" :value="b" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="issueDialogVisible = false">取消</el-button>
        <el-button type="primary" :loading="issueLoading" @click="confirmIssue">
          ç¡®å®š
        </el-button>
      </template>
    </el-dialog>
    <el-dialog v-model="auditDialogVisible" title="工资审核" width="720px">
      <el-form label-position="top">
        <el-form-item label="审核结果" required>
          <el-radio-group v-model="auditForm.result">
            <el-radio :value="4">通过</el-radio>
            <el-radio :value="2">不通过</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="auditDialogVisible = false">取消</el-button>
        <el-button type="primary" :loading="auditLoading" @click="confirmAudit">
          ç¡®å®š
        </el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {
  onMounted,
  computed,
  ref,
  reactive,
  toRefs,
@@ -87,44 +117,58 @@
  nextTick,
} from "vue";
import { ElMessageBox } from "element-plus";
import Cookies from "js-cookie";
import FormDia from "./components/formDia.vue";
import BankSettingDia from "./components/bankSettingDia.vue";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import { bankList } from "@/api/personnelManagement/bank.js";
import {
  monthlyStatisticsListPage,
  monthlyStatisticsDelete,
} from "@/api/personnelManagement/monthlyStatistics.js";
  staffSalaryMainListPage,
  staffSalaryMainDelete,
  staffSalaryMainUpdate,
} from "@/api/personnelManagement/staffSalaryMain.js";
const data = reactive({
  searchForm: {
    title: "",
    documentStatus: "",
    payMonth: "",
    approvalStatus: "",
    status: "",
    salaryMonth: "",
  },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
  { label: "工资主题", prop: "title", minWidth: 140 },
  { label: "工资月份", prop: "payMonth", width: 120 },
  { label: "单据状态", prop: "documentStatusName", width: 100 },
  { label: "审核状态", prop: "approvalStatusName", width: 100 },
  { label: "工资总额", prop: "totalAmount", width: 120 },
  { label: "支付银行", prop: "paymentBank", width: 120 },
  { label: "发放时间", prop: "issueTime", width: 160 },
  { label: "审批人员", prop: "approver", width: 100 },
  { label: "工资主题", prop: "salaryTitle", minWidth: 140 },
  { label: "工资月份", prop: "salaryMonth", width: 120 },
  { label: "状态", prop: "statusName", width: 110 },
  { label: "工资总额", prop: "totalSalary", width: 120 },
  { label: "支付银行", prop: "payBank", width: 120 },
  { label: "审核人", prop: "auditUserName", width: 110 },
  { label: "备注", prop: "remark", minWidth: 120 },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 120,
    width: 180,
    operation: [
      {
        name: "编辑",
        type: "text",
        showHide: (row) => Number(row?.status) === 1 || Number(row?.status) === 2,
        clickFun: (row) => openForm("edit", row),
      },
      {
        name: "审核",
        type: "text",
        showHide: (row) => Number(row?.status) === 3,
        clickFun: (row) => openAudit(row),
      },
      {
        name: "发放",
        type: "text",
        showHide: (row) => Number(row?.status) === 4,
        clickFun: (row) => openIssue(row),
      },
    ],
  },
@@ -143,6 +187,47 @@
const operationType = ref("add");
const currentRow = ref({});
const { proxy } = getCurrentInstance();
const bankSetting = ref({});
const bankDialogVisible = ref(false);
const bankDiaRef = ref(null);
const issueDialogVisible = ref(false);
const issueLoading = ref(false);
const issueRow = ref(null);
const issueForm = reactive({ bank: "" });
const auditDialogVisible = ref(false);
const auditLoading = ref(false);
const auditRow = ref(null);
const auditForm = reactive({ result: 4 }); // 4=通过(待发放) 2=不通过
const issueBankOptions = computed(() => {
  const options = Array.isArray(bankSetting.value?.options) ? bankSetting.value.options : [];
  return options
    .map((v) => (v == null ? "" : String(v).trim()))
    .filter((v) => v !== "");
});
const statusName = (s) => {
  const n = Number(s);
  return (
    {
      1: "草稿",
      2: "审核未通过",
      3: "待审核",
      4: "待发放",
      5: "已发放",
    }[n] || "-"
  );
};
const loadBankSetting = () => {
  return bankList().then((res) => {
    const list = Array.isArray(res?.data) ? res.data : [];
    const options = list
      .map((b) => (b?.bankName == null ? "" : String(b.bankName).trim()))
      .filter((v) => v !== "");
    bankSetting.value = { options, defaultBank: "" };
  });
};
const handleQuery = () => {
  page.current = 1;
@@ -151,9 +236,8 @@
const handleReset = () => {
  searchForm.value.title = "";
  searchForm.value.documentStatus = "";
  searchForm.value.payMonth = "";
  searchForm.value.approvalStatus = "";
  searchForm.value.status = "";
  searchForm.value.salaryMonth = "";
  page.current = 1;
  getList();
};
@@ -166,27 +250,25 @@
const getList = () => {
  tableLoading.value = true;
  monthlyStatisticsListPage({
  staffSalaryMainListPage({
    ...searchForm.value,
    current: page.current,
    size: page.size,
  })
    .then((res) => {
      tableLoading.value = false;
      const records = res.data?.records ?? [];
      const records = res.data?.records ?? res.data?.list ?? [];
      // å…¼å®¹åŽç«¯å­—段:若接口仍返回台账结构,可在此做映射
      tableData.value = records.map((item) => ({
        ...item,
        title: item.title ?? item.payDateStr ?? "-",
        payMonth: item.payMonth ?? item.payDateStr ?? item.payDate ?? "-",
        documentStatusName: item.documentStatusName ?? "-",
        approvalStatusName: item.approvalStatusName ?? "-",
        totalAmount: item.totalAmount ?? item.actualWages ?? "-",
        paymentBank: item.paymentBank ?? "-",
        issueTime: item.issueTime ?? item.createTime ?? "-",
        approver: item.approver ?? "-",
        salaryTitle: item.salaryTitle ?? "-",
        salaryMonth: item.salaryMonth ?? "-",
        statusName: statusName(item.status),
        totalSalary: item.totalSalary ?? "-",
        payBank: (item.payBank == null ? "" : String(item.payBank).trim()) || "-",
        auditUserName: item.auditUserName ?? "-",
      }));
      page.total = res.data?.total ?? 0;
      page.total = res.data?.total ?? res.data?.count ?? 0;
    })
    .catch(() => {
      tableLoading.value = false;
@@ -206,6 +288,85 @@
  });
};
const openBankSetting = () => {
  bankDialogVisible.value = true;
};
const openAudit = (row) => {
  auditRow.value = row || null;
  auditForm.result = 4;
  auditDialogVisible.value = true;
};
const openIssue = (row) => {
  if (!issueBankOptions.value?.length) {
    proxy?.$modal?.msgWarning?.("请先在“设置银行”中维护发放银行选项");
    return;
  }
  issueRow.value = row || null;
  const current = row?.payBank && row.payBank !== "-" ? String(row.payBank).trim() : "";
  issueForm.bank = current;
  issueDialogVisible.value = true;
};
const confirmAudit = () => {
  const row = auditRow.value;
  if (!row?.id) {
    auditDialogVisible.value = false;
    return;
  }
  const username = Cookies.get("username") || "";
  const userIdRaw = Cookies.get("userId");
  const auditUserId = userIdRaw ? Number(userIdRaw) : undefined;
  auditLoading.value = true;
  staffSalaryMainUpdate({
    id: row.id,
    status: Number(auditForm.result) === 2 ? 2 : 4,
    auditUserId,
    auditUserName: username,
  })
    .then(() => {
      proxy?.$modal?.msgSuccess?.("审核成功");
      auditDialogVisible.value = false;
      getList();
    })
    .finally(() => {
      auditLoading.value = false;
    });
};
const confirmIssue = () => {
  const bank = issueForm.bank ? String(issueForm.bank).trim() : "";
  if (!bank) {
    proxy?.$modal?.msgWarning?.("请选择发放银行");
    return;
  }
  const row = issueRow.value;
  if (!row?.id) {
    issueDialogVisible.value = false;
    return;
  }
  issueLoading.value = true;
  staffSalaryMainUpdate({
    id: row.id,
    payBank: bank,
    status: 5,
  })
    .then(() => {
      proxy?.$modal?.msgSuccess?.("发放成功");
      issueDialogVisible.value = false;
      getList();
    })
    .finally(() => {
      issueLoading.value = false;
    });
};
const handleBankSaved = () => {
  loadBankSetting();
  getList();
};
const handleDelete = () => {
  if (!selectedRows.value?.length) {
    proxy.$modal.msgWarning("请选择要删除的数据");
@@ -218,7 +379,7 @@
    type: "warning",
  })
    .then(() => {
      monthlyStatisticsDelete(ids).then(() => {
      staffSalaryMainDelete(ids).then(() => {
        proxy.$modal.msgSuccess("删除成功");
        getList();
      });
@@ -235,6 +396,7 @@
};
onMounted(() => {
  loadBankSetting();
  getList();
});
</script>
src/views/personnelManagement/socialSecuritySet/components/formDia.vue
@@ -15,7 +15,10 @@
          <el-col :span="8">
            <el-form-item label="适用人员:" prop="deptIds">
              <div class="dept-checkbox-wrap">
                <el-checkbox-group v-model="form.deptIds">
                <el-checkbox-group
                  v-model="form.deptIds"
                  :disabled="isDetail"
                >
                  <div
                    v-for="dept in deptList"
                    :key="dept.deptId"
@@ -41,7 +44,12 @@
                <el-icon class="card-collapse"><ArrowUp /></el-icon>
              </template>
              <el-form-item label="方案标题:" prop="title">
                <el-input v-model="form.title" placeholder="请输入" clearable />
                <el-input
                  v-model="form.title"
                  placeholder="请输入"
                  clearable
                  :disabled="isDetail"
                />
              </el-form-item>
              <el-form-item label="备注:" prop="remark">
                <el-input
@@ -50,6 +58,7 @@
                  :rows="2"
                  placeholder="请输入"
                  clearable
                  :disabled="isDetail"
                />
              </el-form-item>
            </el-card>
@@ -58,7 +67,12 @@
            <el-card class="form-card" shadow="never">
              <template #header>
                <span class="card-title"><span class="card-title-line">|</span> ä¿é™©ç±»åž‹</span>
                <el-button type="primary" size="small" @click="addInsuranceBenefit">
                <el-button
                  v-if="!isDetail"
                  type="primary"
                  size="small"
                  @click="addInsuranceBenefit"
                >
                  æ·»åŠ ä¿é™©ç¦åˆ©
                </el-button>
              </template>
@@ -72,7 +86,7 @@
                    <div class="insurance-benefit-title">
                      ä¿é™©ç¦åˆ©{{ index + 1 }}
                      <el-button
                        v-if="form.insuranceBenefits.length > 1"
                        v-if="!isDetail && form.insuranceBenefits.length > 1"
                        type="danger"
                        link
                        size="small"
@@ -92,6 +106,7 @@
                        placeholder="请选择"
                        clearable
                        style="width: 100%"
                        :disabled="isDetail"
                      >
                        <el-option
                          v-for="opt in insuranceTypeOptions"
@@ -102,9 +117,23 @@
                      </el-select>
                    </el-form-item>
                    <el-form-item label="缴费基数:" label-width="100px">
                      <div class="checkbox-group-inline">
                        <el-checkbox v-model="item.baseOnSalary">根据基本工资缴纳</el-checkbox>
                        <el-checkbox v-model="item.useBasicSalary">调用基本工资</el-checkbox>
                      <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">
@@ -115,6 +144,7 @@
                          clearable
                          style="width: 100px"
                          type="number"
                          :disabled="isDetail"
                        />
                        <span class="ratio-unit">(%)</span>
                        <span class="ratio-plus">+</span>
@@ -124,6 +154,7 @@
                          clearable
                          style="width: 100px"
                          type="number"
                          :disabled="isDetail"
                        />
                      </div>
                    </el-form-item>
@@ -139,15 +170,11 @@
</template>
<script setup>
import { ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
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 {
  socialSecurityInfo,
  socialSecurityAdd,
  socialSecurityUpdate,
} from "@/api/personnelManagement/socialSecuritySet.js";
import { socialSecurityAdd, socialSecurityUpdate } from "@/api/personnelManagement/socialSecuritySet.js";
const emit = defineEmits(["close"]);
const { proxy } = getCurrentInstance();
@@ -157,22 +184,28 @@
const formRef = ref(null);
const deptList = ref([]);
const isDetail = computed(() => operationType.value === "detail");
const dialogTitle = () =>
  operationType.value === "add" ? "新增方案" : "编辑方案";
  operationType.value === "add"
    ? "新增方案"
    : operationType.value === "edit"
    ? "编辑方案"
    : "方案详情";
// ä¿é™©ç±»åž‹é€‰é¡¹ï¼ˆå¯æŒ‰å­—典替换)
const insuranceTypeOptions = [
  { label: "养老保险", value: "pension" },
  { label: "医疗保险", value: "medical" },
  { label: "失业保险", value: "unemployment" },
  { label: "工伤保险", value: "work_injury" },
  { label: "生育保险", value: "maternity" },
  { label: "养老保险", value: "养老保险" },
  { label: "医疗保险", value: "医疗保险" },
  { label: "失业保险", value: "失业保险" },
  { label: "工伤保险", value: "工伤保险" },
  { label: "生育保险", value: "生育保险" },
];
const defaultBenefit = () => ({
  _key: Math.random().toString(36).slice(2),
  insuranceType: "",
  baseOnSalary: false,
  paymentBase: "",
  useBasicSalary: false,
  personalRatio: "",
  personalFixed: "",
@@ -231,6 +264,18 @@
  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,
@@ -246,32 +291,77 @@
  dialogFormVisible.value = true;
  loadDeptList();
  resetForm();
  if (type === "edit" && row?.id) {
    socialSecurityInfo(row.id).then((res) => {
      const d = res.data || {};
      form.value.id = d.id;
      form.value.title = d.title;
      form.value.remark = d.remark ?? "";
      form.value.deptIds = d.deptIds ?? [];
      form.value.insuranceBenefits =
        (d.insuranceBenefits && d.insuranceBenefits.length)
          ? d.insuranceBenefits.map((b) => ({
              ...b,
              _key: b._key || Math.random().toString(36).slice(2),
            }))
          : [defaultBenefit()];
    });
  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 = {
      ...form.value,
      insuranceBenefits: form.value.insuranceBenefits.map(
        ({ _key, ...rest }) => rest
      ),
      id: form.value.id,
      title: form.value.title,
      remark: form.value.remark ?? "",
      deptIds,
      insuranceTypes,
      deptNames,
      schemeInsuranceDetailList,
    };
    if (operationType.value === "add") {
      socialSecurityAdd(submitData).then(() => {
@@ -356,6 +446,16 @@
  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;
src/views/personnelManagement/socialSecuritySet/index.vue
@@ -17,8 +17,15 @@
      </div>
    </div>
    <div class="table_list">
      <div style="margin-bottom: 10px">
      <div style="margin-bottom: 10px; display: flex; gap: 10px">
        <el-button type="primary" @click="openForm('add')">新增方案</el-button>
        <el-button
          type="danger"
          @click="handleBatchDelete"
          :disabled="selectedRows.length === 0"
        >
          æ‰¹é‡åˆ é™¤
        </el-button>
      </div>
      <PIMTable
        rowKey="id"
@@ -26,8 +33,10 @@
        :tableData="tableData"
        :page="page"
        :tableLoading="tableLoading"
        @pagination="pagination"
        :total="page.total"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        @pagination="pagination"
      />
    </div>
    <form-dia ref="formDiaRef" @close="handleQuery" />
@@ -36,9 +45,13 @@
<script setup>
import { onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import { ElMessageBox, ElMessage } from "element-plus";
import FormDia from "./components/formDia.vue";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import { socialSecurityListPage } from "@/api/personnelManagement/socialSecuritySet.js";
import {
  socialSecurityListPage,
  socialSecurityDelete,
} from "@/api/personnelManagement/socialSecuritySet.js";
const data = reactive({
  searchForm: {
@@ -49,22 +62,35 @@
const tableColumn = ref([
  { label: "主题", prop: "title", minWidth: 120 },
  { label: "保险类型", prop: "insuranceTypeName", width: 120 },
  { label: "使用范围", prop: "scopeName", width: 120 },
  { label: "保险类型", prop: "insuranceTypes", width: 120 },
  { label: "使用范围", prop: "deptNames", width: 120 },
  { label: "备注", prop: "remark", minWidth: 120 },
  { label: "创建时间", prop: "createTime", width: 160 },
  { label: "创建人", prop: "createBy", width: 100 },
  { label: "创建人", prop: "createUserName", width: 100 },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 120,
    width: 180,
    operation: [
      {
    {
        name: "编辑",
        type: "text",
        clickFun: (row) => openForm("edit", row),
      },
      {
        name: "详情",
        type: "text",
        clickFun: (row) => openForm("detail", row),
      },
      {
        name: "删除",
        type: "text",
        style: {
          color: "#F56C6C",
        },
        clickFun: (row) => handleDelete(row),
      },
    ],
  },
@@ -72,6 +98,7 @@
const tableData = ref([]);
const tableLoading = ref(false);
const selectedRows = ref([]);
const page = reactive({
  current: 1,
  size: 10,
@@ -113,12 +140,70 @@
    });
};
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
const openForm = (type, row) => {
  nextTick(() => {
    formDiaRef.value?.openDialog(type, row);
  });
};
// åˆ é™¤æ–¹æ¡ˆï¼Œé€»è¾‘与其它页面保持一致(确认弹窗 + è°ƒç”¨åˆ é™¤æŽ¥å£ + åˆ·æ–°åˆ—表)
const handleDelete = (row) => {
  ElMessageBox.confirm(
    `确认删除方案"${row.title}"吗?`,
    "删除确认",
    {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    }
  )
    .then(() => {
      socialSecurityDelete([row.id])
        .then(() => {
          ElMessage.success("删除成功");
          getList();
        })
        .catch(() => {
          ElMessage.error("删除失败");
        });
    })
    .catch(() => {
      ElMessage.info("已取消删除");
    });
};
// æ‰¹é‡åˆ é™¤
const handleBatchDelete = () => {
  if (!selectedRows.value.length) return;
  ElMessageBox.confirm(
    `确定要删除选中的 ${selectedRows.value.length} æ¡æ–¹æ¡ˆå—?`,
    "批量删除确认",
    {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    }
  )
    .then(() => {
      const ids = selectedRows.value.map((item) => item.id);
      socialSecurityDelete(ids)
        .then(() => {
          ElMessage.success("删除成功");
          getList();
        })
        .catch(() => {
          ElMessage.error("删除失败");
        });
    })
    .catch(() => {
      ElMessage.info("已取消删除");
    });
};
onMounted(() => {
  getList();
});