1.排班管理-班次、加一个午休时间(h)、人员多选、人员从用户管理获取,加一条列表也只展示一条
2.薪资管理-只要,人员、薪资、月份三个字段
| | |
| | | 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, |
| | | }); |
| | | } |
| | |
| | | method: 'get',
|
| | | params: query
|
| | | })
|
| | | }// 查询用户列表
|
| | | export function listAll(query) {
|
| | | return request({
|
| | | url: '/system/user//listAll',
|
| | | method: 'get',
|
| | | params: query
|
| | | })
|
| | | }
|
| | |
|
| | | // 查询用户详细
|
| | |
| | | <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"> |
| | |
| | | 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, |
| | |
| | | qualityStatistics, |
| | | statisticsReceivablePayable |
| | | } from "@/api/viewIndex.js"; |
| | | import { listPage } from "@/api/personnelManagement/scheduling.js"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const userStore = useUserStore() |
| | | |
| | |
| | | const todoList = ref([]) |
| | | const radio1 = ref(1) |
| | | |
| | | // 排班时间 |
| | | const scheduleTime = ref('') |
| | | const scheduleInfo = ref({}) |
| | | |
| | | // 图表引用 |
| | | const barChart = ref(null) |
| | | const lineChart = ref(null) |
| | |
| | | statisticsReceivable() |
| | | qualityStatisticsInfo() |
| | | getAmountHalfYearNum() |
| | | getCurrentUserSchedule() |
| | | }) |
| | | // 数据统计 |
| | | const getBusinessData = () => { |
| | |
| | | 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) |
| | |
| | | <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"/> |
| | |
| | | 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']) |
| | | |
| | |
| | | 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" }], |
| | | }, |
| | | }); |
| | |
| | | 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') { |
| | |
| | | } |
| | | } |
| | | 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 |
| | | } |
| | | } |
| | | // 提交产品表单 |
| | |
| | | > |
| | | </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"> |
| | |
| | | ></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: { |
| | |
| | | { |
| | | 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: "实发工资", |
| | |
| | | }); |
| | | 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 |
| | |
| | | }); |
| | | }; |
| | | |
| | | /** 导入按钮操作 */ |
| | | 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(); |
| | | }); |
| | |
| | | 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 |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | @click="openScheduleDialog('edit', scope.row)" |
| | | > |
| | | 编辑 |
| | | </el-button> |
| | | <el-button |
| | | link |
| | | type="danger" |
| | | size="small" |
| | | @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-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) => { |
| | |
| | | }; |
| | | |
| | | 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}) |
| | | // 编辑模式,复制数据,将员工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] |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | // 计算工作时长 |
| | | 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') |
| | | } |