feat(crm): 合同含税总金额自动填入订单总金额 & 移除冗余页面

- feat(crm/contract): 含税总额变化后自动填写订单总金额(可配置开关)
- fix(crm/receive): 修复金额单位错误(万元→元);清理未使用导入
- fix(contract/product): 产品备注设置默认值
- chore: 移除已废弃的 OrderDashboard 组件和 finance/order 页面
- feat(wms/hrm): 新增考勤异常管理页面(attendanceAbnormal.vue)
- chore: 移除 trae git 提交规则配置
This commit is contained in:
2026-06-06 13:01:38 +08:00
parent 724c1dd16f
commit 050dd1a965
7 changed files with 901 additions and 381 deletions

View File

@@ -0,0 +1,877 @@
<template>
<div class="app-container">
<div class="date-range-section">
<TimeRangePicker v-model="dateRangeParams" startKey="startDate" endKey="endDate"
:defaultStartTime="defaultStartTime" :defaultEndTime="defaultEndTime" format="yyyy-MM-dd" @change="handleDateRangeChange"
@quick-select="getList" />
</div>
<div class="operation-bar">
<el-button type="primary" plain icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button type="warning" plain icon="el-icon-download" @click="handleExport">导出</el-button>
</div>
<div class="filter-row">
<el-input v-model="queryParams.employeeName" placeholder="请输入员工姓名" clearable class="filter-input" @keyup.enter.native="handleQuery" />
<!-- <el-select v-model="queryParams.overallStatus" placeholder="请选择状态" clearable @change="handleQuery" class="filter-select">
<el-option label="迟到/早退/缺卡" value="abnormal" />
<el-option label="半天旷工" value="absent_half" />
<el-option label="全天旷工" value="absent_full" />
</el-select> -->
</div>
<div class="dept-filter-section" v-if="departmentList.length > 0">
<el-radio-group v-model="selectedDept" @change="handleDeptChange">
<el-radio-button label="">全部{{ totalEmployeeCount }}</el-radio-button>
<el-radio-button v-for="item in departmentList" :key="item.deptName" :label="item.deptName">
{{ item.deptName }}{{ item.count }}
</el-radio-button>
</el-radio-group>
</div>
<el-alert type="info" title="提示:点击修改按钮可查看考勤详情并调整考勤结果"></el-alert>
<el-table v-loading="loading" :data="abnormalList" border stripe>
<el-table-column label="员工姓名" align="center" prop="employeeName" width="100" />
<el-table-column label="日期" align="center" prop="workDate" width="100" />
<el-table-column label="总体状态" align="center" width="120">
<template slot-scope="scope">
<span :class="getStatusClass(scope.row.overallStatus)">{{ getStatusText(scope.row.overallStatus) }}</span>
</template>
</el-table-column>
<el-table-column label="时段一状态" align="center" prop="p1Status" width="100">
<template slot-scope="scope">
<span v-if="scope.row.p1Status" :class="getStatusClass(scope.row.p1Status)">{{ getStatusText(scope.row.p1Status) }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="迟到(分)" align="center" prop="p1LateMinutes" width="80">
<template slot-scope="scope">
<span v-if="scope.row.p1LateMinutes > 0" class="late-info">{{ scope.row.p1LateMinutes }}</span>
<span v-else>0</span>
</template>
</el-table-column>
<el-table-column label="早退(分)" align="center" prop="p1EarlyMinutes" width="80">
<template slot-scope="scope">
<span v-if="scope.row.p1EarlyMinutes > 0" class="early-info">{{ scope.row.p1EarlyMinutes }}</span>
<span v-else>0</span>
</template>
</el-table-column>
<el-table-column label="扣款" align="center" width="80">
<template slot-scope="scope">¥{{ scope.row.p1Deduct || '0.00' }}</template>
</el-table-column>
<el-table-column label="时段二状态" align="center" prop="p2Status" width="100">
<template slot-scope="scope">
<span v-if="scope.row.p2Status" :class="getStatusClass(scope.row.p2Status)">{{ getStatusText(scope.row.p2Status) }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="迟到(分)" align="center" prop="p2LateMinutes" width="80">
<template slot-scope="scope">
<span v-if="scope.row.p2LateMinutes > 0" class="late-info">{{ scope.row.p2LateMinutes }}</span>
<span v-else>0</span>
</template>
</el-table-column>
<el-table-column label="早退(分)" align="center" prop="p2EarlyMinutes" width="80">
<template slot-scope="scope">
<span v-if="scope.row.p2EarlyMinutes > 0" class="early-info">{{ scope.row.p2EarlyMinutes }}</span>
<span v-else>0</span>
</template>
</el-table-column>
<el-table-column label="扣款" align="center" width="80">
<template slot-scope="scope">¥{{ scope.row.p2Deduct || '0.00' }}</template>
</el-table-column>
<el-table-column label="旷工类型" align="center" width="100">
<template slot-scope="scope">
<span v-if="scope.row.absentType === 'half_day'">半天旷工</span>
<span v-else-if="scope.row.absentType === 'full_day'">全天旷工</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="连续旷工天数" align="center" prop="continuousAbsentDays" width="110" />
<el-table-column label="总扣款" align="center" width="90">
<template slot-scope="scope">
<span class="deduct-amount">¥{{ scope.row.totalDeduct || '0.00' }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="80" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">修改</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog title="考勤详情" :visible.sync="detailDialogVisible" width="700px">
<el-form v-if="currentDetail" :model="editForm" label-width="80px">
<el-row>
<el-col :span="12">
<el-form-item label="员工姓名">
<span>{{ currentDetail.employeeName }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="日期">
<span>{{ currentDetail.workDate || currentDetail.startDate }}</span>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="班次名称">
<el-input v-if="isEdit" v-model="editForm.shiftName" placeholder="请输入班次名称" />
<span v-else>{{ currentDetail.shiftName || '-' }}</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="班次类型">
<el-select v-if="isEdit" v-model="editForm.shiftType" placeholder="请选择班次类型">
<el-option label="白班" value="白班" />
<el-option label="夜班" value="夜班" />
</el-select>
<span v-else>{{ currentDetail.shiftType || '-' }}</span>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="总体状态">
<el-select v-if="isEdit" v-model="editForm.overallStatus" placeholder="请选择状态">
<el-option label="正常" value="normal" />
<el-option label="迟到/早退/缺卡" value="abnormal" />
<el-option label="半天旷工" value="absent_half" />
<el-option label="全天旷工" value="absent_full" />
</el-select>
<span v-else :class="getStatusClass(currentDetail.overallStatus)">{{ getStatusText(currentDetail.overallStatus) }}</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="旷工类型">
<el-select v-if="isEdit" v-model="editForm.absentType" placeholder="请选择旷工类型">
<el-option label="半天旷工" value="half_day" />
<el-option label="全天旷工" value="full_day" />
</el-select>
<span v-else>{{ currentDetail.absentType ? (currentDetail.absentType === 'half_day' ? '半天旷工' : '全天旷工') : '-' }}</span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="连续旷工天数">
<el-input v-if="isEdit" v-model="editForm.continuousAbsentDays" type="number" placeholder="请输入天数" />
<span v-else>{{ currentDetail.continuousAbsentDays || '0' }}</span>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="总扣款金额">
<el-input v-if="isEdit" v-model="editForm.totalDeduct" type="number" placeholder="请输入金额" />
<span v-else class="deduct-amount">¥{{ currentDetail.totalDeduct || '0.00' }}</span>
</el-form-item>
</el-col>
<el-col :span="16">
<el-form-item label="备注">
<el-input v-if="isEdit" v-model="editForm.remark" placeholder="请输入备注" />
<span v-else>{{ currentDetail.remark || '-' }}</span>
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">时段一</el-divider>
<el-row>
<el-col :span="12">
<el-form-item label="理论上班">
<el-time-picker style="width: 100%" v-if="isEdit" v-model="editForm.p1StartTime" value-format="HH:mm:ss" placeholder="选择时间" />
<span v-else>{{ formatTime(currentDetail.p1StartTime) }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="理论下班">
<el-time-picker style="width: 100%" v-if="isEdit" v-model="editForm.p1EndTime" value-format="HH:mm:ss" placeholder="选择时间" />
<span v-else>{{ formatTime(currentDetail.p1EndTime) }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实际上班">
<span>{{ formatTime(currentDetail.p1FirstCheck) }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实际下班">
<span>{{ formatTime(currentDetail.p1LastCheck) }}</span>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="6">
<el-form-item label="状态">
<el-select v-if="isEdit" v-model="editForm.p1Status" placeholder="请选择状态">
<el-option label="正常" value="normal" />
<el-option label="迟到预警" value="late_warn" />
<el-option label="迟到I" value="late_one" />
<el-option label="迟到II" value="late_two" />
<el-option label="早退预警" value="early_warn" />
<el-option label="早退I" value="early_one" />
<el-option label="早退II" value="early_two" />
<el-option label="半天旷工" value="absent_half" />
<el-option label="未打卡" value="missed" />
</el-select>
<span v-else :class="getStatusClass(currentDetail.p1Status)">{{ getStatusText(currentDetail.p1Status) }}</span>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="迟到分钟">
<el-input v-if="isEdit" v-model="editForm.p1LateMinutes" type="number" placeholder="请输入分钟数" />
<span v-else>{{ currentDetail.p1LateMinutes || '0' }}</span>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="早退分钟">
<el-input v-if="isEdit" v-model="editForm.p1EarlyMinutes" type="number" placeholder="请输入分钟数" />
<span v-else>{{ currentDetail.p1EarlyMinutes || '0' }}</span>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="扣款金额">
<el-input v-if="isEdit" v-model="editForm.p1Deduct" type="number" placeholder="请输入金额" />
<span v-else>¥{{ currentDetail.p1Deduct || '0.00' }}</span>
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left" v-if="currentDetail.p2StartTime || isEdit">时段二</el-divider>
<el-row v-if="currentDetail.p2StartTime || isEdit">
<el-col :span="12">
<el-form-item label="理论上班">
<el-time-picker style="width: 100%" v-if="isEdit" v-model="editForm.p2StartTime" value-format="HH:mm:ss" placeholder="选择时间" />
<span v-else>{{ formatTime(currentDetail.p2StartTime) }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="理论下班">
<el-time-picker style="width: 100%" v-if="isEdit" v-model="editForm.p2EndTime" value-format="HH:mm:ss" placeholder="选择时间" />
<span v-else>{{ formatTime(currentDetail.p2EndTime) }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实际上班">
<span>{{ formatTime(currentDetail.p2FirstCheck) }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实际下班">
<span>{{ formatTime(currentDetail.p2LastCheck) }}</span>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="currentDetail.p2StartTime || isEdit">
<el-col :span="6">
<el-form-item label="状态">
<el-select v-if="isEdit" v-model="editForm.p2Status" placeholder="请选择状态">
<el-option label="正常" value="normal" />
<el-option label="迟到预警" value="late_warn" />
<el-option label="迟到I" value="late_one" />
<el-option label="迟到II" value="late_two" />
<el-option label="早退预警" value="early_warn" />
<el-option label="早退I" value="early_one" />
<el-option label="早退II" value="early_two" />
<el-option label="半天旷工" value="absent_half" />
<el-option label="未打卡" value="missed" />
</el-select>
<span v-else :class="getStatusClass(currentDetail.p2Status)">{{ getStatusText(currentDetail.p2Status) }}</span>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="迟到分钟">
<el-input v-if="isEdit" v-model="editForm.p2LateMinutes" type="number" placeholder="请输入分钟数" />
<span v-else>{{ currentDetail.p2LateMinutes || '0' }}</span>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="早退分钟">
<el-input v-if="isEdit" v-model="editForm.p2EarlyMinutes" type="number" placeholder="请输入分钟数" />
<span v-else>{{ currentDetail.p2EarlyMinutes || '0' }}</span>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="扣款金额">
<el-input v-if="isEdit" v-model="editForm.p2Deduct" type="number" placeholder="请输入金额" />
<span v-else>¥{{ currentDetail.p2Deduct || '0.00' }}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-divider content-position="left">请假记录</el-divider>
<div class="dialog-filter-bar">
<el-input v-model="detailLeaveQuery.applicantName" placeholder="请假人姓名" class="dialog-filter-input" />
<el-button type="primary" plain size="small" icon="el-icon-search" @click="getDetailLeaveList">搜索</el-button>
</div>
<el-table v-loading="detailLeaveLoading" :data="detailLeaveList" border stripe size="small">
<el-table-column prop="leaveType" label="请假类型" width="100" />
<el-table-column prop="startTime" label="开始时间" width="150" />
<el-table-column prop="endTime" label="结束时间" width="150" />
<el-table-column prop="leaveDays" label="小时" width="80" />
<el-table-column prop="leaveReason" label="原因" />
<el-table-column prop="approvalStatus" label="状态" width="100">
<template slot-scope="scope">
<span :class="getApprovalStatusClass(scope.row.approvalStatus)">{{ getApprovalStatusText(scope.row.approvalStatus) }}</span>
</template>
</el-table-column>
</el-table>
<el-divider content-position="left">外出记录</el-divider>
<div class="dialog-filter-bar">
<el-input v-model="detailOutQuery.applicantName" placeholder="外出人姓名" class="dialog-filter-input" />
<el-button type="primary" plain size="small" icon="el-icon-search" @click="getDetailOutList">搜索</el-button>
</div>
<el-table v-loading="detailOutLoading" :data="detailOutList" border stripe size="small">
<el-table-column prop="outType" label="外出类型" width="100" />
<el-table-column prop="startTime" label="开始时间" width="150" />
<el-table-column prop="endTime" label="结束时间" width="150" />
<el-table-column prop="outHours" label="时长(小时)" width="100" />
<el-table-column prop="outPlace" label="地点" />
<el-table-column prop="approvalStatus" label="状态" width="100">
<template slot-scope="scope">
<span :class="getApprovalStatusClass(scope.row.approvalStatus)">{{ getApprovalStatusText(scope.row.approvalStatus) }}</span>
</template>
</el-table-column>
</el-table>
<el-divider content-position="left">打卡记录</el-divider>
<el-table v-loading="detailRecordsLoading" :data="detailRecordsList" border stripe size="small">
<el-table-column prop="ename" label="姓名" width="100" />
<el-table-column prop="deptname" label="部门" />
<el-table-column prop="checktime" label="打卡时间">
<template slot-scope="scope">
<span>{{ scope.row.checktime }}</span>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button v-if="!isEdit" type="primary" @click="handleEdit">编辑</el-button>
<el-button v-if="isEdit" @click="cancelEdit">取消</el-button>
<el-button v-if="isEdit" type="primary" @click="submitEdit">保存</el-button>
<el-button @click="closeDetail">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listAttendanceCheck, getAttendanceCheck, updateAttendanceCheck } from "@/api/wms/attendanceCheck";
import { listOutRequest } from "@/api/wms/outRequest";
import { listLeaveRequest } from "@/api/wms/leaveRequest";
import { listRecords } from "@/api/wms/attendance";
import { listEmployeeInfo } from "@/api/wms/employeeInfo";
import TimeRangePicker from "@/views/wms/report/components/timeRangePicker";
export default {
name: "AttendanceAbnormal",
components: {
TimeRangePicker
},
data() {
return {
loading: false,
abnormalList: [],
dateRangeParams: {},
defaultStartTime: '',
defaultEndTime: '',
queryParams: {
employeeName: undefined,
startDate: undefined,
endDate: undefined,
overallStatus: undefined,
abnormal: true
},
allEmployees: [],
departmentList: [],
selectedDept: '',
currentDeptEmployeeIds: '',
detailDialogVisible: false,
currentDetail: null,
isEdit: false,
editForm: {},
detailLeaveLoading: false,
detailLeaveList: [],
detailLeaveQuery: {
applicantName: ''
},
detailOutLoading: false,
detailOutList: [],
detailOutQuery: {
applicantName: ''
},
detailRecordsLoading: false,
detailRecordsList: []
};
},
created() {
this.getAllEmployees().then(() => {
this.initDateRange();
});
},
computed: {
totalEmployeeCount() {
return this.allEmployees.length;
}
},
methods: {
initDateRange() {
const now = new Date()
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1)
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0)
this.defaultStartTime = this.formatDate(firstDay)
this.defaultEndTime = this.formatDate(lastDay)
this.dateRangeParams = {
startDate: this.defaultStartTime,
endDate: this.defaultEndTime
}
this.queryParams.startDate = this.defaultStartTime
this.queryParams.endDate = this.defaultEndTime
this.getList()
},
getAllEmployees() {
return listEmployeeInfo({
pageNum: 1,
pageSize: 10000
}).then(res => {
this.allEmployees = res.rows || []
const deptMap = {}
this.allEmployees.forEach(emp => {
const deptName = emp.dept || '未分配部门'
if (!deptMap[deptName]) {
deptMap[deptName] = new Set()
}
deptMap[deptName].add(String(emp.infoId))
})
this.departmentList = Object.keys(deptMap).map(deptName => {
const ids = [...deptMap[deptName]]
return {
deptName,
count: ids.length,
empIds: ids.join(',')
}
})
if (this.departmentList.length > 0) {
this.selectedDept = ''
this.currentDeptEmployeeIds = ''
}
})
},
handleDeptChange(deptName) {
if (!deptName) {
this.currentDeptEmployeeIds = ''
} else {
const dept = this.departmentList.find(d => d.deptName === deptName)
this.currentDeptEmployeeIds = dept ? dept.empIds : ''
}
this.getList()
},
formatDate(date) {
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
return `${year}-${month}-${day}`
},
handleDateRangeChange() {
if (this.dateRangeParams.startDate && this.dateRangeParams.endDate) {
this.queryParams.startDate = this.dateRangeParams.startDate
this.queryParams.endDate = this.dateRangeParams.endDate
this.getList()
}
},
getList() {
this.loading = true;
const params = { ...this.queryParams };
if (this.currentDeptEmployeeIds) {
params.userIds = this.currentDeptEmployeeIds.split(',');
}
listAttendanceCheck(params).then(response => {
this.abnormalList = response.rows || [];
this.loading = false;
}).catch(() => {
this.loading = false;
});
},
handleQuery() {
this.getList();
},
handleExport() {
const params = { ...this.queryParams, abnormal: true };
if (this.currentDeptEmployeeIds) {
params.userIds = this.currentDeptEmployeeIds.split(',');
}
this.download('wms/attendanceCheck/export', params, `attendanceAbnormal_${new Date().getTime()}.xlsx`);
},
handleUpdate(row) {
this.loading = true;
getAttendanceCheck(row.checkId).then(response => {
this.currentDetail = response.data;
this.detailLeaveQuery.applicantName = row.employeeName;
this.detailOutQuery.applicantName = row.employeeName;
this.getDetailLeaveList();
this.getDetailOutList();
this.getDetailRecords(row.employeeName, row.startDate);
this.detailDialogVisible = true;
this.loading = false;
}).catch(() => {
this.loading = false;
});
},
closeDetail() {
this.detailDialogVisible = false;
this.currentDetail = null;
this.isEdit = false;
},
handleEdit() {
this.isEdit = true;
this.editForm = { ...this.currentDetail };
this.initEditForm();
},
initEditForm() {
if (this.currentDetail.p1StartTime) {
this.editForm.p1StartTime = this.convertToTimeString(this.currentDetail.p1StartTime);
}
if (this.currentDetail.p1EndTime) {
this.editForm.p1EndTime = this.convertToTimeString(this.currentDetail.p1EndTime);
}
if (this.currentDetail.p1FirstCheck) {
this.editForm.p1FirstCheck = this.convertToDateTimeString(this.currentDetail.p1FirstCheck);
}
if (this.currentDetail.p1LastCheck) {
this.editForm.p1LastCheck = this.convertToDateTimeString(this.currentDetail.p1LastCheck);
}
if (this.currentDetail.p2StartTime) {
this.editForm.p2StartTime = this.convertToTimeString(this.currentDetail.p2StartTime);
}
if (this.currentDetail.p2EndTime) {
this.editForm.p2EndTime = this.convertToTimeString(this.currentDetail.p2EndTime);
}
if (this.currentDetail.p2FirstCheck) {
this.editForm.p2FirstCheck = this.convertToDateTimeString(this.currentDetail.p2FirstCheck);
}
if (this.currentDetail.p2LastCheck) {
this.editForm.p2LastCheck = this.convertToDateTimeString(this.currentDetail.p2LastCheck);
}
},
convertToTimeString(dateStr) {
if (!dateStr) return ''
if (dateStr instanceof Date) {
return dateStr.toTimeString().slice(0, 8)
}
const str = String(dateStr)
if (str.length >= 8) {
const parts = str.split(/[-T:\s]/)
if (parts.length >= 6) {
return `${parts[3]}:${parts[4]}:${parts[5]}`
} else if (str.includes(':')) {
const timePart = str.split(' ').pop()
return timePart.length >= 8 ? timePart : timePart.padEnd(8, ':00')
}
}
return str
},
convertToDateTimeString(dateStr) {
if (!dateStr) return ''
if (dateStr instanceof Date) {
const d = dateStr
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hours = String(d.getHours()).padStart(2, '0')
const minutes = String(d.getMinutes()).padStart(2, '0')
const seconds = String(d.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
const str = String(dateStr)
if (str.includes('T')) {
return str.replace('T', ' ')
}
return str
},
cancelEdit() {
this.isEdit = false;
this.editForm = {};
},
submitEdit() {
this.loading = true;
updateAttendanceCheck(this.editForm).then(response => {
this.$modal.msgSuccess("修改成功");
this.isEdit = false;
this.editForm = {};
this.getList();
this.closeDetail();
}).finally(() => {
this.loading = false;
});
},
formatTime(time) {
if (!time) return '-'
return time
},
getDetailLeaveList() {
this.detailLeaveLoading = true;
const query = {
applicantName: this.detailLeaveQuery.applicantName,
startTime: this.queryParams.startDate,
endTime: this.queryParams.endDate
};
listLeaveRequest(query).then(response => {
this.detailLeaveList = response.rows || response.data || [];
this.detailLeaveLoading = false;
}).catch(() => {
this.detailLeaveLoading = false;
});
},
getDetailOutList() {
this.detailOutLoading = true;
const query = {
applicantName: this.detailOutQuery.applicantName,
startTime: this.queryParams.startDate,
endTime: this.queryParams.endDate
};
listOutRequest(query).then(response => {
this.detailOutList = response.rows || response.data || [];
this.detailOutLoading = false;
}).catch(() => {
this.detailOutLoading = false;
});
},
getDetailRecords(ename, date) {
this.detailRecordsLoading = true;
let dateStr = '';
if (date) {
if (date instanceof Date) {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
dateStr = `${y}-${m}-${d}`;
} else if (typeof date === 'string') {
dateStr = date.split(' ')[0];
}
}
if (!dateStr) {
dateStr = this.queryParams.startDate || '';
}
if (!dateStr) {
this.detailRecordsLoading = false;
return;
}
listRecords({
pageNum: 1,
pageSize: 100,
ename,
checktimeStart: dateStr + ' 00:00:00',
checktimeEnd: dateStr + ' 23:59:59'
}).then(response => {
this.detailRecordsList = response.rows || [];
this.detailRecordsLoading = false;
}).catch(() => {
this.detailRecordsLoading = false;
});
},
getApprovalStatusClass(status) {
switch (status) {
case '待审批': return 'approval-pending'
case '已同意': return 'approval-approved'
case '已驳回': return 'approval-rejected'
case '已撤销': return 'approval-canceled'
default: return 'approval-default'
}
},
getApprovalStatusText(status) {
switch (status) {
case '待审批': return '待审批'
case '已同意': return '已同意'
case '已驳回': return '已驳回'
case '已撤销': return '已撤销'
default: return status || '-'
}
},
getStatusClass(status) {
switch (status) {
case 'normal': return 'status-normal'
case 'late_warn':
case 'late_one':
case 'late_two': return 'status-late'
case 'early_warn':
case 'early_one':
case 'early_two': return 'status-early'
case 'absent_half': return 'status-absent-half'
case 'absent_full': return 'status-absent-full'
case 'abnormal': return 'status-abnormal'
case 'missed': return 'status-missed'
case 'missed_start': return 'status-missed'
case 'missed_end': return 'status-missed'
default: return 'status-default'
}
},
getStatusText(status) {
switch (status) {
case 'normal': return '正常'
case 'late_warn': return '迟到预警'
case 'late_one': return '迟到I'
case 'late_two': return '迟到II'
case 'early_warn': return '早退预警'
case 'early_one': return '早退I'
case 'early_two': return '早退II'
case 'absent_half': return '半天旷工'
case 'absent_full': return '全天旷工'
case 'abnormal': return '迟到/早退/缺卡'
case 'missed': return '未打卡'
case 'missed_start': return '上班漏打卡'
case 'missed_end': return '下班漏打卡'
default: return '-'
}
}
}
};
</script>
<style scoped>
.app-container {
padding: 20px;
}
.date-range-section {
margin-bottom: 20px;
}
.operation-bar {
margin-bottom: 20px;
}
.filter-row {
margin-bottom: 15px;
display: flex;
gap: 10px;
}
.filter-input {
width: 200px;
}
.filter-select {
width: 180px;
}
.dept-filter-section {
margin-bottom: 20px;
display: flex;
align-items: center;
flex-wrap: wrap;
}
.late-info {
color: #e6a23c;
font-weight: bold;
}
.early-info {
color: #f56c6c;
font-weight: bold;
}
.deduct-amount {
color: #f56c6c;
font-weight: bold;
}
.status-normal {
background-color: #67c23a;
color: #fff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-late {
background-color: #e6a23c;
color: #fff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-early {
background-color: #f56c6c;
color: #fff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-absent-half {
background-color: #909399;
color: #fff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-absent-full {
background-color: #606266;
color: #fff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-abnormal {
background-color: #f56c6c;
color: #fff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-missed {
background-color: #e6a23c;
color: #fff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-default {
background-color: #c0c4cc;
color: #fff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.dialog-filter-bar {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.dialog-filter-input {
width: 150px;
margin-right: 10px;
}
.approval-pending {
color: #e6a23c;
}
.approval-approved {
color: #67c23a;
}
.approval-rejected {
color: #f56c6c;
}
.approval-canceled {
color: #909399;
}
.approval-default {
color: #606266;
}
</style>