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>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-descriptions :column="3" title="财务状态" border>
|
<el-descriptions :column="3" title="财务状态" border>
|
||||||
<el-descriptions-item label="订单总金额">{{ order.orderAmount }}万元</el-descriptions-item>
|
<el-descriptions-item label="订单总金额">{{ order.orderAmount }}元</el-descriptions-item>
|
||||||
<el-descriptions-item label="已收款金额">{{ receivedAmount }}万元</el-descriptions-item>
|
<el-descriptions-item label="已收款金额">{{ receivedAmount }}元</el-descriptions-item>
|
||||||
<el-descriptions-item label="未收款金额">{{ unreceivedAmount }}万元</el-descriptions-item>
|
<el-descriptions-item label="未收款金额">{{ unreceivedAmount }}元</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
<el-descriptions title="收款明细"></el-descriptions>
|
<el-descriptions title="收款明细"></el-descriptions>
|
||||||
@@ -137,8 +137,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listReceivable, getReceivable, delReceivable, addReceivable, updateReceivable, updatePaidAmount } from "@/api/finance/receivable";
|
import { listReceivable, getReceivable, delReceivable, addReceivable, updateReceivable, updatePaidAmount } from "@/api/finance/receivable"
|
||||||
import { updateOrder } from "@/api/crm/order";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Receivable",
|
name: "Receivable",
|
||||||
|
|||||||
@@ -242,12 +242,12 @@ export default {
|
|||||||
Object.assign(item, calculateProductFields(item, 'quantity'));
|
Object.assign(item, calculateProductFields(item, 'quantity'));
|
||||||
});
|
});
|
||||||
this.products = products;
|
this.products = products;
|
||||||
this.remark = data.remark || '';
|
this.remark = data.remark || '净边料/毛边料、简包/裸包、卷重结算';
|
||||||
this.productName = data.productName || '';
|
this.productName = data.productName || '';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('解析content失败:', error);
|
console.error('解析content失败:', error);
|
||||||
this.products = [{}];
|
this.products = [{}];
|
||||||
this.remark = '';
|
this.remark = '净边料/毛边料、简包/裸包、卷重结算';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 数量变更
|
// 数量变更
|
||||||
|
|||||||
@@ -89,6 +89,9 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-checkbox v-model="autoFillOrderAmount">含税总额变化后自动填写订单总金额</el-checkbox>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="产品内容">
|
<el-form-item label="产品内容">
|
||||||
<ProductContent v-model="form.productContent" :readonly="false" />
|
<ProductContent v-model="form.productContent" :readonly="false" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -233,6 +236,8 @@ export default {
|
|||||||
},
|
},
|
||||||
// 表单参数
|
// 表单参数
|
||||||
form: {},
|
form: {},
|
||||||
|
// 是否自动将含税总额填入订单总金额
|
||||||
|
autoFillOrderAmount: true,
|
||||||
// 导出预览
|
// 导出预览
|
||||||
exportDialogVisible: false,
|
exportDialogVisible: false,
|
||||||
exportRow: null,
|
exportRow: null,
|
||||||
@@ -314,6 +319,19 @@ export default {
|
|||||||
created() {
|
created() {
|
||||||
this.getDictList();
|
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: {
|
methods: {
|
||||||
/** 处理客户选择 */
|
/** 处理客户选择 */
|
||||||
handleCustomerChange(customer) {
|
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