| | |
| | | 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')"> |
| | |
| | | @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 |
| | | type="primary" |
| | | size="small" |
| | | link |
| | | type="primary" |
| | | @click="openScheduleDialog('edit', scope.row)" |
| | | > |
| | | 编辑 |
| | | </el-button> |
| | | <el-button |
| | | type="danger" |
| | | size="small" |
| | | link |
| | | type="danger" |
| | | @click="handleDelete(scope.row)" |
| | | > |
| | | 删除 |
| | |
| | | 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-select> |
| | | </el-form-item> |
| | | <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%"> |
| | |
| | | </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 |
| | |
| | | </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 |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-row> --> |
| | | </el-form> |
| | | |
| | | <template #footer> |
| | |
| | | </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' |
| | |
| | | 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(); |
| | | |
| | |
| | | // 筛选表单 |
| | | const filterForm = reactive({ |
| | | staffName: '', |
| | | shiftType: '', |
| | | dateRange: [], |
| | | current:1, |
| | | size: 10 |
| | | }) |
| | |
| | | // 排班表单 |
| | | 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) |
| | | |
| | |
| | | * 获取当前在职人员列表 |
| | | */ |
| | | const getPersonList = () => { |
| | | getStaffOnJob().then(res => { |
| | | personList.value = res.data |
| | | }) |
| | | listUser().then(res => { |
| | | personList.value = res.rows |
| | | }) |
| | | }; |
| | | const paginationChange = (obj) => { |
| | | filterForm.current = obj.page; |
| | |
| | | }; |
| | | |
| | | 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 |
| | | } |
| | | |
| | | // 获取班次标签类型 |
| | |
| | | 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 |
| | |
| | | // 重置筛选 |
| | | const resetFilter = () => { |
| | | filterForm.staffName = '' |
| | | filterForm.shiftType = '' |
| | | filterForm.dateRange = [] |
| | | } |
| | | |
| | | // 打开排班对话框 |
| | |
| | | scheduleDialog.value = true |
| | | getPersonList() |
| | | if (type === 'edit' && data) { |
| | | // 编辑模式,复制数据 |
| | | Object.assign(scheduleForm, {...data}) |
| | | } else { |
| | | // 新增模式,重置表单 |
| | | Object.keys(scheduleForm).forEach(key => { |
| | | scheduleForm[key] = '' |
| | | // 编辑模式,复制数据,将员工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 || '' |
| | | }) |
| | | // scheduleForm.status = '已安排' |
| | | scheduleForm.workDate = new Date().toISOString().split('T')[0] |
| | | } |
| | | } 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] |
| | | } |
| | | } |
| | | |
| | | // 关闭排班对话框 |
| | |
| | | |
| | | // 计算工作时长 |
| | | 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() |
| | |
| | | // 导出 |
| | | const handleExport = () => { |
| | | let searchForm = { |
| | | ...filterForm, |
| | | ...(filterForm.dateRange.length > 0 && { |
| | | startDate: filterForm.dateRange[0], |
| | | endDate: filterForm.dateRange[1], |
| | | }) |
| | | ...filterForm |
| | | } |
| | | proxy.download('/staff/staffScheduling/export', {}, '人员排班.xlsx') |
| | | } |