进销存升级
1.添加社会保险设置页面,开发与联调
2.丰富新增入职所填字段字段并于用户管理关联
3.修改人员薪资页面样式和逻辑
已添加7个文件
已修改3个文件
3226 ■■■■ 文件已修改
src/api/personnelManagement/socialSecuritySet.js 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/BasicInfoSection.vue 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/EducationWorkSection.vue 263 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/EmergencyAndAttachmentSection.vue 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/JobInfoSection.vue 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue 620 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/components/formDia.vue 819 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/index.vue 503 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/socialSecuritySet/components/formDia.vue 369 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/socialSecuritySet/index.vue 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/socialSecuritySet.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
// ç¤¾ä¼šä¿é™©è®¾ç½®
import request from "@/utils/request";
// åˆ†é¡µæŸ¥è¯¢åˆ—表
export function socialSecurityListPage(query) {
  return request({
    url: "/socialSecurity/plan/listPage",
    method: "get",
    params: query,
  });
}
// æŸ¥è¯¢è¯¦æƒ…
export function socialSecurityInfo(id) {
  return request({
    url: "/socialSecurity/plan/" + id,
    method: "get",
  });
}
// æ–°å¢ž
export function socialSecurityAdd(data) {
  return request({
    url: "/socialSecurity/plan/add",
    method: "post",
    data,
  });
}
// ä¿®æ”¹
export function socialSecurityUpdate(data) {
  return request({
    url: "/socialSecurity/plan/update",
    method: "post",
    data,
  });
}
// åˆ é™¤
export function socialSecurityDelete(ids) {
  return request({
    url: "/socialSecurity/plan/delete",
    method: "delete",
    data: ids,
  });
}
src/views/personnelManagement/employeeRecord/components/BasicInfoSection.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,159 @@
<template>
  <el-card class="form-card" shadow="never">
    <template #header>
      <span class="card-title">
        <span class="card-title-line">|</span>
        åŸºæœ¬ä¿¡æ¯
      </span>
    </template>
    <el-row :gutter="24">
      <el-col :span="5">
        <el-form-item label="员工编号" prop="staffNo">
          <el-input
            v-model="form.staffNo"
            placeholder="请输入"
            clearable
            maxlength="20"
            show-word-limit
            :disabled="operationType !== 'add'"
          />
        </el-form-item>
      </el-col>
      <el-col :span="5">
        <el-form-item label="姓名" prop="staffName">
          <el-input
            v-model="form.staffName"
            placeholder="请输入"
            clearable
            maxlength="50"
            show-word-limit
          />
        </el-form-item>
      </el-col>
      <el-col :span="5">
        <el-form-item label="别名" prop="aliasName">
          <el-input
            v-model="form.aliasName"
            placeholder="请输入"
            clearable
            maxlength="50"
            show-word-limit
          />
        </el-form-item>
      </el-col>
      <el-col :span="5">
        <el-form-item label="手机" prop="phone">
          <el-input
            v-model="form.phone"
            placeholder="请输入"
            clearable
            maxlength="11"
            show-word-limit
          />
        </el-form-item>
      </el-col>
      <el-col :span="4">
        <el-form-item label="性别" prop="sex">
          <el-select
            v-model="form.sex"
            placeholder="请选择"
            clearable
            style="width: 100%"
          >
            <el-option label="男" value="男" />
            <el-option label="女" value="女" />
          </el-select>
        </el-form-item>
      </el-col>
    </el-row>
    <el-row :gutter="24">
      <el-col :span="5">
        <el-form-item label="出生日期" prop="birthDate">
          <el-date-picker
            v-model="form.birthDate"
            type="date"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
            placeholder="请选择"
            style="width: 100%"
            clearable
          />
        </el-form-item>
      </el-col>
      <el-col :span="5">
        <el-form-item label="年龄" prop="age">
          <el-input-number
            v-model="form.age"
            :min="0"
            :max="150"
            :precision="0"
            :step="1"
            style="width: 100%"
          />
        </el-form-item>
      </el-col>
      <el-col :span="5">
        <el-form-item label="籍贯" prop="nativePlace">
          <el-input
            v-model="form.nativePlace"
            placeholder="请输入"
            clearable
            maxlength="50"
            show-word-limit
          />
        </el-form-item>
      </el-col>
      <el-col :span="5">
        <el-form-item label="民族" prop="nation">
          <el-input
            v-model="form.nation"
            placeholder="请输入"
            clearable
            maxlength="20"
            show-word-limit
          />
        </el-form-item>
      </el-col>
      <el-col :span="4">
        <el-form-item label="婚姻状况" prop="maritalStatus">
          <el-select
            v-model="form.maritalStatus"
            placeholder="请选择"
            clearable
            style="width: 100%"
          >
            <el-option label="未婚" value="single" />
            <el-option label="已婚" value="married" />
            <el-option label="离异" value="divorced" />
            <el-option label="丧偶" value="widowed" />
          </el-select>
        </el-form-item>
      </el-col>
    </el-row>
  </el-card>
</template>
<script setup>
import { toRefs } from "vue";
const props = defineProps({
  form: { type: Object, required: true },
  operationType: { type: String, default: "add" },
});
const { form, operationType } = toRefs(props);
</script>
<style scoped>
.form-card {
  margin-bottom: 16px;
}
.card-title-line {
  color: #f56c6c;
  margin-right: 4px;
}
</style>
src/views/personnelManagement/employeeRecord/components/EducationWorkSection.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,263 @@
<template>
  <div>
    <!-- æ•™è‚²ç»åކ -->
    <el-card class="form-card" shadow="never">
      <template #header>
        <span class="card-title">
          <span class="card-title-line">|</span>
          æ•™è‚²ç»åކ
        </span>
      </template>
      <el-table :data="form.educationList" border>
        <el-table-column label="学历" prop="degree" width="120">
          <template #default="{ row }">
            <el-select
              v-model="row.degree"
              placeholder="请选择"
              clearable
              style="width: 100%"
            >
              <el-option label="中专及以下" value="secondary" />
              <el-option label="大专" value="junior_college" />
              <el-option label="本科" value="bachelor" />
              <el-option label="硕士" value="master" />
              <el-option label="博士及以上" value="doctor" />
            </el-select>
          </template>
        </el-table-column>
        <el-table-column label="毕业院校" prop="school" min-width="160">
          <template #default="{ row }">
            <el-input
              v-model="row.school"
              placeholder="请输入"
              clearable
              maxlength="30"
              show-word-limit
            />
          </template>
        </el-table-column>
        <el-table-column label="入学时间" prop="admissionDate" width="150">
          <template #default="{ row }">
            <el-date-picker
              v-model="row.admissionDate"
              type="date"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              placeholder="请选择"
              style="width: 100%"
              clearable
            />
          </template>
        </el-table-column>
        <el-table-column label="毕业时间" prop="graduationDate" width="150">
          <template #default="{ row }">
            <el-date-picker
              v-model="row.graduationDate"
              type="date"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              placeholder="请选择"
              style="width: 100%"
              clearable
            />
          </template>
        </el-table-column>
        <el-table-column label="专业" prop="major" min-width="140">
          <template #default="{ row }">
            <el-input
              v-model="row.major"
              placeholder="请输入"
              clearable
              maxlength="20"
              show-word-limit
            />
          </template>
        </el-table-column>
        <el-table-column label="学位" prop="academicDegree" width="140">
          <template #default="{ row }">
            <el-input
              v-model="row.academicDegree"
              placeholder="请输入"
              clearable
              maxlength="20"
              show-word-limit
            />
          </template>
        </el-table-column>
        <el-table-column label="操作" width="80" align="center">
          <template #default="scope">
            <el-button
              v-if="form.educationList.length > 1"
              type="primary"
              link
              @click="removeEducationRow(scope.$index)"
            >
              åˆ é™¤
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="table-add-row" @click="addEducationRow">新建一行</div>
    </el-card>
    <!-- å·¥ä½œç»åކ -->
    <el-card class="form-card" shadow="never">
      <template #header>
        <span class="card-title">
          <span class="card-title-line">|</span>
          å·¥ä½œç»åކ
        </span>
      </template>
      <el-table :data="form.workExperienceList" border>
        <el-table-column label="前公司" prop="company" min-width="180">
          <template #default="{ row }">
            <el-input
              v-model="row.company"
              placeholder="请输入"
              clearable
              maxlength="30"
              show-word-limit
            />
          </template>
        </el-table-column>
        <el-table-column label="前公司部门" prop="department" min-width="140">
          <template #default="{ row }">
            <el-input
              v-model="row.department"
              placeholder="请输入"
              clearable
              maxlength="20"
              show-word-limit
            />
          </template>
        </el-table-column>
        <el-table-column label="前公司职位" prop="position" min-width="140">
          <template #default="{ row }">
            <el-input
              v-model="row.position"
              placeholder="请输入"
              clearable
              maxlength="20"
              show-word-limit
            />
          </template>
        </el-table-column>
        <el-table-column label="开始日期" prop="startDate" width="150">
          <template #default="{ row }">
            <el-date-picker
              v-model="row.startDate"
              type="date"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              placeholder="请选择"
              style="width: 100%"
              clearable
            />
          </template>
        </el-table-column>
        <el-table-column label="结束日期" prop="endDate" width="150">
          <template #default="{ row }">
            <el-date-picker
              v-model="row.endDate"
              type="date"
              value-format="YYYY-MM-DD"
              format="YYYY-MM-DD"
              placeholder="请选择"
              style="width: 100%"
              clearable
            />
          </template>
        </el-table-column>
        <el-table-column label="工作描述" prop="description" min-width="220">
          <template #default="{ row }">
            <el-input
              v-model="row.description"
              type="textarea"
              :rows="2"
              placeholder="请输入"
              clearable
              maxlength="500"
              show-word-limit
            />
          </template>
        </el-table-column>
        <el-table-column label="操作" width="80" align="center">
          <template #default="scope">
            <el-button
              v-if="form.workExperienceList.length > 1"
              type="primary"
              link
              @click="removeWorkRow(scope.$index)"
            >
              åˆ é™¤
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="table-add-row" @click="addWorkRow">新建一行</div>
    </el-card>
  </div>
