数据建设页面开发
This commit is contained in:
@@ -1,12 +1,207 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="stats-view">
|
<div class="stats-view">
|
||||||
<el-card>
|
<el-card class="box-card" shadow="never">
|
||||||
<template #header>
|
<template #header>
|
||||||
<h2>数据统计</h2>
|
<div class="card-header">
|
||||||
|
<span>员工锁定站点统计</span>
|
||||||
|
<div class="header-actions">
|
||||||
|
<el-text size="small" type="info" style="margin-right: 12px">只看本月</el-text>
|
||||||
|
<el-switch v-model="showOnlyThisMonth" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<p>数据统计页面正在建设中...</p>
|
<div v-loading="loading">
|
||||||
|
<el-empty v-if="!staffWithLockedTickets.length && !loading" description="暂无已锁定的站点数据" />
|
||||||
|
<el-collapse v-else v-model="activeCollapse" accordion>
|
||||||
|
<el-collapse-item v-for="staff in staffWithLockedTickets" :key="staff.id" :name="staff.id">
|
||||||
|
<template #title>
|
||||||
|
<div class="collapse-title" @mousedown.prevent>
|
||||||
|
<div class="staff-info">
|
||||||
|
<el-avatar :size="28">{{ staff.name.slice(0, 1) }}</el-avatar>
|
||||||
|
<span class="staff-name">{{ staff.name }}</span>
|
||||||
|
</div>
|
||||||
|
<el-tag type="success" effect="dark" round>
|
||||||
|
已锁定 {{ staff.lockedTickets.length }} 个站点
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-table :data="staff.lockedTickets" stripe border size="small" class="inner-table">
|
||||||
|
<el-table-column prop="id" label="工单ID" width="80" align="center" />
|
||||||
|
<el-table-column prop="website" label="站点名称" min-width="200" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="operator" label="运营" width="120" align="center" />
|
||||||
|
<el-table-column prop="datanum" label="数据量" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span>{{
|
||||||
|
row.datanum && row.datanum !== "0" ? row.datanum : "-"
|
||||||
|
}}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="锁定日期" width="150" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.lock_time ? row.lock_time.slice(0, 10) : "-" }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="is_complete" label="状态" width="100" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.is_complete === '已完成' ? 'success' : 'info'" size="small">{{ row.is_complete
|
||||||
|
}}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup></script>
|
<script setup>
|
||||||
|
import { ref, onMounted, computed } from "vue";
|
||||||
|
import { ticketApi } from "@/api/ticket";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
|
const loading = ref(true);
|
||||||
|
const staffList = ref([]);
|
||||||
|
const allTickets = ref([]);
|
||||||
|
const activeCollapse = ref("");
|
||||||
|
const showOnlyThisMonth = ref(false);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const [staffRes, ticketsRes] = await Promise.all([
|
||||||
|
ticketApi.getStaffs(),
|
||||||
|
ticketApi.getTickets({ page: 1, limit: 9999 }), // 获取所有工单
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (staffRes.code) {
|
||||||
|
staffList.value = staffRes.data;
|
||||||
|
} else {
|
||||||
|
ElMessage.error("获取员工列表失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticketsRes.code) {
|
||||||
|
allTickets.value = ticketsRes.data.data;
|
||||||
|
} else {
|
||||||
|
ElMessage.error("获取工单列表失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("加载数据失败:", error);
|
||||||
|
ElMessage.error("加载数据失败");
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const staffWithLockedTickets = computed(() => {
|
||||||
|
if (!staffList.value.length || !allTickets.value.length) return [];
|
||||||
|
|
||||||
|
// 筛选出所有已锁定(已完成/已完结)且具有有效锁定时间的工单
|
||||||
|
let lockedTickets = allTickets.value.filter((ticket) => {
|
||||||
|
const isLockedStatus = ["已完成", "已完结"].includes(ticket.is_complete);
|
||||||
|
// lock_time 是 "YYYY-MM-DD HH:mm" 格式的字符串,只需判断其是否存在
|
||||||
|
return isLockedStatus && ticket.staff_id && ticket.lock_time;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果开启了“只看本月”,则进一步筛选
|
||||||
|
if (showOnlyThisMonth.value) {
|
||||||
|
const now = new Date();
|
||||||
|
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
|
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59);
|
||||||
|
|
||||||
|
lockedTickets = lockedTickets.filter((ticket) => {
|
||||||
|
// 直接用日期字符串创建 Date 对象进行比较
|
||||||
|
const lockDate = new Date(ticket.lock_time);
|
||||||
|
return (
|
||||||
|
!isNaN(lockDate.getTime()) && lockDate >= startOfMonth && lockDate <= endOfMonth
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一个员工的Map,方便快速查找
|
||||||
|
const staffMap = new Map(
|
||||||
|
staffList.value.map((staff) => [String(staff.id), { ...staff, lockedTickets: [] }])
|
||||||
|
);
|
||||||
|
|
||||||
|
// 将锁定的工单分配到对应的员工下
|
||||||
|
lockedTickets.forEach((ticket) => {
|
||||||
|
const staffId = String(ticket.staff_id);
|
||||||
|
if (staffMap.has(staffId)) {
|
||||||
|
staffMap.get(staffId).lockedTickets.push(ticket);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 对每个员工的工单按锁定时间倒序排序
|
||||||
|
staffMap.forEach((staff) => {
|
||||||
|
staff.lockedTickets.sort((a, b) => {
|
||||||
|
// 使用 new Date().getTime() 来比较日期字符串
|
||||||
|
return new Date(b.lock_time).getTime() - new Date(a.lock_time).getTime();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 返回一个数组,只包含有锁定工单的员工,并按工单数量排序
|
||||||
|
return Array.from(staffMap.values())
|
||||||
|
.filter((staff) => staff.lockedTickets.length > 0)
|
||||||
|
.sort((a, b) => b.lockedTickets.length - a.lockedTickets.length);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.stats-view {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-card {
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header>span {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.staff-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.staff-name {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-table {
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
margin: 0 16px 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-collapse-item__header) {
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-collapse-item__content) {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user