gaoluyang
2025-11-14 7efe917a161f7c6965cfa75ac5ad7e664dcc1eb4
src/views/personnelManagement/scheduling/index.vue
@@ -11,31 +11,15 @@
              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-button>
          <el-button type="primary" @click="openScheduleDialog('add')">
          <el-icon><Plus/></el-icon>
@@ -57,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="120">
          <template #default="scope">
            <el-tag :type="getShiftTagType(scope.row.shiftType)">
              {{ (shift_type.find(i => i.value === String(scope.row.shiftType)) || {}).label }}
              {{ (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="startTime" label="开始时间" width="100"/>
        <el-table-column prop="endTime" label="结束时间" width="100"/>
        <el-table-column prop="workHours" label="工作时长" width="100">
        <!-- <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="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)"
            >
              删除
@@ -106,6 +95,13 @@
          </template>
        </el-table-column>
      </el-table>
        <pagination
            v-if="tableCount > 0"
            :total="tableCount"
            :page="filterForm.current"
            :limit="filterForm.size"
            @pagination="paginationChange"
        />
    </div>
    <!-- 批量操作 -->
@@ -133,22 +129,36 @@
          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-col :span="12">
            <el-form-item label="班次类型:" prop="shiftType">
              <el-select v-model="scheduleForm.shiftType" placeholder="请选择班次类型" style="width: 100%">
                <el-option v-for="item in shift_type" :label="item.label" :value="item.value" :key="item.value"/>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <!-- <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="部门:" prop="department">
              <el-select v-model="scheduleForm.department" placeholder="请选择部门" style="width: 100%">
@@ -163,9 +173,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
@@ -185,34 +195,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
@@ -223,7 +248,7 @@
              />
            </el-form-item>
          </el-col>
        </el-row>
        </el-row> -->
      </el-form>
      <template #footer>
@@ -237,14 +262,19 @@
</template>
<script setup>
import {ref, reactive, computed, onMounted} 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 {save, del, delByIds, listPage} from "@/api/personnelManagement/scheduling.js"
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 tableCount = ref(0)
// 响应式数据
const scheduleDialog = ref(false)
const dialogType = ref('add')
@@ -254,36 +284,38 @@
// 筛选表单
const filterForm = reactive({
  staffName: '',
  shiftType: '',
  dateRange: []
  current:1,
  size: 10
})
// 排班表单
const scheduleForm = reactive({
  id: '',
  staffId: '',
  staffNo: '',
  department: '',
  staffIds: [],
  // staffNo: '',
  // department: '',
  shiftType: '',
  workDate: '',
  startTime: '',
  endTime: '',
  // 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'}],
  staffIds: [{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'}]
  // 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)
@@ -300,16 +332,20 @@
 * 获取当前在职人员列表
 */
const getPersonList = () => {
  getStaffOnJob().then(res => {
    personList.value = res.data
  })
   listUser().then(res => {
      personList.value = res.rows
   })
};
const paginationChange = (obj) => {
  filterForm.current = obj.page;
  filterForm.size = obj.limit;
  handleFilter();
};
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
}
// 获取班次标签类型
@@ -328,19 +364,19 @@
const handleFilter = async () => {
  tableLoading.value = true
  let searchForm = {
    staffName: filterForm.staffName,
    shiftType: filterForm.shiftType,
    ...(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
@@ -350,8 +386,6 @@
// 重置筛选
const resetFilter = () => {
  filterForm.staffName = ''
  filterForm.shiftType = ''
  filterForm.dateRange = []
}
// 打开排班对话框
@@ -360,16 +394,29 @@
  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]
    }
}
// 关闭排班对话框
@@ -380,37 +427,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: scheduleForm.shiftType,
      workDate: '',
      status: '',
      remark: ''
    }
    await save(newSchedule)
    ElMessage.success('保存排班成功')
    ElMessage.success(`成功为 ${selectedStaffNames.length} 个员工创建排班`)
    handleFilter()
    closeScheduleDialog()
@@ -467,6 +563,13 @@
  selectedRows.value = selection
}
// 导出
const handleExport = () => {
  let searchForm = {
    ...filterForm
  }
  proxy.download('/staff/staffScheduling/export', {}, '人员排班.xlsx')
}
// 生命周期
onMounted(() => {