</template>
<script setup>
import { toRefs } from "vue";
const props = defineProps({
  form: { type: Object, required: true },
});
const emit = defineEmits(["update:form"]);
const { form } = toRefs(props);
const addEducationRow = () => {
  form.value.educationList.push({
    degree: "",
    school: "",
    admissionDate: "",
    graduationDate: "",
    major: "",
    academicDegree: "",
  });
};
const removeEducationRow = (index) => {
  if (form.value.educationList.length <= 1) return;
  form.value.educationList.splice(index, 1);
};
const addWorkRow = () => {
  form.value.workExperienceList.push({
    company: "",
    department: "",
    position: "",
    startDate: "",
    endDate: "",
    description: "",
  });
};
const removeWorkRow = (index) => {
  if (form.value.workExperienceList.length <= 1) return;
  form.value.workExperienceList.splice(index, 1);
};
</script>
<style scoped>
.form-card {
  margin-bottom: 16px;
}
.card-title-line {
  color: #f56c6c;
  margin-right: 4px;
}
.table-add-row {
  margin-top: 8px;
  color: #409eff;
  cursor: pointer;
  font-size: 14px;
}
</style>
src/views/personnelManagement/employeeRecord/components/EmergencyAndAttachmentSection.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,148 @@
<template>
  <div>
    <!-- ç´§æ€¥è”系人 -->
    <el-card class="form-card" shadow="never">
      <template #header>
        <span class="card-title">
          <span class="card-title-line">|</span>
          ç´§æ€¥è”系人
        </span>
      </template>
      <el-table :data="form.emergencyContacts" border>
        <el-table-column label="紧急联系人姓名" prop="name" min-width="160">
          <template #default="{ row }">
            <el-input
              v-model="row.name"
              placeholder="请输入"
              clearable
              maxlength="50"
              show-word-limit
            />
          </template>
        </el-table-column>
        <el-table-column label="紧急联系人关系" prop="relation" min-width="140">
          <template #default="{ row }">
            <el-input
              v-model="row.relation"
              placeholder="请输入"
              clearable
              maxlength="20"
              show-word-limit
            />
          </template>
        </el-table-column>
        <el-table-column label="紧急联系人手机" prop="phone" width="160">
          <template #default="{ row }">
            <el-input
              v-model="row.phone"
              placeholder="请输入"
              clearable
              maxlength="11"
              show-word-limit
            />
          </template>
        </el-table-column>
        <el-table-column label="紧急联系人住址" prop="address" min-width="220">
          <template #default="{ row }">
            <el-input
              v-model="row.address"
              placeholder="请输入"
              clearable
              maxlength="50"
              show-word-limit
            />
          </template>
        </el-table-column>
        <el-table-column label="操作" width="80" align="center">
          <template #default="scope">
            <el-button
              v-if="form.emergencyContacts.length > 1"
              type="primary"
              link
              @click="removeEmergencyRow(scope.$index)"
            >
              åˆ é™¤
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="table-add-row" @click="addEmergencyRow">新建一行</div>
    </el-card>
    <!-- ææ–™é™„ä»¶ -->
    <el-card class="form-card" shadow="never">
      <template #header>
        <div class="card-title">
          <span class="card-title-line">|</span>
          <span>材料附件</span>
          <span class="upload-tip">
            å›¾ç‰‡æ”¯æŒjpeg、jpg、png等格式,附件文件支持pdf、rar、zip、doc、docx格式。
          </span>
        </div>
      </template>
      <el-form-item label="附件">
        <el-upload
          v-model:file-list="form.attachments"
          action="#"
          :auto-upload="false"
          multiple
          list-type="picture-card"
        >
          <el-icon>
            <Plus />
          </el-icon>
        </el-upload>
      </el-form-item>
    </el-card>
  </div>
</template>
<script setup>
import { toRefs } from "vue";
import { Plus } from "@element-plus/icons-vue";
const props = defineProps({
  form: { type: Object, required: true },
});
const { form } = toRefs(props);
const addEmergencyRow = () => {
  form.value.emergencyContacts.push({
    name: "",
    relation: "",
    phone: "",
    address: "",
  });
};
const removeEmergencyRow = (index) => {
  if (form.value.emergencyContacts.length <= 1) return;
  form.value.emergencyContacts.splice(index, 1);
};
</script>
<style scoped>
.form-card {
  margin-bottom: 16px;
}
.card-title-line {
  color: #f56c6c;
  margin-right: 4px;
}
.table-add-row {
  margin-top: 8px;
  color: #409eff;
  cursor: pointer;
  font-size: 14px;
}
.upload-tip {
  margin-left: 12px;
  font-size: 12px;
  color: #909399;
}
</style>
src/views/personnelManagement/employeeRecord/components/JobInfoSection.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,172 @@
<template>
  <el-card class="form-card" shadow="never">
    <template #header>
      <span class="card-title">
        <span class="card-title-line">|</span>
        åœ¨èŒä¿¡æ¯
      </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-date-picker
            v-model="form.entryDate"
            type="date"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
            placeholder="请选择"
            style="width: 100%"
            clearable
          />
        </el-form-item>
      </el-col>
      <el-col :span="5">
        <el-form-item label="试用期(月)" prop="probationPeriod">
          <el-input-number
            v-model="form.probationPeriod"
            :min="0"
            :max="24"
            :precision="0"
            :step="1"
            style="width: 100%"
          />
        </el-form-item>
      </el-col>
      <el-col :span="4">
        <el-form-item label="转正日期" prop="regularDate">
          <el-date-picker
            v-model="form.regularDate"
            type="date"
            value-format="YYYY-MM-DD"
            format="YYYY-MM-DD"
            placeholder="请选择"
            style="width: 100%"
            clearable
          />
        </el-form-item>
      </el-col>
    </el-row>
    <el-row :gutter="24">
      <el-col :span="8">
        <el-form-item label="部门" prop="sysDeptId">
          <el-tree-select
            v-model="form.sysDeptId"
            :data="deptOptions"
            check-strictly
            :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>
      <el-col :span="8">
        <el-form-item label="岗位" prop="sysPostId">
          <el-select
            v-model="form.sysPostId"
            placeholder="请选择"
            clearable
            style="width: 100%"
          >
            <el-option
              v-for="item in postOptions"
              :key="item.postId"
              :label="item.postName"
              :value="item.postId"
              :disabled="item.status === '1'"
            />
          </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-form-item label="基本工资" prop="basicSalary">
          <el-input-number
            v-model="form.basicSalary"
            :min="0"
            :max="999999"
            :precision="2"
            :step="100"
            style="width: 100%"
          />
        </el-form-item>
      </el-col>
    </el-row>
  </el-card>
