1.排班管理-班次、加一个午休时间(h)、人员多选、人员从用户管理获取,加一条列表也只展示一条
2.薪资管理-只要,人员、薪资、月份三个字段
已修改6个文件
737 ■■■■ 文件已修改
src/api/personnelManagement/payrollManagement.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/payrollManagement/components/formDia.vue 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/payrollManagement/index.vue 219 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/scheduling/index.vue 279 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/payrollManagement.js
@@ -33,3 +33,19 @@
    data: query,
  });
}
// 导入
export function importData(query) {
  return request({
    url: "/compensationPerformance/importData",
    method: "post",
    data: query,
  });
}
// 下载模版
export function exportTemplate(query) {
  return request({
    url: "/compensationPerformance/exportTemplate",
    method: "post",
    data: query,
  });
}
src/api/system/user.js
@@ -8,6 +8,13 @@
    method: 'get',
    params: query
  })
}// 查询用户列表
export function listAll(query) {
  return request({
    url: '/system/user//listAll',
    method: 'get',
    params: query
  })
}
// 查询用户详细
src/views/index.vue
@@ -16,6 +16,10 @@
                            <el-icon color="#5053B5" size="22"><Clock /></el-icon>
                            <span>登陆日期:{{userStore.currentLoginTime}}</span>
                        </div>
                    <div style="display: flex;align-items: center;gap: 8px">
                        <el-icon color="#5053B5" size="22"><Calendar /></el-icon>
                        <span>排班时间:{{scheduleTime}}</span>
                    </div>
                    </div>
                </div>
                <div class="data-cards">
