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:
@@ -1,9 +0,0 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
scene: git_message
|
||||
---
|
||||
|
||||
使用中文编写提交信息, 提交信息格式为:
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
```
|
||||
@@ -1,163 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="order-analysis-dashboard"
|
||||
v-loading="loading"
|
||||
>
|
||||
<!-- 业绩区 -->
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="业绩总览" name="performance">
|
||||
<PerformanceArea mode="mini" :performance-area="dashboardData.performanceArea" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PerformanceArea from '@/views/wms/order/components/PerformanceArea.vue'
|
||||
import CurrentSituationArea from '@/views/wms/order/components/CurrentSituationArea.vue'
|
||||
import RecommendationArea from '@/views/wms/order/components/RecommendationArea.vue'
|
||||
import { getDashboardData } from '@/api/wms/order'
|
||||
|
||||
export default {
|
||||
name: 'OrderAnalysisDashboard',
|
||||
components: {
|
||||
PerformanceArea,
|
||||
CurrentSituationArea,
|
||||
RecommendationArea,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 新的数据结构
|
||||
dashboardData: {
|
||||
performanceArea: {},
|
||||
currentSituationArea: {},
|
||||
recommendationArea: {}
|
||||
},
|
||||
// 新增定时刷新相关数据
|
||||
drawerVisible: false,
|
||||
autoRefresh: false,
|
||||
refreshInterval: 30, // 默认30秒
|
||||
refreshTimer: null,
|
||||
loading: false,
|
||||
activeTab: 'performance'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchAllData()
|
||||
this.loadRefreshSetting()
|
||||
this.startAutoRefresh()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearAutoRefresh()
|
||||
},
|
||||
methods: {
|
||||
async fetchAllData() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await getDashboardData()
|
||||
const data = res
|
||||
|
||||
// 更新新的数据结构
|
||||
this.dashboardData = {
|
||||
performanceArea: data.performanceArea || {},
|
||||
currentSituationArea: data.currentSituationArea || {},
|
||||
recommendationArea: data.recommendationArea || {}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取数据看板数据失败:', error)
|
||||
this.$message.error('获取数据失败,请稍后重试')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
handleRefresh() {
|
||||
this.fetchAllData()
|
||||
},
|
||||
// 定时刷新相关
|
||||
startAutoRefresh() {
|
||||
this.clearAutoRefresh()
|
||||
if (this.autoRefresh) {
|
||||
this.refreshTimer = setInterval(() => {
|
||||
this.fetchAllData()
|
||||
}, this.refreshInterval * 1000)
|
||||
}
|
||||
},
|
||||
clearAutoRefresh() {
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer)
|
||||
this.refreshTimer = null
|
||||
}
|
||||
},
|
||||
saveRefreshSetting() {
|
||||
// 可持久化到localStorage
|
||||
localStorage.setItem('orderDashboardAutoRefresh', JSON.stringify({
|
||||
autoRefresh: this.autoRefresh,
|
||||
refreshInterval: this.refreshInterval
|
||||
}))
|
||||
this.drawerVisible = false
|
||||
this.startAutoRefresh()
|
||||
},
|
||||
loadRefreshSetting() {
|
||||
const setting = localStorage.getItem('orderDashboardAutoRefresh')
|
||||
if (setting) {
|
||||
const { autoRefresh, refreshInterval } = JSON.parse(setting)
|
||||
this.autoRefresh = autoRefresh
|
||||
this.refreshInterval = refreshInterval
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
autoRefresh(val) {
|
||||
if (!val) {
|
||||
this.clearAutoRefresh()
|
||||
}
|
||||
},
|
||||
refreshInterval(val) {
|
||||
if (this.autoRefresh) {
|
||||
this.startAutoRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.order-analysis-dashboard {
|
||||
padding: 24px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.section-row {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-bottom: 20px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.section-title h2 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.section-title p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.top-row,
|
||||
.chart-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-row > .el-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-descriptions :column="3" title="财务状态" border>
|
||||
<el-descriptions-item label="订单总金额">{{ order.orderAmount }}万元</el-descriptions-item>
|
||||
<el-descriptions-item label="已收款金额">{{ receivedAmount }}万元</el-descriptions-item>
|
||||
<el-descriptions-item label="未收款金额">{{ unreceivedAmount }}万元</el-descriptions-item>
|
||||
<el-descriptions-item label="订单总金额">{{ order.orderAmount }}元</el-descriptions-item>
|
||||
<el-descriptions-item label="已收款金额">{{ receivedAmount }}元</el-descriptions-item>
|
||||
<el-descriptions-item label="未收款金额">{{ unreceivedAmount }}元</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-descriptions title="收款明细"></el-descriptions>
|
||||
@@ -137,8 +137,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listReceivable, getReceivable, delReceivable, addReceivable, updateReceivable, updatePaidAmount } from "@/api/finance/receivable";
|
||||
import { updateOrder } from "@/api/crm/order";
|
||||
import { listReceivable, getReceivable, delReceivable, addReceivable, updateReceivable, updatePaidAmount } from "@/api/finance/receivable"
|
||||
|
||||
export default {
|
||||
name: "Receivable",
|
||||
|
||||
@@ -242,12 +242,12 @@ export default {
|
||||
Object.assign(item, calculateProductFields(item, 'quantity'));
|
||||
});
|
||||
this.products = products;
|
||||
this.remark = data.remark || '';
|
||||
this.remark = data.remark || '净边料/毛边料、简包/裸包、卷重结算';
|
||||
this.productName = data.productName || '';
|
||||
} catch (error) {
|
||||
console.error('解析content失败:', error);
|
||||
this.products = [{}];
|
||||
this.remark = '';
|
||||
this.remark = '净边料/毛边料、简包/裸包、卷重结算';
|
||||
}
|
||||
},
|
||||
// 数量变更
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="autoFillOrderAmount">含税总额变化后自动填写订单总金额</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品内容">
|
||||
<ProductContent v-model="form.productContent" :readonly="false" />
|
||||
</el-form-item>
|
||||
@@ -233,6 +236,8 @@ export default {
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 是否自动将含税总额填入订单总金额
|
||||
autoFillOrderAmount: true,
|
||||
// 导出预览
|
||||
exportDialogVisible: false,
|
||||
exportRow: null,
|
||||
@@ -314,6 +319,19 @@ export default {
|
||||
created() {
|
||||
this.getDictList();
|
||||
},
|
||||
watch: {
|
||||
'form.productContent': function (newVal) {
|
||||
if (!this.autoFillOrderAmount || !newVal) return;
|
||||
try {
|
||||
const data = JSON.parse(newVal);
|
||||
if (data.totalTaxTotal != null) {
|
||||
this.form.orderAmount = data.totalTaxTotal;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore parse errors
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 处理客户选择 */
|
||||
handleCustomerChange(customer) {
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
<template>
|
||||
<div style="padding: 20px;">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<div>
|
||||
<el-row>
|
||||
<el-input v-model="queryParams.orderCode" placeholder="请输入单据编号" clearable
|
||||
@keyup.enter.native="handleQuery" />
|
||||
</el-row>
|
||||
|
||||
<klp-list :list-data="orderList" list-key="orderId" :loading="loading" @item-click="handleItemClick"
|
||||
info1-field="orderCode" info1-max-percent="40" info5-field="createTime" info4-field="taxAmount">
|
||||
<!-- info4 插槽:Vue2 用 slot 指定插槽名,slot-scope 接收作用域变量 -->
|
||||
<template slot="info4" slot-scope="{ item }">
|
||||
<span class="info-value info-value--primary">
|
||||
{{ item.taxAmount }}元(含税)
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<!-- info1 插槽:同理修改插槽语法 -->
|
||||
<template slot="info1" slot-scope="{ item }">
|
||||
<span class="info-value info-value--primary">
|
||||
{{ item.salesManager }}【{{ item.orderCode }}】
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<!-- info2 插槽:dict-tag 若为 Vue2 兼容组件,用法不变 -->
|
||||
<template slot="info2" slot-scope="{ item }">
|
||||
<dict-tag :options="dict.type.order_status" :value="item.orderStatus" />
|
||||
</template>
|
||||
|
||||
<!-- actions 插槽:el-button 为 Element UI Vue2 组件,用法不变 -->
|
||||
<template slot="actions" slot-scope="{ item }">
|
||||
<el-button size="small" type="text" style="color: red" icon="el-icon-delete"
|
||||
@click.stop="handleDelete(item)"></el-button>
|
||||
</template>
|
||||
</klp-list>
|
||||
|
||||
<pagination :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" :total="total"
|
||||
layout="prev, pager, next" @pagination="getList" />
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="18" v-loading="rightLoading">
|
||||
<el-tabs v-if="currentOrder" v-model="activeTab">
|
||||
<el-tab-pane label="订单信息" name="orderDetail">
|
||||
<el-form>
|
||||
<el-form-item label="订单ID">
|
||||
<el-input v-model="currentOrder.orderId" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="订单编号">
|
||||
<el-input v-model="currentOrder.orderCode" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="销售经理">
|
||||
<el-input v-model="currentOrder.salesManager" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="客户">
|
||||
<el-input v-model="currentOrder.customerName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="含税金额">
|
||||
<el-input v-model="currentOrder.taxAmount" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="无税金额">
|
||||
<el-input v-model="currentOrder.noTaxAmount" disabled />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="订单明细" name="orderList">
|
||||
<order-detail-list :orderId="currentOrder.orderId" :editable="false" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="应收明细" name="receivable">
|
||||
<div style="margin-top: 10px;">
|
||||
<ReceiveTable :order-id="currentOrder.orderId" :searchable="false" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="应付明细" name="payable">
|
||||
<div style="margin-top: 10px;">
|
||||
<PayTable :order-id="currentOrder.orderId" :searchable="false" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="凭证管理" name="document">
|
||||
<KLPTable :data="currentOrder.documents" style="width: 100%" empty-text="暂无数据">
|
||||
<el-table-column prop="docNo" label="凭证编号" />
|
||||
<el-table-column prop="docDate" label="凭证日期" />
|
||||
<el-table-column prop="amount" label="凭证金额" />
|
||||
<el-table-column prop="status" label="凭证状态" />
|
||||
</KLPTable>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<el-empty v-else description="请选择订单" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listOrder } from "@/api/wms/order";
|
||||
import { listOrderDetail } from "@/api/wms/orderDetail";
|
||||
import { listReceivable } from "@/api/finance/receivable";
|
||||
import { listPayable } from "@/api/finance/payable";
|
||||
import { listFinancialDocument } from "@/api/finance/financialDocument";
|
||||
|
||||
import OrderDetailList from '@/views/wms/order/panels/detail.vue'
|
||||
import klpList from "@/components/KLPUI/KLPList/index.vue"; // 引入klp-list组件
|
||||
import ReceiveTable from '../components/ReceiveTable.vue';
|
||||
import PayTable from '../components/PayTable.vue';
|
||||
|
||||
export default {
|
||||
name: "Order",
|
||||
components: {
|
||||
OrderDetailList,
|
||||
klpList, // 注册klp-list组件
|
||||
ReceiveTable,
|
||||
PayTable
|
||||
},
|
||||
dicts: ['order_status'],
|
||||
data() {
|
||||
return {
|
||||
activeTab: "orderDetail",
|
||||
currentOrder: null,
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
orderCode: undefined
|
||||
},
|
||||
orderListLoading: false,
|
||||
orderList: [], // 用于klp-list的列表数据
|
||||
total: 0,
|
||||
rightLoading: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
getList() {
|
||||
this.orderListLoading = true;
|
||||
listOrder(this.queryParams).then(response => {
|
||||
this.orderList = response.rows; // 直接使用原始数据
|
||||
this.total = response.total;
|
||||
}).finally(() => {
|
||||
this.orderListLoading = false;
|
||||
});
|
||||
},
|
||||
// 处理列表项点击事件
|
||||
handleItemClick(selectedItem) {
|
||||
if (!selectedItem) {
|
||||
this.currentOrder = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.rightLoading) {
|
||||
this.$message.warning('请等待当前订单加载完成');
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentOrder = selectedItem;
|
||||
this.fetchData(selectedItem.orderId);
|
||||
},
|
||||
// 查看详情按钮点击事件
|
||||
handleViewDetail(item) {
|
||||
this.currentOrder = item;
|
||||
this.fetchData(item.orderId);
|
||||
},
|
||||
async fetchData(orderId) {
|
||||
this.rightLoading = true;
|
||||
try {
|
||||
// 并行请求提高性能
|
||||
const [
|
||||
orderDetailRes,
|
||||
receivableRes,
|
||||
payableRes,
|
||||
documentRes
|
||||
] = await Promise.all([
|
||||
listOrderDetail({ orderId, pageSize: 1000 }),
|
||||
listReceivable({ orderId, pageSize: 1000 }),
|
||||
listPayable({ orderId, pageSize: 1000 }),
|
||||
listFinancialDocument({ orderId, pageSize: 1000 })
|
||||
]);
|
||||
|
||||
this.currentOrder = {
|
||||
...this.currentOrder,
|
||||
details: orderDetailRes.rows,
|
||||
receivables: receivableRes.rows,
|
||||
payables: payableRes.rows,
|
||||
documents: documentRes.rows
|
||||
};
|
||||
} catch (error) {
|
||||
this.$message.error('数据加载失败,请重试');
|
||||
console.error(error);
|
||||
} finally {
|
||||
this.rightLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
877
klp-ui/src/views/wms/hrm/attendance/attendanceAbnormal.vue
Normal file
877
klp-ui/src/views/wms/hrm/attendance/attendanceAbnormal.vue
Normal 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>
|
||||
Reference in New Issue
Block a user