</template>
<script setup>
import { toRefs } from "vue";
const props = defineProps({
  form: { type: Object, required: true },
  postOptions: { type: Array, default: () => [] },
  deptOptions: { type: Array, default: () => [] },
});
const { form, postOptions, deptOptions } = toRefs(props);
</script>
<style scoped>
.form-card {
  margin-bottom: 16px;
}
.card-title-line {
  color: #f56c6c;
  margin-right: 4px;
}
</style>
src/views/personnelManagement/employeeRecord/components/NewOrEditFormDia.vue
@@ -1,373 +1,299 @@
<template>
  <div>
    <el-dialog v-model="dialogFormVisible"
               :title="operationType === 'add' ? '新增入职' : '编辑人员'"
               width="70%"
               @close="closeDia">
      <el-form :model="form"
               label-width="140px"
               label-position="top"
               :rules="rules"
               ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="员工编号:"
                          prop="staffNo">
              <el-input v-model="form.staffNo"
                        placeholder="请输入"
                        clearable
                        :disabled="operationType !== 'add'" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="姓名:"
                          prop="staffName">
              <el-input v-model="form.staffName"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="性别:"
                          prop="sex">
              <el-select v-model="form.sex">
                <el-option label="男"
                           value="男" />
                <el-option label="女"
                           value="女" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="户籍住址:"
                          prop="nativePlace">
              <el-input v-model="form.nativePlace"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="岗位:"
                          prop="sysPostId">
              <el-select v-model="form.sysPostId"
                         placeholder="请选择岗位"
                         clearable>
                <el-option v-for="item in postOptions"
                           :key="item.postId"
                           :label="item.postName"
                           :value="item.postId"
                           :disabled="item.status === '1'" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="现住址:"
                          prop="adress">
              <el-input v-model="form.adress"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="部门:"
                          prop="sysDeptId">
              <el-tree-select v-model="form.sysDeptId"
                              :data="deptOptions"
                              :props="{ value: 'id', label: 'label', children: 'children' }"
                              value-key="id"
                              placeholder="请选择部门"
                              check-strictly />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="年龄:"
                          prop="age">
              <el-input-number v-model="form.age"
                               :precision="0"
                               :step="1"
                               style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="第一学历:"
                          prop="firstStudy">
              <el-input v-model="form.firstStudy"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="专业:"
                          prop="profession">
              <el-input v-model="form.profession"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="联系电话:"
                          prop="phone">
              <el-input v-model="form.phone"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="紧急联系人:"
                          prop="emergencyContact">
              <el-input v-model="form.emergencyContact"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="紧急联系人联系电话:"
                          prop="emergencyContactPhone">
              <el-input v-model="form.emergencyContactPhone"
                        placeholder="请输入"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="合同年限:"
                          prop="contractTerm">
              <el-input-number v-model="form.contractTerm"
                               :precision="0"
                               :step="1"
                               style="width: 100%"
                               :disabled="true" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="合同开始日期:"
                          prop="contractStartTime">
              <el-date-picker v-model="form.contractStartTime"
                              type="date"
                              placeholder="请选择日期"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              clearable
                              style="width: 100%"
                              @change="calculateContractTerm" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="合同结束日期:"
                          prop="contractEndTime">
              <el-date-picker v-model="form.contractEndTime"
                              type="date"
                              placeholder="请选择日期"
                              value-format="YYYY-MM-DD"
                              format="YYYY-MM-DD"
                              clearable
                              style="width: 100%"
                              @change="calculateContractTerm" />
            </el-form-item>
          </el-col>
        </el-row>
  <FormDialog
    v-model="dialogFormVisible"
    :operation-type="operationType"
    :title="dialogTitle"
    width="90%"
    @close="closeDia"
    @confirm="submitForm"
    @cancel="closeDia"
  >
    <div class="form-dia-body">
      <el-form
        ref="formRef"
        :model="form"
        :rules="rules"
        label-position="top"
      >
        <BasicInfoSection :form="form" :operation-type="operationType" />
        <JobInfoSection
          :form="form"
          :post-options="postOptions"
          :dept-options="deptOptions"
        />
        <EducationWorkSection :form="form" />
        <EmergencyAndAttachmentSection :form="form" />
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary"
                     @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
    </div>
  </FormDialog>
</template>
<script setup>
  import { ref, onMounted } from "vue";
  import { findPostOptions } from "@/api/system/post.js";
  import { listDept } from "@/api/system/dept.js";
  import {
    staffOnJobInfo,
    createStaffOnJob,
    updateStaffOnJob,
  } from "@/api/personnelManagement/staffOnJob.js";
  import { deptTreeSelect } from "@/api/system/user.js";
  const { proxy } = getCurrentInstance();
  const emit = defineEmits(["close"]);