@@ -169,6 +173,7 @@
import Echarts from "@/components/Echarts/echarts.vue";
import * as echarts from 'echarts';
import useUserStore from "@/store/modules/user.js";
import { Clock, Calendar } from '@element-plus/icons-vue'
import {
    analysisCustomerContractAmounts, getAmountHalfYear,
    getBusiness,
@@ -176,6 +181,8 @@
    qualityStatistics,
    statisticsReceivablePayable
} from "@/api/viewIndex.js";
import { listPage } from "@/api/personnelManagement/scheduling.js";
import dayjs from "dayjs";
const userStore = useUserStore()
@@ -341,6 +348,10 @@
const todoList = ref([])
const radio1 = ref(1)
// 排班时间
const scheduleTime = ref('')
const scheduleInfo = ref({})
// 图表引用
const barChart = ref(null)
const lineChart = ref(null)
@@ -358,6 +369,7 @@
    statisticsReceivable()
    qualityStatisticsInfo()
    getAmountHalfYearNum()
    getCurrentUserSchedule()
})
// 数据统计
const getBusinessData = () => {
@@ -409,6 +421,42 @@
        qualityStatisticsObject.value.factoryNum = res.data.factoryNum
    })
}
// 获取当前用户排班信息
const getCurrentUserSchedule = async () => {
  try {
    const today = dayjs().format('YYYY-MM-DD')
    const res = await listPage({
      staffName: userStore.name,
      startDate: today,
      endDate: today,
      current: 1,
      size: 10
    })
    if (res.data && res.data.records && res.data.records.length > 0) {
      const currentSchedule = res.data.records[0]
      scheduleInfo.value = currentSchedule
      // 格式化排班时间显示
      if (currentSchedule.startTime && currentSchedule.endTime) {
        scheduleTime.value = `${currentSchedule.startTime} - ${currentSchedule.endTime}`
      } else if (currentSchedule.workStartTime && currentSchedule.workEndTime) {
        const startTime = dayjs(currentSchedule.workStartTime).format('HH:mm')
        const endTime = dayjs(currentSchedule.workEndTime).format('HH:mm')
        scheduleTime.value = `${startTime} - ${endTime}`
      } else {
        scheduleTime.value = '今日无排班'
      }
    } else {
      scheduleTime.value = '今日无排班'
      scheduleInfo.value = {}
    }
  } catch (error) {
    console.error('获取排班信息失败:', error)
    scheduleTime.value = '获取排班信息失败'
  }
}
const getAmountHalfYearNum = async () => {
    const res = await getAmountHalfYear()
    console.log(res)
src/views/personnelManagement/payrollManagement/components/formDia.vue
@@ -27,128 +27,15 @@
                            <el-select v-model="form.staffId" placeholder="请选择人员" style="width: 100%" @change="handleSelect" :disabled="operationType === 'edit'">
                                <el-option
                                    v-for="item in personList"
                                    :key="item.id"
                                    :label="item.staffName"
                                    :value="item.id"
                                    :key="item.userId"
                                    :label="item.nickName"
                                    :value="item.userId"
                                />
                            </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="应出勤天数:" prop="shouldAttendedNum">
                            <el-input v-model="form.shouldAttendedNum" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="实际出勤天数:" prop="actualAttendedNum">
              <el-input v-model="form.actualAttendedNum" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="基本工资:" prop="basicSalary">
              <el-input v-model="form.basicSalary" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="岗位工资:" prop="postSalary">
              <el-input v-model="form.postSalary" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="入离职缺勤扣款:" prop="deductionAbsenteeism">
              <el-input v-model="form.deductionAbsenteeism" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="病假扣款:" prop="sickLeaveDeductions">
              <el-input v-model="form.sickLeaveDeductions" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="事假扣款:" prop="deductionPersonalLeave">
              <el-input v-model="form.deductionPersonalLeave" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="忘记打卡扣款:" prop="forgetClockDeduct">
              <el-input v-model="form.forgetClockDeduct" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="绩效得分:" prop="performanceScore">
              <el-input v-model="form.performanceScore" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="绩效工资:" prop="performancePay">
              <el-input v-model="form.performancePay" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="应发合计:" prop="payableWages">
              <el-input v-model="form.payableWages" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="社保个人:" prop="socialSecurityIndividuals">
              <el-input v-model="form.socialSecurityIndividuals" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="社保公司:" prop="socialSecurityCompanies">
                            <el-input v-model="form.socialSecurityCompanies" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="社保合计:" prop="socialSecurityTotal">
                            <el-input v-model="form.socialSecurityTotal" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="公积金个人:" prop="providentFundIndividuals">
                            <el-input v-model="form.providentFundIndividuals" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="公积金公司:" prop="providentFundCompany">
                            <el-input v-model="form.providentFundCompany" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="公积金合计:" prop="providentFundTotal">
                            <el-input v-model="form.providentFundTotal" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="应税工资:" prop="taxableWaget">
                            <el-input v-model="form.taxableWaget" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="个人所得税:" prop="personalIncomeTax">
                            <el-input v-model="form.personalIncomeTax" :step="0.1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="实发工资:" prop="actualWages">
                            <el-input v-model="form.actualWages" style="width: 100%" type="number"/>
@@ -170,6 +57,7 @@
import {ref} from "vue";
import {getStaffJoinInfo, getStaffOnJob, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {compensationAdd, compensationUpdate} from "@/api/personnelManagement/payrollManagement.js";
import {listUser} from "@/api/system/user.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
@@ -180,50 +68,11 @@
        payDate: "",
    staffId: "",
        name: "",
        shouldAttendedNum: "",
        actualAttendedNum: "",
        basicSalary: "",
        postSalary: "",
        deductionAbsenteeism: "",
        sickLeaveDeductions: "",
        deductionPersonalLeave: "",
        forgetClockDeduct: "",
        performanceScore: "",
        performancePay: "",
        payableWages: "",
        socialSecurityIndividuals: "",
        socialSecurityCompanies: "",
        socialSecurityTotal: "",
        providentFundIndividuals: "",
        providentFundCompany: "",
        providentFundTotal: "",
        taxableWaget: "",
        personalIncomeTax: "",
        actualWages: "",
  },
  rules: {
        payDate: [{ required: true, message: "请选择", trigger: "change" },],
        staffId: [{ required: true, message: "请选择", trigger: "change" },],
    staffName: [{ required: true, message: "请输入", trigger: "blur" }],
        shouldAttendedNum: [{ required: true, message: "请输入", trigger: "blur" }],
        actualAttendedNum: [{ required: true, message: "请输入", trigger: "blur" }],
        basicSalary: [{ required: true, message: "请输入", trigger: "blur" }],
        postSalary: [{ required: true, message: "请输入", trigger: "blur" }],
        deductionAbsenteeism: [{ required: true, message: "请输入", trigger: "blur" }],
        sickLeaveDeductions: [{ required: true, message: "请输入", trigger: "blur" }],
        deductionPersonalLeave: [{ required: true, message: "请输入", trigger: "blur" }],
        forgetClockDeduct: [{ required: true, message: "请输入", trigger: "blur" }],
        performanceScore: [{ required: true, message: "请输入", trigger: "blur" }],
        performancePay: [{ required: true, message: "请输入", trigger: "blur" }],
        payableWages: [{ required: true, message: "请输入", trigger: "blur" }],
        socialSecurityIndividuals: [{ required: true, message: "请输入", trigger: "blur" }],
        socialSecurityCompanies: [{ required: true, message: "请输入", trigger: "blur" }],
        socialSecurityTotal: [{ required: true, message: "请输入", trigger: "blur" }],
        providentFundIndividuals: [{ required: true, message: "请输入", trigger: "blur" }],
        providentFundCompany: [{ required: true, message: "请输入", trigger: "blur" }],
        providentFundTotal: [{ required: true, message: "请输入", trigger: "blur" }],
        taxableWaget: [{ required: true, message: "请输入", trigger: "blur" }],
        personalIncomeTax: [{ required: true, message: "请输入", trigger: "blur" }],
        actualWages: [{ required: true, message: "请输入", trigger: "blur" }],
  },
});
@@ -234,8 +83,8 @@
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    getStaffOnJob().then(res => {
        personList.value = res.data
    listUser().then(res => {
        personList.value = res.rows
    })
    form.value = {}
  if (operationType.value === 'edit') {
@@ -246,10 +95,9 @@
  }
}
const handleSelect = (value) => {
    console.log('value', value)
    const index = personList.value.findIndex(row => row.id === value)
    const index = personList.value.findIndex(row => row.userId === value)
    if (index > -1) {
        form.value.name = personList.value[index].staffName
        form.value.name = personList.value[index].nickName
    }
}
// 提交产品表单
src/views/personnelManagement/payrollManagement/index.vue
@@ -27,9 +27,10 @@
                >
            </div>
            <div>
                <el-button @click="handleExport" style="margin-right: 10px">导出</el-button>
                <el-button @click="handleImport">导入</el-button>
                <el-button type="primary" @click="openForm('add')">新增薪资</el-button>
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
                <el-button @click="handleExport">导出</el-button>
            </div>
        </div>
        <div class="table_list">
@@ -46,17 +47,56 @@
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
        <!-- 导入弹窗 -->
        <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body @close="handleUploadClose">
            <el-upload
                ref="uploadRef"
                :limit="1"
                accept=".xlsx, .xls"
                :headers="upload.headers"
                :action="upload.url"
                :disabled="upload.isUploading"
                :on-progress="upload.onProgress"
                :on-success="upload.onSuccess"
                :on-error="upload.onError"
                :on-change="upload.onChange"
                :auto-upload="false"
                drag
            >
                <el-icon class="el-icon--upload"><upload-filled /></el-icon>
                <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
                <template #tip>
                    <div class="el-upload__tip text-center">
                        <span>仅允许导入xls、xlsx格式文件。</span>
                        <el-link
                            type="primary"
                            :underline="false"
                            style="font-size: 12px; vertical-align: baseline"
                            @click="importTemplate"
                        >下载模板</el-link>
                    </div>
                </template>
            </el-upload>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitFileForm">确 定</el-button>
                    <el-button @click="handleUploadCancel">取 消</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>
<script setup>
import { Search } from "@element-plus/icons-vue";
import { Search, UploadFilled } from "@element-plus/icons-vue";
import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick} from "vue";
import FormDia from "@/views/personnelManagement/payrollManagement/components/formDia.vue";
import {staffJoinDel} from "@/api/personnelManagement/onboarding.js";
import {ElMessageBox} from "element-plus";
import dayjs from "dayjs";
import {compensationDelete, compensationListPage} from "@/api/personnelManagement/payrollManagement.js";
import { getToken } from "@/utils/auth.js";
const data = reactive({
    searchForm: {
@@ -73,98 +113,6 @@
    {
        label: "姓名",
        prop: "name",
    },
    {
        label: "应出勤天数",
        prop: "shouldAttendedNum",
        width:100
    },
    {
        label: "实际出勤天数",
        prop: "actualAttendedNum",
        width:110
    },
    {
        label: "基本工资",
        prop: "basicSalary",
    },
    {
        label: "岗位工资",
        prop: "postSalary",
        width:100
    },
    {
        label: "入离职缺勤扣款",
        prop: "deductionAbsenteeism",
        width:130
    },
    {
        label: "病假扣款",
        prop: "sickLeaveDeductions",
        width:100
    },
    {
        label: "事假扣款",
        prop: "deductionPersonalLeave",
        width:100
    },
    {
        label: "忘记打卡扣款",
        prop: "forgetClockDeduct",
        width:110
    },
    {
        label: "绩效得分",
        prop: "performanceScore",
        width:150
    },
    {
        label: "绩效工资",
        prop: "performancePay",
        width: 120
    },
    {
        label: "应发合计",
        prop: "payableWages",
        width:150
    },
    {
        label: "社保个人",
        prop: "socialSecurityIndividuals",
    },
    {
        label: "社保公司",
        prop: "socialSecurityCompanies",
        width: 120
    },
    {
        label: "社保合计",
        prop: "socialSecurityTotal",
        width: 120
    },
    {
        label: "公积金个人",
        prop: "providentFundIndividuals",
        width: 120
    },
    {
        label: "公积金公司",
        prop: "providentFundCompany",
        width: 120
    },
    {
        label: "公积金合计",
        prop: "providentFundTotal",
        width: 120
    },
    {
        label: "应税工资",
        prop: "taxableWaget",
    },
    {
        label: "个人所得税",
        prop: "personalIncomeTax",
        width: 120
    },
    {
        label: "实发工资",
@@ -197,6 +145,56 @@
});
const formDia = ref()
const { proxy } = getCurrentInstance()
// 导入功能配置
const upload = reactive({
  // 是否显示弹出层(薪资导入)
  open: false,
  // 弹出层标题(薪资导入)
  title: "",
  // 是否禁用上传
  isUploading: false,
  // 设置上传的请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // 上传的地址
  url: import.meta.env.VITE_APP_BASE_API + "/compensationPerformance/importData",
  // 文件上传前的回调
  beforeUpload: (file) => {
    // 可以在此处做文件类型或大小校验
    const isValid = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
    if (!isValid) {
      proxy.$modal.msgError("只能上传 Excel 文件");
    }
    return isValid;
  },
  // 文件状态改变时的回调
  onChange: (file, fileList) => {
    console.log('文件状态改变', file, fileList);
  },
  // 文件上传成功时的回调
  onSuccess: (response, file, fileList) => {
    upload.isUploading = false;
    if(response.code === 200){
      proxy.$modal.msgSuccess("文件上传成功");
      handleUploadClose();
      getList();
    }else if(response.code === 500){
      proxy.$modal.msgError(response.msg);
    }else{
      proxy.$modal.msgWarning(response.msg);
    }
  },
  // 文件上传失败时的回调
  onError: (error, file, fileList) => {
    console.error('上传失败', error, file, fileList);
    upload.isUploading = false;
    proxy.$modal.msgError("文件上传失败");
  },
  // 文件上传进度回调
  onProgress: (event, file, fileList) => {
    console.log('上传中...', event.percent);
  }
});
const handleDateChange = (value,type) => {
    searchForm.value.entryDateEnd = null
@@ -299,6 +297,39 @@
        });
};
/** 导入按钮操作 */
function handleImport() {
  upload.title = "薪资导入";
  upload.open = true;
}
/** 提交上传文件 */
function submitFileForm() {
  upload.isUploading = true;
  proxy.$refs["uploadRef"].submit();
}
/** 下载模板 */
function importTemplate() {
  proxy.download("/compensationPerformance/exportTemplate", {}, "薪资导入模板.xlsx");
}
// 处理上传弹框取消
function handleUploadCancel() {
  upload.open = false;
  handleUploadClose();
}
// 处理上传弹框关闭
function handleUploadClose() {
  upload.open = false;
  upload.isUploading = false;
  // 清空上传文件缓存
  if (proxy.$refs.uploadRef) {
    proxy.$refs.uploadRef.clearFiles();
  }
}
onMounted(() => {
    getList();
});
src/views/personnelManagement/scheduling/index.vue
@@ -11,34 +11,14 @@
              style="width: 150px"
          />
        </el-form-item>
        <el-form-item label="班次类型:">
          <el-select v-model="filterForm.shiftType" placeholder="请选择班次" clearable style="width: 120px">
            <el-option v-for="item in shift_type" :label="item.label" :value="item.value" :key="item.value"/>
          </el-select>
        </el-form-item>
        <el-form-item label="日期范围:">
          <el-date-picker
              v-model="filterForm.dateRange"
              type="daterange"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              format="YYYY-MM-DD"
              value-format="YYYY-MM-DD"
              style="width: 250px"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleFilter">
            <el-icon><Search/></el-icon>
            筛选
            搜索
          </el-button>
          <el-button @click="resetFilter">
            <el-icon><Refresh/></el-icon>
            重置
          </el-button>
          <el-button @click="handleExport">
            <el-icon><Download/></el-icon>
            导出
          </el-button>
          <el-button type="primary" @click="openScheduleDialog('add')">