import {
  ref,
  reactive,
  toRefs,
  onMounted,
  getCurrentInstance,
  nextTick,
} from "vue";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import { findPostOptions } from "@/api/system/post.js";
import { deptTreeSelect } from "@/api/system/user.js";
import {
  staffOnJobInfo,
  createStaffOnJob,
  updateStaffOnJob,
} from "@/api/personnelManagement/staffOnJob.js";
  const dialogFormVisible = ref(false);
  const operationType = ref("");
  const id = ref(0);
  const data = reactive({
    form: {
      staffNo: "",
      staffName: "",
      sex: "",
      nativePlace: "",
      postJob: "",
      adress: "",
      firstStudy: "",
      profession: "",
      age: 0,
      phone: "",
      emergencyContact: "",
      emergencyContactPhone: "",
      contractTerm: 0,
      contractStartTime: "",
      contractEndTime: "",
      sysPostId: undefined,
      sysDeptId: undefined,
    },
    rules: {
      staffNo: [{ required: true, message: "请输入", trigger: "blur" }],
      staffName: [{ required: true, message: "请输入", trigger: "blur" }],
      sex: [{ required: true, message: "请输入", trigger: "blur" }],
      nativePlace: [{ required: true, message: "请输入", trigger: "blur" }],
      postJob: [{ required: true, message: "请输入", trigger: "blur" }],
      adress: [{ required: true, message: "请输入", trigger: "blur" }],
      firstStudy: [{ required: true, message: "请输入", trigger: "blur" }],
      profession: [{ required: true, message: "请输入", trigger: "blur" }],
      age: [{ required: true, message: "请输入", trigger: "blur" }],
      phone: [{ required: true, message: "请输入", trigger: "blur" }],
      emergencyContact: [{ required: true, message: "请输入", trigger: "blur" }],
      emergencyContactPhone: [
        { required: true, message: "请输入", trigger: "blur" },
      ],
      contractTerm: [{ required: true, message: "请输入", trigger: "blur" }],
      contractStartTime: [{ required: true, message: "请输入", trigger: "blur" }],
      contractEndTime: [{ required: true, message: "请输入", trigger: "blur" }],
      sysDeptId: [{ required: true, message: "请选择", trigger: "change" }],
    },
    postOptions: [], // å²—位选项
    deptOptions: [], // éƒ¨é—¨é€‰é¡¹
import BasicInfoSection from "./BasicInfoSection.vue";
import JobInfoSection from "./JobInfoSection.vue";
import EducationWorkSection from "./EducationWorkSection.vue";
import EmergencyAndAttachmentSection from "./EmergencyAndAttachmentSection.vue";
const { proxy } = getCurrentInstance();
const emit = defineEmits(["close"]);
const dialogFormVisible = ref(false);
const operationType = ref("add");
const id = ref(0);
const formRef = ref(null);
const dialogTitle = () =>
  operationType.value === "add" ? "新增入职" : "编辑人员";
const createEmptyEducation = () => ({
  degree: "",
  school: "",
  admissionDate: "",
  graduationDate: "",
  major: "",
  academicDegree: "",
});
const createEmptyWork = () => ({
  company: "",
  department: "",
  position: "",
  startDate: "",
  endDate: "",
  description: "",
});
const createEmptyEmergency = () => ({
  name: "",
  relation: "",
  phone: "",
  address: "",
});
const createDefaultForm = () => ({
  id: undefined,
  // åŸºæœ¬ä¿¡æ¯
  staffNo: "",
  staffName: "",
  aliasName: "",
  phone: "",
  sex: "",
  birthDate: "",
  age: undefined,
  nativePlace: "",
  nation: "",
  maritalStatus: "",
  politicalStatus: "",
  firstWorkDate: "",
  workingYears: undefined,
  idCardNo: "",
  hukouType: "",
  email: "",
  currentAddress: "",
  // åœ¨èŒä¿¡æ¯
  jobNo: "",
  staffType: "",
  entryDate: "",
  probationPeriod: undefined,
  regularDate: "",
  sysDeptId: undefined,
  directLeader: "",
  sysPostId: undefined,
  jobLevel: "",
  basicSalary: undefined,
  // é“¶è¡Œå¡ä¿¡æ¯
  bankName: "",
  bankCardNo: "",
  // æ•™è‚²ç»åކ
  educationList: [createEmptyEducation()],
  // å·¥ä½œç»åކ
  workExperienceList: [createEmptyWork()],
  // ç´§æ€¥è”系人
  emergencyContacts: [createEmptyEmergency()],
  emergencyContact: "",
  emergencyContactPhone: "",
  // ææ–™é™„件(仅前端展示)
  attachments: [],
});
const state = reactive({
  form: createDefaultForm(),
  rules: {
    staffNo: [{ required: true, message: "请输入员工编号", trigger: "blur" }],
    staffName: [{ required: true, message: "请输入姓名", trigger: "blur" }],
    phone: [{ required: true, message: "请输入手机", trigger: "blur" }],
    sex: [{ required: true, message: "请选择性别", trigger: "change" }],
    birthDate: [
      { required: true, message: "请选择出生日期", trigger: "change" },
    ],
    jobNo: [{ required: true, message: "请输入工号", trigger: "blur" }],
    staffType: [
      { required: true, message: "请选择员工类型", trigger: "change" },
    ],
    entryDate: [
      { required: true, message: "请选择入职日期", trigger: "change" },
    ],
    sysDeptId: [
      { required: true, message: "请选择部门", trigger: "change" },
    ],
  },
  postOptions: [],
  deptOptions: [],
});
const { form, rules, postOptions, deptOptions } = toRefs(state);
const resetForm = () => {
  Object.assign(form.value, createDefaultForm());
  nextTick(() => {
    formRef.value?.clearValidate();
  });
  const { form, rules, postOptions, deptOptions } = toRefs(data);
};
  // æ‰“开弹框
  const openDialog = (type, row) => {
    operationType.value = type;
    dialogFormVisible.value = true;
    if (operationType.value === "edit") {
      id.value = row.id;
      staffOnJobInfo(id.value, {}).then(res => {
        form.value = { ...res.data };
        if (form.value.sysPostId === 0) {
          form.value.sysPostId = undefined;
        }
        if (form.value.sysDeptId === 0) {
          form.value.sysDeptId = undefined;
        }
        // ç¼–辑时也计算一次合同年限
        calculateContractTerm();
      });
    } else {
      form.value.id = "";
const fetchPostOptions = () => {
  findPostOptions().then((res) => {
    postOptions.value = res.data || [];
  });
};
const fetchDeptOptions = () => {
  deptTreeSelect().then((response) => {
    deptOptions.value = filterDisabledDept(
      JSON.parse(JSON.stringify(response.data || []))
    );
  });
};
function filterDisabledDept(deptList) {
  return deptList.filter((dept) => {
    if (dept.disabled) {
      return false;
    }
  };
  onMounted(() => {
    fetchPostOptions();
    fetchDeptOptions();
    if (dept.children && dept.children.length) {
      dept.children = filterDisabledDept(dept.children);
    }
    return true;
  });
}
  const fetchPostOptions = () => {
    findPostOptions().then(res => {
      postOptions.value = res.data;
    });
  };
const syncEmergencyToLegacyField = () => {
  const first = form.value.emergencyContacts?.[0];
  form.value.emergencyContact = first?.name || "";
  form.value.emergencyContactPhone = first?.phone || "";
};
  // æŸ¥è¯¢éƒ¨é—¨åˆ—表
  const fetchDeptOptions = () => {
    deptTreeSelect().then(response => {
      deptOptions.value = filterDisabledDept(
        JSON.parse(JSON.stringify(response.data))
      );
    });
  };
  /** è¿‡æ»¤ç¦ç”¨çš„部门 */
  function filterDisabledDept(deptList) {
    return deptList.filter(dept => {
      if (dept.disabled) {
        return false;
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
  fetchPostOptions();
  fetchDeptOptions();
  resetForm();
  if (type === "edit" && row?.id) {
    id.value = row.id;
    staffOnJobInfo(id.value, {}).then((res) => {
      const d = res.data || {};
      Object.assign(form.value, {
        ...form.value,
        ...d,
      });
      if (!Array.isArray(form.value.educationList) || !form.value.educationList.length) {
        form.value.educationList = [createEmptyEducation()];
      }
      if (dept.children && dept.children.length) {
        dept.children = filterDisabledDept(dept.children);
      if (
        !Array.isArray(form.value.workExperienceList) ||
        !form.value.workExperienceList.length
      ) {
        form.value.workExperienceList = [createEmptyWork()];
      }
      return true;
      if (
        !Array.isArray(form.value.emergencyContacts) ||
        !form.value.emergencyContacts.length
      ) {
        form.value.emergencyContacts = [createEmptyEmergency()];
      }
      if (form.value.sysPostId === 0) {
        form.value.sysPostId = undefined;
      }
      if (form.value.sysDeptId === 0) {
        form.value.sysDeptId = undefined;
      }
    });
  }
};
  // æäº¤äº§å“è¡¨å•
  const submitForm = () => {
    if (!form.value.sysPostId) {
      form.value.sysPostId = undefined;
    }
    if (!form.value.sysDeptId) {
      form.value.sysDeptId = undefined;
    }
    proxy.$refs.formRef.validate(valid => {
      if (valid) {
        if (operationType.value === "add") {
          createStaffOnJob(form.value).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeDia();
          });
        } else {
          updateStaffOnJob(id.value, form.value).then(res => {
            proxy.$modal.msgSuccess("提交成功");
            closeDia();
          });
        }
      }
    });
  };
  // è®¡ç®—合同年限
  const calculateContractTerm = () => {
    if (form.value.contractStartTime && form.value.contractEndTime) {
      const startDate = new Date(form.value.contractStartTime);
      const endDate = new Date(form.value.contractEndTime);
onMounted(() => {
  fetchPostOptions();
  fetchDeptOptions();
});
      if (endDate > startDate) {
        // è®¡ç®—年份差
        const yearDiff = endDate.getFullYear() - startDate.getFullYear();
        const monthDiff = endDate.getMonth() - startDate.getMonth();
        const dayDiff = endDate.getDate() - startDate.getDate();
        let years = yearDiff;
        // å¦‚果结束日期的月日小于开始日期的月日,则减去1å¹´
        if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
          years = yearDiff - 1;
        }
        form.value.contractTerm = Math.max(0, years);
const submitForm = () => {
  if (!form.value.sysPostId) {
    form.value.sysPostId = undefined;
  }
  if (!form.value.sysDeptId) {
    form.value.sysDeptId = undefined;
  }
  syncEmergencyToLegacyField();
  formRef.value?.validate((valid) => {
    if (valid) {
      if (operationType.value === "add") {
        createStaffOnJob(form.value).then(() => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        });
      } else {
        form.value.contractTerm = 0;
        updateStaffOnJob(id.value, form.value).then(() => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        });
      }
    } else {
      form.value.contractTerm = 0;
    }
  };
  // å…³é—­å¼¹æ¡†
  const closeDia = () => {
    proxy.resetForm("formRef");
    dialogFormVisible.value = false;
    emit("close");
  };
  defineExpose({
    openDialog,
  });
};
const closeDia = () => {
  formRef.value?.resetFields();
  dialogFormVisible.value = false;
  emit("close");
};
defineExpose({
  openDialog,
});
</script>
<style scoped>
.form-dia-body {
  padding: 0;
}
.card-title-line {
  color: #f56c6c;
  margin-right: 4px;
}
.form-card {
  margin-bottom: 16px;
}
.dialog-footer {
  text-align: right;
}
</style>
src/views/personnelManagement/monthlyStatistics/components/formDia.vue
@@ -1,318 +1,565 @@
<template>
  <el-dialog v-model="dialogVisible"
             :title="title"
             width="700px"
             :close-on-click-modal="false">
    <el-form ref="formRef"
             :model="form"
             :rules="rules"
             label-width="140px"
             label-position="top">
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="统计月份"
                        prop="payDate">
            <el-date-picker v-model="form.payDate"
                            type="month"
                            value-format="YYYY-MM"
                            format="YYYY-MM"
                            placeholder="请选择月份"
                            style="width: 100%"
                            :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="姓名"
                        prop="staffId">
            <el-select v-model="form.staffId"
                       placeholder="请选择员工"
                       style="width: 100%"
                       :disabled="operationType === 'view'">
              <el-option v-for="item in userList"
                         :key="item.id"
                         :label="item.staffName"
                         :value="item.id" />
            </el-select>
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="基本工资"
                        prop="basicSalary">
            <el-input v-model="form.basicSalary"
                      type="number"
                      placeholder="请输入基本工资"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="计件工资"
                        prop="pieceworkSalary">
            <el-input v-model="form.pieceworkSalary"
                      type="number"
                      placeholder="请输入计件工资"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="计时工资"
                        prop="hourlySalary">
            <el-input v-model="form.hourlySalary"
                      type="number"
                      placeholder="请输入计时工资"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="其他收入"
                        prop="otherIncome">
            <el-input v-model="form.otherIncome"
                      type="number"
                      placeholder="请输入其他收入"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="社保个人"
                        prop="socialSecurityIndividuals">
            <el-input v-model="form.socialSecurityIndividuals"
                      type="number"
                      placeholder="请输入社保个人"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="公积金个人"
                        prop="providentFundIndividuals">
            <el-input v-model="form.providentFundIndividuals"
                      type="number"
                      placeholder="请输入公积金个人"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="12">
          <el-form-item label="个人所得税"
                        prop="personalIncomeTax">
            <el-input v-model="form.personalIncomeTax"
                      type="number"
                      placeholder="请输入个人所得税"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="其他扣款"
                        prop="otherDeductions">
            <el-input v-model="form.otherDeductions"
                      type="number"
                      placeholder="请输入其他扣款"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row :gutter="20">
        <el-col :span="24">
          <el-form-item label="备注"
                        prop="remark">
            <el-input v-model="form.remark"
                      type="textarea"
                      placeholder="请输入备注"
                      :rows="3"
                      :disabled="operationType === 'view'" />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
  <el-dialog
    v-model="dialogVisible"
    :title="operationType === 'add' ? '新建工资表' : '编辑工资表'"
    width="90%"
    :close-on-click-modal="false"
    destroy-on-close
    @close="closeDia"
  >
    <div class="form-dia-body">
      <!-- åŸºç¡€èµ„æ–™ -->
      <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 ref="formRef" :model="form" :rules="rules" label-position="top">
          <el-row :gutter="24">
            <el-col :span="6">
              <el-form-item label="工资主题" prop="title">
                <el-input
                  v-model="form.title"
                  placeholder="请输入"
                  clearable
                  maxlength="20"
                  show-word-limit
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="选择部门" prop="deptId">
                <el-select
                  v-model="form.deptId"
                  placeholder="请选择"
                  clearable
                  style="width: 100%"
                >
                  <el-option
                    v-for="item in deptOptions"
                    :key="item.deptId"
                    :label="item.deptName"
                    :value="item.deptId"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="选择工资月份" prop="payMonth">
                <el-date-picker
                  v-model="form.payMonth"
                  type="month"
                  value-format="YYYY-MM"
                  format="YYYY-MM"
                  placeholder="请选择工资月份"
                  style="width: 100%"
                  clearable
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="备注" prop="remark">
                <el-input
                  v-model="form.remark"
                  placeholder="请输入"
                  clearable
                />
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
      </el-card>
      <!-- æ“ä½œæŒ‰é’® -->
      <div class="toolbar">
        <el-button type="primary" @click="handleGenerate">生成工资表</el-button>
        <el-button @click="handleExport">导出</el-button>
        <el-button @click="handleImport">导入</el-button>
        <el-button @click="handleClear">清空</el-button>
        <el-button @click="openAddPerson">新增人员</el-button>
        <el-button @click="handleBatchDelete">删除</el-button>
        <el-button @click="handleTaxForm">个税表</el-button>
      </div>
      <!-- å‘˜å·¥å·¥èµ„详情表格 -->
      <div class="employee-table-wrap">
        <el-table
          ref="employeeTableRef"
          :data="employeeList"
          border
          max-height="400"
          @selection-change="onEmployeeSelectionChange"
        >
          <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 }">
              <el-input
                v-model.number="row.basicSalary"
                type="number"
                placeholder="0"
                size="small"
                @input="row.basicSalary = parseNum(row.basicSalary)"
              />
            </template>
          </el-table-column>
          <el-table-column label="计件工资" minWidth="110">
            <template #default="{ row }">
              <el-input
                v-model.number="row.pieceworkSalary"
                type="number"
                placeholder="0"
                size="small"
                @input="row.pieceworkSalary = parseNum(row.pieceworkSalary)"
              />
            </template>
          </el-table-column>
          <el-table-column label="计时工资" minWidth="110">
            <template #default="{ row }">
              <el-input
                v-model.number="row.hourlySalary"
                type="number"
                placeholder="0"
                size="small"
                @input="row.hourlySalary = parseNum(row.hourlySalary)"
              />
            </template>
          </el-table-column>
          <el-table-column label="其他收入" minWidth="110">
            <template #default="{ row }">
              <el-input
                v-model.number="row.otherIncome"
                type="number"
                placeholder="0"
                size="small"
                @input="row.otherIncome = parseNum(row.otherIncome)"
              />
            </template>
          </el-table-column>
          <el-table-column label="社保个人" minWidth="110">
            <template #default="{ row }">
              <el-input
                v-model.number="row.socialSecurityIndividuals"
                type="number"
                placeholder="0"
                size="small"
                @input="row.socialSecurityIndividuals = parseNum(row.socialSecurityIndividuals)"
              />
            </template>
          </el-table-column>
          <el-table-column label="公积金个人" minWidth="120">
            <template #default="{ row }">
              <el-input
                v-model.number="row.providentFundIndividuals"
                type="number"
                placeholder="0"
                size="small"
                @input="row.providentFundIndividuals = parseNum(row.providentFundIndividuals)"
              />
            </template>
          </el-table-column>
          <el-table-column label="操作" width="80" align="center" fixed="right">
            <template #default="{ row }">
              <el-button type="primary" link @click="removeEmployee(row)">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <div v-if="!employeeList.length" class="table-empty">暂无数据</div>
      </div>
    </div>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary"
                   @click="submitForm"
                   v-if="operationType !== 'view'">
          ç¡®å®š
        </el-button>
      </span>
      <div class="dialog-footer">
        <el-button @click="closeDia">取消</el-button>
        <el-button type="primary" @click="submitForm">确定</el-button>
      </div>
    </template>
    <!-- æ–°å¢žäººå‘˜å¼¹çª— -->
    <el-dialog
      v-model="addPersonVisible"
      title="新增人员"
      width="400px"
      append-to-body
      @close="addPersonClose"
    >
      <div class="add-person-tree">
        <el-tree
          ref="personTreeRef"
          :data="deptStaffTree"
          show-checkbox
          node-key="id"
          :props="{ label: 'label', children: 'children' }"
          default-expand-all
        />
      </div>
      <template #footer>
        <el-button @click="addPersonVisible = false">取消</el-button>
        <el-button type="primary" @click="confirmAddPerson">确定</el-button>
      </template>
    </el-dialog>
    <!-- ä¸ªç¨Žè¡¨å¼¹çª— -->
    <el-dialog
      v-model="taxDialogVisible"
      title="个税表"
      width="700px"
      append-to-body
    >
      <div class="tax-desc">个人所得税免征额:5000元</div>
      <el-table :data="taxTableData" border style="width: 100%;margin-bottom: 20px;">
        <el-table-column prop="level" label="级数" width="80" align="center" />
        <el-table-column
          prop="range"
          label="全年应纳税所得额/元"
          min-width="220"
        />
        <el-table-column
          prop="rate"
          label="税率(%)"
          width="100"
          align="center"
        />
        <el-table-column
          prop="quickDeduction"
          label="速算扣除数/元"
          width="160"
          align="center"
        />
      </el-table>
    </el-dialog>
  </el-dialog>
</template>
<script setup>
  import { ref, reactive, computed, onMounted } from "vue";
  import { ElMessage } from "element-plus";
  import {
    monthlyStatisticsAdd,
    monthlyStatisticsUpdate,
    staffOnJobList,
  } from "@/api/personnelManagement/monthlyStatistics.js";
import { ref, reactive, toRefs, computed, getCurrentInstance, nextTick } from "vue";
import { ArrowUp } from "@element-plus/icons-vue";
import { listDept } from "@/api/system/dept.js";
import { staffOnJobList } from "@/api/personnelManagement/monthlyStatistics.js";
import {
  monthlyStatisticsAdd,
  monthlyStatisticsUpdate,
  monthlyStatisticsGet,
} from "@/api/personnelManagement/monthlyStatistics.js";
  const props = defineProps({
    modelValue: {
      type: Boolean,
      default: false,
    },
    operationType: {
      type: String,
      default: "add",
    },
    row: {
      type: Object,
      default: () => ({}),
    },
  });
const emit = defineEmits(["update:modelValue", "close"]);
const props = defineProps({
  modelValue: { type: Boolean, default: false },
  operationType: { type: String, default: "add" },
  row: { type: Object, default: () => ({}) },
});
  const emit = defineEmits(["update:modelValue", "close"]);
const { proxy } = getCurrentInstance();
  const dialogVisible = computed({
    get: () => props.modelValue,
    set: val => emit("update:modelValue", val),
  });
const dialogVisible = computed({
  get: () => props.modelValue,
  set: (val) => emit("update:modelValue", val),
});
  const title = computed(() => {
    if (props.operationType === "add") return "新增薪资台账";
    if (props.operationType === "edit") return "编辑薪资台账";
    return "查看薪资台账";
  });
const formRef = ref(null);
const employeeTableRef = ref(null);
const personTreeRef = ref(null);
const addPersonVisible = ref(false);
const taxDialogVisible = ref(false);
const deptOptions = ref([]);
const deptStaffTree = ref([]);
const employeeList = ref([]);
const selectedEmployees = ref([]);
const taxTableData = ref([
  { level: 1, range: "不超过36000元", rate: 3, quickDeduction: 0 },
  { level: 2, range: "超过36000-144000元", rate: 10, quickDeduction: 2520 },
  { level: 3, range: "超过144000-300000元", rate: 20, quickDeduction: 16920 },
  { level: 4, range: "超过300000-420000元", rate: 25, quickDeduction: 31920 },
  { level: 5, range: "超过420000-660000元", rate: 30, quickDeduction: 52920 },
  { level: 6, range: "超过660000-960000元", rate: 35, quickDeduction: 85920 },
  { level: 7, range: "超过960000元", rate: 45, quickDeduction: 181920 },
]);
  const formRef = ref();
  const form = reactive({
    id: "",
    payDate: "",
    staffId: "",
    basicSalary: 0,
    pieceworkSalary: 0,
    hourlySalary: 0,
    otherIncome: 0,
    socialSecurityIndividuals: 0,
    providentFundIndividuals: 0,
    personalIncomeTax: 0,
    otherDeductions: 0,
    payableWages: 0,
    deductibleWages: 0,
    actualWages: 0,
function parseNum(v) {
  if (v === "" || v == null) return 0;
  const n = Number(v);
  return isNaN(n) ? 0 : n;
}
// åŸºç¡€èµ„料表单
const data = reactive({
  form: {
    id: undefined,
    title: "",
    deptId: undefined,
    payMonth: "",
    remark: "",
  },
  rules: {
    title: [{ required: true, message: "请输入工资主题", trigger: "blur" }],
    deptId: [{ required: true, message: "请选择部门", trigger: "change" }],
    payMonth: [{ required: true, message: "请选择工资月份", trigger: "change" }],
  },
});
const { form, rules } = toRefs(data);
// æ‰å¹³åŒ–部门树供下拉使用
function flattenDept(tree, list = []) {
  if (!tree?.length) return list;
  tree.forEach((node) => {
    list.push({ deptId: node.deptId, deptName: node.deptName });
    if (node.children?.length) flattenDept(node.children, list);
  });
  return list;
}
  const rules = {
    payDate: [{ required: true, message: "请选择统计月份", trigger: "change" }],
    staffId: [{ required: true, message: "请选择员工", trigger: "change" }],
    basicSalary: [{ required: true, message: "请输入基本工资", trigger: "blur" }],
  };
const loadDeptOptions = () => {
  listDept().then((res) => {
    const tree = res.data ?? [];
    deptOptions.value = flattenDept(tree);
  });
};
  const userList = ref([]);
  const loadUserList = () => {
    // userListNoPage().then(res => {
    //   userList.value = res.data || [];
    // });
    staffOnJobList().then(res => {
      userList.value = res.data || [];
// æž„建 éƒ¨é—¨-人员 æ ‘(用于新增人员弹窗)
const loadDeptStaffTree = () => {
  Promise.all([listDept(), staffOnJobList()]).then(([deptRes, staffRes]) => {
    const tree = deptRes.data ?? [];
    const staffList = staffRes.data ?? [];
    const deptMap = new Map();
    function walk(nodes) {
      nodes.forEach((node) => {
        deptMap.set(node.deptId, {
          id: "dept_" + node.deptId,
          deptId: node.deptId,
          label: node.deptName,
          type: "dept",
          children: [],
        });
        if (node.children?.length) walk(node.children);
      });
    }
    walk(tree);
    staffList.forEach((s) => {
      const deptId = s.deptId ?? s.dept_id;
      const node = deptMap.get(deptId);
      if (node) {
        node.children.push({
          id: s.id ?? s.staffId,
          staffId: s.id ?? s.staffId,
          label: s.staffName ?? s.name,
          type: "staff",
          ...s,
        });
      }
    });
  };
    deptStaffTree.value = Array.from(deptMap.values()).filter(
      (n) => n.children && n.children.length > 0
    );
  });
};
  const openDialog = (type, row) => {
    // é‡ç½®è¡¨å•
    Object.assign(form, {
      id: "",
      payDate: "",
      staffId: "",
const openDialog = (type, row) => {
  nextTick(() => {
    loadDeptOptions();
    employeeList.value = [];
    Object.assign(form.value, {
      id: undefined,
      title: "",
      deptId: undefined,
      payMonth: "",
      remark: "",
    });
    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),
        }));
      });
    }
  });
};
const openAddPerson = () => {
  loadDeptStaffTree();
  addPersonVisible.value = true;
  nextTick(() => {
    personTreeRef.value?.setCheckedKeys([]);
  });
};
const addPersonClose = () => {};
const confirmAddPerson = () => {
  const tree = personTreeRef.value;
  if (!tree) {
    addPersonVisible.value = false;
    return;
  }
  const checked = tree.getCheckedNodes();
  const staffNodes = checked.filter((n) => n.type === "staff");
  const existIds = new Set(employeeList.value.map((e) => e.staffId || e.id));
  staffNodes.forEach((node) => {
    const id = node.staffId ?? node.id;
    if (existIds.has(id)) return;
    existIds.add(id);
    employeeList.value.push({
      staffId: id,
      id: id,
      staffName: node.label,
      roleName: node.roleName ?? node.role ?? "",
      deptName: node.deptName ?? "",
      basicSalary: 0,
      pieceworkSalary: 0,
      hourlySalary: 0,
      otherIncome: 0,
      socialSecurityIndividuals: 0,
      providentFundIndividuals: 0,
      personalIncomeTax: 0,
      otherDeductions: 0,
      payableWages: 0,
      deductibleWages: 0,
      actualWages: 0,
      remark: "",
    });
  });
  addPersonVisible.value = false;
};
    if (type === "add") {
      dialogVisible.value = true;
    } else if (type === "edit" || type === "view") {
      if (row && row.id) {
        Object.assign(form, row);
        dialogVisible.value = true;
      }
const removeEmployee = (row) => {
  employeeList.value = employeeList.value.filter(
    (e) => (e.staffId || e.id) !== (row.staffId || row.id)
  );
};
const onEmployeeSelectionChange = (selection) => {
  selectedEmployees.value = selection;
};
const handleBatchDelete = () => {
  if (!selectedEmployees.value?.length) {
    proxy.$modal.msgWarning("请先勾选要删除的员工");
    return;
  }
  const ids = new Set(selectedEmployees.value.map((e) => e.staffId || e.id));
  employeeList.value = employeeList.value.filter(
    (e) => !ids.has(e.staffId || e.id)
  );
};
const handleGenerate = () => {
  proxy.$modal.msgInfo("生成工资表功能需对接后端");
};
const handleExport = () => {
  proxy.$modal.msgInfo("导出功能需对接后端");
};
const handleImport = () => {
  proxy.$modal.msgInfo("导入功能需对接后端");
};
const handleClear = () => {
  proxy.$modal.confirm("确定清空当前员工列表吗?").then(() => {
    employeeList.value = [];
  }).catch(() => {});
};
const handleTaxForm = () => {
  taxDialogVisible.value = true;
};
const submitForm = () => {
  formRef.value?.validate((valid) => {
    if (!valid) return;
    const payload = {
      ...form.value,
      detailList: employeeList.value.map((e) => ({
        staffId: e.staffId ?? e.id,
        staffName: e.staffName,
        basicSalary: parseNum(e.basicSalary),
        pieceworkSalary: parseNum(e.pieceworkSalary),
        hourlySalary: parseNum(e.hourlySalary),
        otherIncome: parseNum(e.otherIncome),
        socialSecurityIndividuals: parseNum(e.socialSecurityIndividuals),
        providentFundIndividuals: parseNum(e.providentFundIndividuals),
      })),
    };
    if (props.operationType === "add") {
      monthlyStatisticsAdd(payload).then(() => {
        proxy.$modal.msgSuccess("新增成功");
        closeDia();
      });
    } else {
      monthlyStatisticsUpdate(payload).then(() => {
        proxy.$modal.msgSuccess("修改成功");
        closeDia();
      });
    }
  };
  const submitForm = () => {
    formRef.value.validate(valid => {
      if (valid) {
        form.basicSalary = Number(form.basicSalary);
        form.pieceworkSalary = Number(form.pieceworkSalary);
        form.hourlySalary = Number(form.hourlySalary);
        form.otherIncome = Number(form.otherIncome);
        form.socialSecurityIndividuals = Number(form.socialSecurityIndividuals);
        form.providentFundIndividuals = Number(form.providentFundIndividuals);
        form.personalIncomeTax = Number(form.personalIncomeTax);
        form.otherDeductions = Number(form.otherDeductions);
        // è®¡ç®—应发工资、应扣工资和实发工资
        const payableWages =
          form.basicSalary +
          form.pieceworkSalary +
          form.hourlySalary +
          form.otherIncome;
        const deductibleWages =
          form.socialSecurityIndividuals +
          form.providentFundIndividuals +
          form.personalIncomeTax +
          form.otherDeductions;
        const actualWages = payableWages - deductibleWages;
        const submitData = {
          ...form,
          payableWages,
          deductibleWages,
          actualWages,
        };
        if (props.operationType === "add") {
          monthlyStatisticsAdd(submitData).then(res => {
            if (res.code === 200) {
              ElMessage.success("新增成功");
              dialogVisible.value = false;
              emit("close");
            } else {
              ElMessage.error(res.msg || "新增失败");
            }
          });
        } else if (props.operationType === "edit") {
          monthlyStatisticsUpdate(submitData).then(res => {
            if (res.code === 200) {
              ElMessage.success("更新成功");
              dialogVisible.value = false;
              emit("close");
            } else {
              ElMessage.error(res.msg || "更新失败");
            }
          });
        }
      }
    });
  };
  onMounted(() => {
    loadUserList();
  });
};
  defineExpose({
    openDialog,
  });
const closeDia = () => {
  dialogVisible.value = false;
  emit("close");
};
defineExpose({ openDialog });
</script>
<style scoped>
  .dialog-footer {
    text-align: right;
  }
</style>
.form-dia-body {
  padding: 0;
}
.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;
}
.toolbar {
  margin-bottom: 16px;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.employee-table-wrap {
  position: relative;
  min-height: 120px;
}
.table-empty {
  text-align: center;
  padding: 24px;
  color: #999;
  font-size: 14px;
}
.add-person-tree {
  max-height: 360px;
  overflow-y: auto;
  padding: 8px 0;
}
.tax-desc {
  margin-bottom: 12px;
  font-size: 14px;
  color: #606266;
}
.dialog-footer {
  text-align: right;
}
</style>
src/views/personnelManagement/monthlyStatistics/index.vue
@@ -2,302 +2,255 @@
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">姓名:</span>
        <el-input v-model="searchForm.staffName"
                  style="width: 240px"
                  placeholder="请输入姓名搜索"
                  @change="handleQuery"
                  clearable
                  :prefix-icon="Search" />
        <span class="search_title ml10">月份:</span>
        <el-date-picker v-model="searchForm.payDateStr"
                        type="month"
                        @change="handleQuery"
                        value-format="YYYY-MM"
                        format="YYYY-MM"
                        placeholder="请选择月份"
                        style="width: 240px"
                        clearable />
        <el-button type="primary"
                   @click="handleQuery"
                   style="margin-left: 10px">
        <span class="search_title">主题:</span>
        <el-input
          v-model="searchForm.title"
          style="width: 240px"
          placeholder="请输入主题"
          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" />
        </el-select>
        <span class="search_title ml10">工资月份:</span>
        <el-date-picker
          v-model="searchForm.payMonth"
          type="month"
          value-format="YYYY-MM"
          format="YYYY-MM"
          placeholder="请选择工资月份"
          style="width: 180px"
          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>
      </div>
      <div>
        <el-button @click="handleExport"
                   style="margin-right: 10px">导出</el-button>
        <el-button type="primary"
                   @click="openForm('add')">新增台账</el-button>
        <el-button type="danger"
                   plain
                   @click="handleDelete">删除</el-button>
        <el-button @click="handleReset">重置</el-button>
      </div>
    </div>
    <div class="table_list">
      <PIMTable rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"></PIMTable>
      <div style="margin-bottom: 10px">
        <el-button type="primary" @click="openForm('add')">新建工资表</el-button>
        <el-button @click="handleDelete">删除</el-button>
        <el-button @click="handleExport">导出</el-button>
      </div>
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="true"
        :tableLoading="tableLoading"
        @selection-change="handleSelectionChange"
        @pagination="pagination"
        :total="page.total"
      />
    </div>
    <form-dia v-model="dialogVisible"
              :operation-type="operationType"
              :row="currentRow"
              ref="formDia"
              @close="handleQuery"></form-dia>
    <form-dia
      v-model="dialogVisible"
      :operation-type="operationType"
      :row="currentRow"
      ref="formDiaRef"
      @close="handleQuery"
    />
  </div>
</template>
<script setup>
  import { Search } from "@element-plus/icons-vue";
  import {
    onMounted,
    ref,
    reactive,
    toRefs,
    getCurrentInstance,
    nextTick,
  } from "vue";
  import { ElMessageBox } from "element-plus";
  import dayjs from "dayjs";
  import FormDia from "./components/formDia.vue";
  import {
    monthlyStatisticsListPage,
    monthlyStatisticsDelete,
  } from "@/api/personnelManagement/monthlyStatistics.js";
import {
  onMounted,
  ref,
  reactive,
  toRefs,
  getCurrentInstance,
  nextTick,
} from "vue";
import { ElMessageBox } from "element-plus";
import FormDia from "./components/formDia.vue";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import {
  monthlyStatisticsListPage,
  monthlyStatisticsDelete,
} from "@/api/personnelManagement/monthlyStatistics.js";
  const data = reactive({
    searchForm: {
      staffName: "",
      payDateStr: "",
    },
  });
const data = reactive({
  searchForm: {
    title: "",
    documentStatus: "",
    payMonth: "",
    approvalStatus: "",
  },
});
const { searchForm } = toRefs(data);
  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: "remark", minWidth: 120 },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 120,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => openForm("edit", row),
      },
    ],
  },
]);
  const tableColumn = ref([
    {
      label: "员工姓名",
      prop: "staffName",
    },
    {
      label: "部门",
      prop: "deptName",
      width: 140,
    },
    {
      label: "月份",
      prop: "payDate",
    },
    {
      label: "基本工资",
      prop: "basicSalary",
    },
    {
      label: "计件工资",
      prop: "pieceworkSalary",
    },
    {
      label: "计时工资",
      prop: "hourlySalary",
    },
    {
      label: "其他收入",
      prop: "otherIncome",
    },
    {
      label: "社保个人",
      prop: "socialSecurityIndividuals",
    },
    {
      label: "公积金个人",
      prop: "providentFundIndividuals",
      width: 140,
    },
    {
      label: "工资个税",
      prop: "personalIncomeTax",
    },
    {
      label: "其他支出",
      prop: "otherDeductions",
    },
    {
      label: "应发工资",
      prop: "payableWages",
    },
    {
      label: "应扣工资",
      prop: "deductibleWages",
    },
    {
      label: "实发工资",
      prop: "actualWages",
    },
    {
      label: "备注",
      prop: "remark",
      width: 150,
    },
    {
      dataType: "action",
      label: "操作",
      align: "center",
      fixed: "right",
      width: 220,
      operation: [
        {
          name: "编辑",
          type: "text",
          clickFun: row => {
            openForm("edit", row);
          },
        },
        // {
        //   name: "查看",
        //   type: "text",
        //   clickFun: row => {
        //     openForm("view", row);
        //   },
        // },
      ],
    },
  ]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 10,
  total: 0,
});
const formDiaRef = ref(null);
const dialogVisible = ref(false);
const operationType = ref("add");
const currentRow = ref({});
const { proxy } = getCurrentInstance();
  const tableData = ref([]);
  const selectedRows = ref([]);
  const tableLoading = ref(false);
  const page = reactive({
    current: 1,
    size: 100,
    total: 0,
  });
const handleQuery = () => {
  page.current = 1;
  getList();
};
  const formDia = ref();
  const dialogVisible = ref(false);
  const operationType = ref("add");
  const currentRow = ref({});
  const { proxy } = getCurrentInstance();
const handleReset = () => {
  searchForm.value.title = "";
  searchForm.value.documentStatus = "";
  searchForm.value.payMonth = "";
  searchForm.value.approvalStatus = "";
  page.current = 1;
  getList();
};
  // æŸ¥è¯¢åˆ—表
  /** æœç´¢æŒ‰é’®æ“ä½œ */
  const handleQuery = () => {
    page.current = 1;
    getList();
  };
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
  const pagination = obj => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
  };
  const getList = () => {
    tableLoading.value = true;
    monthlyStatisticsListPage({ ...page, ...searchForm.value })
      .then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
      })
      .catch(err => {
        tableLoading.value = false;
      });
  };
  // è¡¨æ ¼é€‰æ‹©æ•°æ®
  const handleSelectionChange = selection => {
    selectedRows.value = selection;
  };
  // æ‰“开弹框
  const openForm = (type, row) => {
    operationType.value = type;
    currentRow.value = row || {};
    dialogVisible.value = true;
    nextTick(() => {
      formDia.value?.openDialog(type, row);
const getList = () => {
  tableLoading.value = true;
  monthlyStatisticsListPage({
    ...searchForm.value,
    current: page.current,
    size: page.size,
  })
    .then((res) => {
      tableLoading.value = false;
      const records = res.data?.records ?? [];
      // å…¼å®¹åŽç«¯å­—段:若接口仍返回台账结构,可在此做映射
      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 ?? "-",
      }));
      page.total = res.data?.total ?? 0;
    })
    .catch(() => {
      tableLoading.value = false;
    });
  };
};
  // åˆ é™¤
  const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
      ids = selectedRows.value.map(item => item.id);
    } else {
      proxy.$modal.msgWarning("请选择数据");
      return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        monthlyStatisticsDelete(ids).then(res => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
  // å¯¼å‡º
  const handleExport = () => {
    ElMessageBox.confirm("是否确认导出人员薪资台账?", "导出", {
      confirmButtonText: "确认",
      cancelButtonText: "取消",
      type: "warning",
    })
      .then(() => {
        proxy.download(
          "/compensationPerformance/export",
          { ...searchForm.value, ...page },
          "人员薪资台账.xlsx"
        );
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
  };
  onMounted(() => {
    getList();
const openForm = (type, row) => {
  operationType.value = type;
  currentRow.value = row || {};
  dialogVisible.value = true;
  nextTick(() => {
    formDiaRef.value?.openDialog(type, row);
  });
};
const handleDelete = () => {
  if (!selectedRows.value?.length) {
    proxy.$modal.msgWarning("请选择要删除的数据");
    return;
  }
  const ids = selectedRows.value.map((item) => item.id);
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      monthlyStatisticsDelete(ids).then(() => {
        proxy.$modal.msgSuccess("删除成功");
        getList();
      });
    })
    .catch(() => {});
};
const handleExport = () => {
  proxy.download(
    "/compensationPerformance/export",
    { ...searchForm.value, current: page.current, size: page.size },
    "工资表.xlsx"
  );
};
onMounted(() => {
  getList();
});
</script>
<style scoped>
  .search_form {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    flex-wrap: wrap;
    gap: 10px;
  }
  .search_title {
    font-weight: 500;
    margin-right: 5px;
  }
  .ml10 {
    margin-left: 10px;
  }
  .table_list {
    margin-top: 20px;
  }
  .dialog-footer {
    text-align: right;
  }
</style>
.search_form {
  margin-bottom: 20px;
}
.search_title {
  font-weight: 500;
  margin-right: 5px;
}
.ml10 {
  margin-left: 10px;
}
.table_list {
  margin-top: 20px;
}
</style>
src/views/personnelManagement/socialSecuritySet/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,369 @@
<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">
                  <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 />
              </el-form-item>
              <el-form-item label="备注:" prop="remark">
                <el-input
                  v-model="form.remark"
                  type="textarea"
                  :rows="2"
                  placeholder="请输入"
                  clearable
                />
              </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 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="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%"
                      >
                        <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="checkbox-group-inline">
                        <el-checkbox v-model="item.baseOnSalary">根据基本工资缴纳</el-checkbox>
                        <el-checkbox v-model="item.useBasicSalary">调用基本工资</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"
                        />
                        <span class="ratio-unit">(%)</span>
                        <span class="ratio-plus">+</span>
                        <el-input
                          v-model="item.personalFixed"
                          placeholder="请输入"
                          clearable
                          style="width: 100px"
                          type="number"
                        />
                      </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 } 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";
const emit = defineEmits(["close"]);
const { proxy } = getCurrentInstance();
const dialogFormVisible = ref(false);
const operationType = ref("add");
const formRef = ref(null);
const deptList = ref([]);
const dialogTitle = () =>
  operationType.value === "add" ? "新增方案" : "编辑方案";
// ä¿é™©ç±»åž‹é€‰é¡¹ï¼ˆå¯æŒ‰å­—典替换)
const insuranceTypeOptions = [
  { label: "养老保险", value: "pension" },
  { label: "医疗保险", value: "medical" },
  { label: "失业保险", value: "unemployment" },
  { label: "工伤保险", value: "work_injury" },
  { label: "生育保险", value: "maternity" },
];
const defaultBenefit = () => ({
  _key: Math.random().toString(36).slice(2),
  insuranceType: "",
  baseOnSalary: false,
  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 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" && 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()];
    });
  }
};
const submitForm = () => {
  formRef.value?.validate((valid) => {
    if (!valid) return;
    const submitData = {
      ...form.value,
      insuranceBenefits: form.value.insuranceBenefits.map(
        ({ _key, ...rest }) => rest
      ),
    };
    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;
}
.personal-ratio-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ratio-unit,
.ratio-plus {
  color: #606266;
  font-size: 14px;
}
</style>
src/views/personnelManagement/socialSecuritySet/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title">主题:</span>
        <el-input
          v-model="searchForm.title"
          style="width: 240px"
          placeholder="请输入主题"
          clearable
          @keyup.enter="handleQuery"
        />
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">
          æœç´¢
        </el-button>
        <el-button @click="handleReset">重置</el-button>
      </div>
    </div>
    <div class="table_list">
      <div style="margin-bottom: 10px">
        <el-button type="primary" @click="openForm('add')">新增方案</el-button>
      </div>
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :tableLoading="tableLoading"
        @pagination="pagination"
        :total="page.total"
      />
    </div>
    <form-dia ref="formDiaRef" @close="handleQuery" />
  </div>
</template>
<script setup>
import { onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import FormDia from "./components/formDia.vue";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
import { socialSecurityListPage } from "@/api/personnelManagement/socialSecuritySet.js";
const data = reactive({
  searchForm: {
    title: "",
  },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
  { label: "主题", prop: "title", minWidth: 120 },
  { label: "保险类型", prop: "insuranceTypeName", width: 120 },
  { label: "使用范围", prop: "scopeName", width: 120 },
  { label: "备注", prop: "remark", minWidth: 120 },
  { label: "创建时间", prop: "createTime", width: 160 },
  { label: "创建人", prop: "createBy", width: 100 },
  {
    dataType: "action",
    label: "操作",
    align: "center",
    fixed: "right",
    width: 120,
    operation: [
      {
        name: "编辑",
        type: "text",
        clickFun: (row) => openForm("edit", row),
      },
    ],
  },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 10,
  total: 0,
});
const formDiaRef = ref(null);
const handleQuery = () => {
  page.current = 1;
  getList();
};
const handleReset = () => {
  searchForm.value.title = "";
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  socialSecurityListPage({
    ...searchForm.value,
    current: page.current,
    size: page.size,
  })
    .then((res) => {
      tableLoading.value = false;
      tableData.value = res.data?.records ?? [];
      page.total = res.data?.total ?? 0;
    })
    .catch(() => {
      tableLoading.value = false;
    });
};
const openForm = (type, row) => {
  nextTick(() => {
    formDiaRef.value?.openDialog(type, row);
  });
};
onMounted(() => {
  getList();
});
</script>
<style scoped></style>