@@ -61,48 +41,53 @@
          @selection-change="handleSelectionChange"
      >
        <el-table-column type="selection" width="55"/>
        <el-table-column prop="staffName" label="员工姓名" width="120"/>
        <el-table-column prop="staffNo" label="员工工号" width="100"/>
        <el-table-column prop="department" label="部门" width="120">
        <el-table-column prop="staffName" label="员工姓名"/>
        <!-- <el-table-column prop="staffNo" label="员工工号" width="100"/> -->
        <!-- <el-table-column prop="department" label="部门" width="120">
          <template #default="scope">
              {{ (department_type.find(i => i.value === String(scope.row.department)) || {}).label }}
          </template>
        </el-table-column>
        <el-table-column prop="shiftType" label="班次类型" width="100">
        </el-table-column> -->
        <!-- <el-table-column prop="shiftType" label="班次类型" width="100">
          <template #default="scope">
            <el-tag :type="getShiftTagType(scope.row.shiftType)">
              {{ (shift_type.find(i => i.value === String(scope.row.shiftType)) || {}).label }}
            </el-tag>
          </template>
        </el-table-column> -->
        <!-- <el-table-column prop="workDate" label="工作日期" width="120"/> -->
        <el-table-column prop="workStartTime" label="开始时间"/>
        <el-table-column prop="workEndTime" label="结束时间"/>
        <el-table-column prop="lunchTime" label="午休时间(h)">
          <template #default="scope">
            {{ scope.row.lunchTime }}小时
          </template>
        </el-table-column>
        <el-table-column prop="workDate" label="工作日期" width="120"/>
        <el-table-column prop="startTime" label="开始时间" width="100"/>
        <el-table-column prop="endTime" label="结束时间" width="100"/>
        <el-table-column prop="workHours" label="工作时长" width="100">
        <!-- <el-table-column prop="workHours" label="工作时长" width="100">
          <template #default="scope">
            {{ scope.row.workHours }}小时
          </template>
        </el-table-column>
        <el-table-column prop="status" label="状态" width="100">
        </el-table-column> -->
        <!-- <el-table-column prop="status" label="状态" width="100">
          <template #default="scope">
            <el-tag :type="getStatusTagType(scope.row.status)">
              {{ (schedule_status.find(i => i.value === String(scope.row.status)) || {}).label }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="remark" label="备注" min-width="150"/>
        <el-table-column label="操作" width="200" fixed="right">
        </el-table-column> -->
        <!-- <el-table-column prop="remark" label="备注" min-width="150"/> -->
        <el-table-column label="操作" width="200" fixed="right" align="center">
          <template #default="scope">
            <el-button
                            link
                type="primary"
                size="small"
                @click="openScheduleDialog('edit', scope.row)"
            >
              编辑
            </el-button>
            <el-button
                            link
                type="danger"
                size="small"
                @click="handleDelete(scope.row)"
            >
              删除
@@ -144,22 +129,26 @@
          label-width="120px"
      >
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="员工姓名:" prop="staffId">
              <el-select v-model="scheduleForm.staffId" placeholder="请输入员工姓名" style="width: 100%"
          <el-col :span="24">
            <el-form-item label="员工姓名:" prop="staffIds">
          <el-select v-model="scheduleForm.staffIds" placeholder="请选择员工姓名" style="width: 100%"
                         multiple filterable collapse-tags-tooltip
                         @change="handleSelectStaff">
                <el-option v-for="item in personList" :label="item.staffName" :value="item.id" :key="item.id"/>
            <el-option v-for="item in personList" :label="item.nickName" :value="item.userId" :key="item.userId"/>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <!-- <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="员工工号:" prop="staffNo">
              <el-input :disabled="true" v-model="scheduleForm.staffNo" placeholder=""/>
            </el-form-item>
          </el-col>
        </el-row>
        </el-row> -->
        <el-row :gutter="20">
        <!-- <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="部门:" prop="department">
              <el-select v-model="scheduleForm.department" placeholder="请选择部门" style="width: 100%">
@@ -174,9 +163,9 @@
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        </el-row> -->
        <el-row :gutter="20">
        <!-- <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工作日期:" prop="workDate">
              <el-date-picker
@@ -196,34 +185,49 @@
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        </el-row> -->
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="开始时间:" prop="startTime">
            <el-form-item label="开始时间:" prop="workStartTime">
              <el-time-picker
                  v-model="scheduleForm.startTime"
                  v-model="scheduleForm.workStartTime"
                  placeholder="选择开始时间"
                  style="width: 100%"
                  format="HH:mm"
                  value-format="HH:mm"
                  value-format="YYYY-MM-DD HH:mm:ss"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="结束时间:" prop="endTime">
            <el-form-item label="结束时间:" prop="workEndTime">
              <el-time-picker
                  v-model="scheduleForm.endTime"
                  v-model="scheduleForm.workEndTime"
                  placeholder="选择结束时间"
                  style="width: 100%"
                  format="HH:mm"
                  value-format="HH:mm"
                  value-format="YYYY-MM-DD HH:mm:ss"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="午休时间(h):" prop="lunchTime">
              <el-input-number
                  v-model="scheduleForm.lunchTime"
                  :min="0"
                  :max="8"
                  :step="0.5"
                  placeholder="请输入午休时间"
                  style="width: 100%"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注:" prop="remark">
              <el-input
@@ -234,7 +238,7 @@
              />
            </el-form-item>
          </el-col>
        </el-row>
        </el-row> -->
      </el-form>
      <template #footer>
@@ -248,7 +252,7 @@
</template>
<script setup>
import {ref, reactive, computed, onMounted, getCurrentInstance} from 'vue'
import {ref, reactive, computed, onMounted, getCurrentInstance, watch} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import {useDict} from "@/utils/dict.js"
import {Plus, Download, Search, Refresh} from '@element-plus/icons-vue'
@@ -256,6 +260,7 @@
import {getStaffOnJob} from "@/api/personnelManagement/onboarding.js";
import dayjs from "dayjs";
import pagination from "@/components/PIMTable/Pagination.vue";
import {listUser} from "@/api/system/user.js";
const { proxy } = getCurrentInstance();
@@ -269,8 +274,6 @@
// 筛选表单
const filterForm = reactive({
  staffName: '',
  shiftType: '',
  dateRange: [],
  current:1,
  size: 10
})
@@ -278,29 +281,31 @@
// 排班表单
const scheduleForm = reactive({
  id: '',
  staffId: '',
  staffNo: '',
  department: '',
  shiftType: '',
  workDate: '',
  startTime: '',
  endTime: '',
  staffIds: [],
  // staffNo: '',
  // department: '',
  // shiftType: '',
  // workDate: '',
  workStartTime: '',
  workEndTime: '',
  workHours: 0,
  status: '',
  remark: ''
  lunchTime: 3,
  // workStartTime: '',
  // workEndTime: '',
  // workHours: 0,
  // status: '',
  // remark: ''
})
// 表单验证规则
const scheduleRules = reactive({
  staffId: [{required: true, message: '请选择员工', trigger: 'change'}],
  department: [{required: true, message: '请选择部门', trigger: 'change'}],
  shiftType: [{required: true, message: '请选择班次类型', trigger: 'change'}],
  workDate: [{required: true, message: '请选择工作日期', trigger: 'change'}],
  startTime: [{required: true, message: '请选择开始时间', trigger: 'change'}],
  endTime: [{required: true, message: '请选择结束时间', trigger: 'change'}],
  status: [{required: true, message: '请选择状态', trigger: 'change'}]
  staffIds: [{required: true, message: '请选择员工', trigger: 'change'}],
  // department: [{required: true, message: '请选择部门', trigger: 'change'}],
  // shiftType: [{required: true, message: '请选择班次类型', trigger: 'change'}],
  // workDate: [{required: true, message: '请选择工作日期', trigger: 'change'}],
  workStartTime: [{required: true, message: '请选择开始时间', trigger: 'change'}],
  workEndTime: [{required: true, message: '请选择结束时间', trigger: 'change'}],
  lunchTime: [{required: true, message: '请输入午休时间', trigger: 'blur'}],
  // status: [{required: true, message: '请选择状态', trigger: 'change'}]
})
const tableLoading = ref(false)
@@ -317,8 +322,8 @@
 * 获取当前在职人员列表
 */
const getPersonList = () => {
  getStaffOnJob().then(res => {
    personList.value = res.data
    listUser().then(res => {
        personList.value = res.rows
  })
};
const paginationChange = (obj) => {
@@ -328,9 +333,9 @@
};
const handleSelectStaff = (val) => {
  let obj = personList.value.find(item => item.id === val)
  scheduleForm.staffNo = obj.staffNo
  // 多选员工,不再自动设置员工工号
  // let obj = personList.value.find(item => item.id === val)
  // scheduleForm.staffNo = obj.staffNo
}
// 获取班次标签类型
@@ -349,19 +354,19 @@
const handleFilter = async () => {
  tableLoading.value = true
  let searchForm = {
    ...filterForm,
    ...(filterForm.dateRange.length > 0 && {
      startDate: filterForm.dateRange[0],
      endDate: filterForm.dateRange[1],
    })
    ...filterForm
  }
  let resp = await listPage(searchForm)
  tableCount.value = resp.data.total
  scheduleList.value = resp.data.records.map(it => {
    return {
      ...it,
      'startTime': dayjs(it.workStartTime).format('HH:mm'),
      'endTime': dayjs(it.workEndTime).format('HH:mm'),
      // 保存原始时间格式用于编辑
      'originalWorkStartTime': it.workStartTime,
      'originalWorkEndTime': it.workEndTime,
      // 格式化时间用于表格显示
      'workStartTime': dayjs(it.workStartTime).format('HH:mm'),
      'workEndTime': dayjs(it.workEndTime).format('HH:mm'),
    }
  })
  tableLoading.value = false
@@ -371,8 +376,6 @@
// 重置筛选
const resetFilter = () => {
  filterForm.staffName = ''
  filterForm.shiftType = ''
  filterForm.dateRange = []
}
// 打开排班对话框
@@ -381,15 +384,28 @@
  scheduleDialog.value = true
  getPersonList()
  if (type === 'edit' && data) {
    // 编辑模式,复制数据
    Object.assign(scheduleForm, {...data})
    // 编辑模式,复制数据,将员工ID字符串转换为数组格式,并处理时间字段
    Object.assign(scheduleForm, {
      ...data,
        lunchTime: Number(data.lunchTime),
      staffIds: data.staffId ? data.staffId.split(',').map(id => parseInt(id)) : [],
      // 使用原始时间字符串,因为表格中显示的是格式化后的HH:mm
      workStartTime: data.originalWorkStartTime || '',
      workEndTime: data.originalWorkEndTime || ''
    })
  } else {
    // 新增模式,重置表单
    Object.keys(scheduleForm).forEach(key => {
        if (key === 'staffIds') {
          scheduleForm[key] = []
        } else if (key === 'lunchTime') {
          scheduleForm[key] = 3
        } else {
      scheduleForm[key] = ''
        }
    })
    // scheduleForm.status = '已安排'
    scheduleForm.workDate = new Date().toISOString().split('T')[0]
      // scheduleForm.workDate = new Date().toISOString().split('T')[0]
  }
}
@@ -401,37 +417,86 @@
// 计算工作时长
const calculateWorkHours = () => {
  if (scheduleForm.workDate && scheduleForm.startTime && scheduleForm.endTime) {
    // 使用 workDate 与 startTime 和 endTime 组合
    const startDateTime = new Date(`${scheduleForm.workDate} ${scheduleForm.startTime}`)
    const endDateTime = new Date(`${scheduleForm.workDate} ${scheduleForm.endTime}`)
  if (!scheduleForm.workStartTime || !scheduleForm.workEndTime) {
    return;
  }
  try {
    // 使用dayjs正确解析时间
    const startDayjs = dayjs(scheduleForm.workStartTime);
    const endDayjs = dayjs(scheduleForm.workEndTime);
    if (!startDayjs.isValid() || !endDayjs.isValid()) {
      return;
    }
    const startDateTime = startDayjs.toDate();
    const endDateTime = endDayjs.toDate();
    // 处理跨天情况(结束时间早于开始时间)
    if (endDateTime < startDateTime) {
      // 跨天,将结束日期加一天
      endDateTime.setDate(endDateTime.getDate() + 1)
      endDateTime.setDate(endDateTime.getDate() + 1);
    }
    // 计算工作时长(小时)
    const diffMs = endDateTime - startDateTime
    const diffHours = diffMs / (1000 * 60 * 60)
    scheduleForm.workHours = Math.round(diffHours * 100) / 100
    scheduleForm.workStartTime = dayjs(startDateTime).format("YYYY-MM-DD HH:mm:ss")
    scheduleForm.workEndTime = dayjs(endDateTime).format("YYYY-MM-DD HH:mm:ss")
    // 计算工作时长(小时)
    const diffMs = endDateTime - startDateTime;
    const diffHours = diffMs / (1000 * 60 * 60);
    scheduleForm.workHours = Math.round(diffHours * 100) / 100;
  } catch (error) {
    console.error('时间计算错误:', error);
  }
}
// 监听时间字段变化,自动计算工作时长
watch(
  () => [scheduleForm.workStartTime, scheduleForm.workEndTime],
  () => {
    calculateWorkHours()
  },
  { deep: true }
)
// 提交排班表单
const submitScheduleForm = async () => {
  const valid = await scheduleFormRef.value.validate()
  if (!valid) return
  calculateWorkHours()
  const newSchedule = {...scheduleForm}
  // 由于员工是多选,需要为每个选中的员工创建排班记录
  const selectedStaffIds = scheduleForm.staffIds || []
  if (selectedStaffIds.length === 0) {
    ElMessage.warning('请至少选择一个员工')
    return
  }
  try {
    // 获取选中的员工姓名列表
    const selectedStaffNames = selectedStaffIds.map(staffId => {
      const staff = personList.value.find(item => item.userId === staffId)
      return staff ? staff.nickName : ''
    }).filter(name => name !== '')
    // 将员工姓名组装成逗号分隔的字符串
    const staffNameString = selectedStaffNames.join(',')
    // 创建排班记录,将员工姓名保存为字符串格式
    const newSchedule = {
      ...scheduleForm,
      staffName: staffNameString,
      staffId: selectedStaffIds.join(','), // 将员工ID也保存为逗号分隔的字符串
      // 设置其他必要字段的默认值
      staffNo: '', // 可以根据需要从personList中获取
      department: '',
      shiftType: '',
      workDate: '',
      status: '',
      remark: ''
    }
    await save(newSchedule)
    ElMessage.success('保存排班成功')
    ElMessage.success(`成功为 ${selectedStaffNames.length} 个员工创建排班`)
    handleFilter()
    closeScheduleDialog()
@@ -491,11 +556,7 @@
// 导出
const handleExport = () => {
  let searchForm = {
    ...filterForm,
    ...(filterForm.dateRange.length > 0 && {
      startDate: filterForm.dateRange[0],
      endDate: filterForm.dateRange[1],
    })
    ...filterForm
  }
  proxy.download('/staff/staffScheduling/export', {}, '人员排班.xlsx')
}