407 lines
11 KiB
Vue
407 lines
11 KiB
Vue
<template>
|
||
<div class="h-full flex flex-col p-2">
|
||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||
<div v-show="showSearch" class="mb-[10px]">
|
||
<el-card shadow="hover">
|
||
<div>
|
||
<div class="flex flex-items-center justify-between">
|
||
<div>
|
||
<div class="line"></div>
|
||
<div class="metatitle">巡更数据概况</div>
|
||
</div>
|
||
<div class="rightSearch flex flex-items-center justify-around">
|
||
<div>选择日期</div>
|
||
<el-date-picker
|
||
v-model="statDate"
|
||
:disabled-date="disabledDate"
|
||
format="YYYY/MM/DD"
|
||
placeholder="选择日期"
|
||
type="date"
|
||
value-format="YYYY/MM/DD"
|
||
@update:model-value="handleStateDate"
|
||
/>
|
||
<i @click="handleQueryDay(0)">今日</i>
|
||
<i @click="handleQueryDay(1)">昨日</i>
|
||
<i @click="handleQueryDay(7)">近7天</i>
|
||
<i @click="handleQueryDay(30)">近30日</i>
|
||
</div>
|
||
</div>
|
||
<div class="overviewBox">
|
||
<div v-for="(item, index) in list" :key="index" class="overviewitem">
|
||
<div class="overviewitem_name">{{ item.name }}</div>
|
||
<div class="overviewitem_value">{{ item.value }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
</transition>
|
||
|
||
<el-card class="flex-1" shadow="never">
|
||
<template #header>
|
||
<span style="font-size: 1.1rem">数据统计 </span>
|
||
</template>
|
||
<el-row :gutter="40" class="h-full">
|
||
<el-col :span="11">
|
||
<div class="echartsbox flex h-full flex-col">
|
||
<div class="echartsactions">
|
||
<div
|
||
v-for="(item, index) in ecartslist"
|
||
:key="index"
|
||
:class="{ active: activeEchartsindex === item.key }"
|
||
class="echartsactions_item"
|
||
@click="handleEchartsData(item)"
|
||
>
|
||
{{ item.name }}
|
||
</div>
|
||
</div>
|
||
<div class="echartsbody flex-1">
|
||
<EchartsCompontent :options="options"></EchartsCompontent>
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
<el-col :span="1"></el-col>
|
||
<el-col :span="12">
|
||
<el-table v-loading="loading" :data="itemList" border>
|
||
<el-table-column align="center" fixed="left" label="日期" prop="statDate" width="100" />
|
||
<el-table-column align="center" label="计划巡检任务" prop="totalTasks" width="110" />
|
||
<el-table-column align="center" label="按时完成" prop="completedTasks" />
|
||
<el-table-column align="center" label="超时完成" prop="outCompletedTasks" />
|
||
<el-table-column align="center" label="超时未完成" prop="outUncompletedTasks" width="110" />
|
||
<el-table-column align="center" label="进行中" prop="activeTask" />
|
||
<el-table-column align="center" label="未开始" prop="pendingTask" />
|
||
</el-table>
|
||
<pagination
|
||
v-show="total > 0"
|
||
v-model:limit="queryParams.pageSize"
|
||
v-model:page="queryParams.pageNum"
|
||
:layout="layout"
|
||
:total="total"
|
||
@pagination="getList"
|
||
/>
|
||
</el-col>
|
||
</el-row>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" name="Item" setup>
|
||
import EchartsCompontent from '@/components/Echarts/index.vue';
|
||
import { listItem, type Request } from '@/api/system/Inspection';
|
||
import { diffDays, formatDate, getLastNDays, getNaturalDayDiff } from '@/utils/time';
|
||
import { EChartsOption } from 'echarts';
|
||
|
||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||
|
||
const itemList = ref<any[]>([]);
|
||
const loading = ref(true);
|
||
const showSearch = ref(true);
|
||
const total = ref(100);
|
||
|
||
const layout = document.body.clientWidth <= 1500 ? 'total, sizes, prev, pager, next' : 'total, sizes, prev, pager, next,';
|
||
const disabledDate = (time: Date) => {
|
||
return time.getTime() > Date.now();
|
||
};
|
||
const statDate = ref('');
|
||
const data = reactive<{
|
||
queryParams: Request;
|
||
}>({
|
||
queryParams: {
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
statDate: '',
|
||
dataType: '0' // 0 总览 1 日报 3 月报
|
||
}
|
||
});
|
||
|
||
const list = ref([
|
||
{
|
||
name: '计划数量',
|
||
key: 'totalPlans',
|
||
value: 0
|
||
},
|
||
{
|
||
name: '任务数量',
|
||
key: 'totalTasks',
|
||
value: 0
|
||
},
|
||
{
|
||
name: '任务完成数量',
|
||
key: 'completedTasks',
|
||
value: 0
|
||
},
|
||
{
|
||
name: ' 完成率',
|
||
key: 'onTimeRateDisplay',
|
||
value: '20%'
|
||
},
|
||
{
|
||
name: '未完成数量',
|
||
key: 'outUncompletedTasks',
|
||
value: 0
|
||
}
|
||
]);
|
||
|
||
// ! echarts =================================================================
|
||
const activeEchartsindex = ref('totalTasks');
|
||
// echarts tabs
|
||
const ecartslist = ref([
|
||
{
|
||
name: '全部任务',
|
||
key: 'totalTasks',
|
||
color: '#1a75ff',
|
||
offsetbottomColor: '#f2f9ff'
|
||
},
|
||
{
|
||
name: '按时完成',
|
||
key: 'completedTasks',
|
||
color: '#42b983',
|
||
offsetbottomColor: '#c6eada'
|
||
},
|
||
{
|
||
name: '超时未完成',
|
||
key: 'outUncompletedTasks',
|
||
color: '#f83333',
|
||
offsetbottomColor: '#fed6d6'
|
||
},
|
||
{
|
||
name: '进行中',
|
||
key: 'activeTask',
|
||
color: '#9d33f8',
|
||
offsetbottomColor: '#ebd6fe'
|
||
},
|
||
{
|
||
name: '未开始',
|
||
key: 'pendingTask',
|
||
color: '#909399',
|
||
offsetbottomColor: 'rgb(233, 233, 235)'
|
||
}
|
||
]);
|
||
|
||
// echarts options
|
||
const options = computed<EChartsOption>(() => ({
|
||
grid: {
|
||
left: '1%',
|
||
right: '1%',
|
||
top: '10%',
|
||
bottom: '1%',
|
||
containLabel: true
|
||
},
|
||
xAxis: { data: xAxislist.value },
|
||
yAxis: { type: 'value' as const },
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
axisPointer: {
|
||
type: 'shadow',
|
||
shadowStyle: {
|
||
color: '#f6f8fc',
|
||
opacity: 0.3
|
||
}
|
||
}
|
||
},
|
||
series: activeSeries.value // 动态系列
|
||
}));
|
||
//
|
||
const diffDay = ref(0);
|
||
const activeSeries = ref();
|
||
const xAxislist = ref([]);
|
||
|
||
// join Echarts Options object
|
||
function handleEchartsOptions(data: number[], name: string, colorList: string[] = ['#1a75ff', '#f2f9ff']) {
|
||
activeSeries.value = null;
|
||
activeSeries.value = {
|
||
data: data,
|
||
type: 'line' as const,
|
||
smooth: true,
|
||
name: name,
|
||
areaStyle: {
|
||
color: {
|
||
type: 'linear',
|
||
x: 0,
|
||
y: 0,
|
||
x2: 0,
|
||
y2: 1, // 从上到下渐变
|
||
colorStops: colorList.map((item, index) => {
|
||
return {
|
||
offset: index / colorList.length,
|
||
color: item
|
||
};
|
||
})
|
||
}
|
||
},
|
||
lineStyle: {
|
||
color: colorList[0]
|
||
},
|
||
itemStyle: {
|
||
color: colorList[0]
|
||
},
|
||
label: {
|
||
show: true,
|
||
color: colorList[0],
|
||
position: 'top'
|
||
}
|
||
};
|
||
}
|
||
|
||
// echarts tabs switch
|
||
function handleEchartsData(row: (typeof ecartslist.value)[number]) {
|
||
activeEchartsindex.value = row.key;
|
||
if (row) {
|
||
xAxislist.value = [];
|
||
// 避免修改原数组,创建副本排序
|
||
const sortedList = [...itemList.value].sort((a, b) => new Date(a.statDate).getTime() - new Date(b.statDate).getTime());
|
||
// 创建日期到值的映射,标准化日期格式(假设 statDate 为 '2024-04-16 00:00:00',截取前10位)
|
||
const dataMap = new Map<string, number>();
|
||
sortedList.forEach((item) => {
|
||
const dateKey = item.statDate.split(' ')[0]; // 截取 'YYYY-MM-DD'
|
||
dataMap.set(dateKey, Number(item[row.key]) || 0); // 确保为数字
|
||
});
|
||
|
||
// 根据 x 轴日期生成数据数组
|
||
const xAxisDates = getLastNDays(diffDay.value, undefined, 'YYYY/MM/DD'); // 确保格式为 ['2024-04-16', ...]
|
||
const arr = xAxisDates.map((date) => dataMap.get(date) || 0);
|
||
xAxislist.value = xAxisDates;
|
||
handleEchartsOptions(arr, row.name, [row.color]);
|
||
}
|
||
}
|
||
|
||
// 选择日期
|
||
function handleStateDate(val: string) {
|
||
diffDay.value = diffDays(val);
|
||
queryParams.value.statDate = `${formatDate(new Date(val), 'YYYY/MM/DD')}-${formatDate(new Date(), 'YYYY/MM/DD')}`;
|
||
getList();
|
||
}
|
||
// ! echarts =================================================================
|
||
|
||
const day = 1000 * 60 * 60 * 24;
|
||
// search options
|
||
function handleQueryDay(val: number) {
|
||
const newDay = new Date(); // 当前日期
|
||
const time = new Date(newDay.getTime() - val * day); // 指定日期
|
||
diffDay.value = val; // 差值
|
||
diffDay.value = getNaturalDayDiff(time, newDay);
|
||
queryParams.value.statDate = formatDate(time, 'YYYY/MM/DD');
|
||
getList();
|
||
}
|
||
|
||
const { queryParams } = toRefs(data);
|
||
|
||
/** 查询巡检项目列表 */
|
||
const getList = async () => {
|
||
loading.value = true;
|
||
const res = await listItem(queryParams.value);
|
||
itemList.value = res.rows;
|
||
list.value.forEach((item) => {
|
||
item.value = 0;
|
||
});
|
||
list.value[3].value = itemList.value[0].completionRateDisplay || '0%';
|
||
itemList.value.forEach((item) => {
|
||
list.value[0].value = item.villagePlanCount ?? 0;
|
||
list.value[1].value += item.totalTasks ?? 0;
|
||
list.value[4].value += item.outUncompletedTasks ?? 0;
|
||
const num = item.outCompletedTasks + item.completedTasks || 0;
|
||
list.value[2].value += num;
|
||
});
|
||
if (!Number.isNaN(+list.value[2].value) && !Number.isNaN(+list.value[1].value)) {
|
||
const num = Number(list.value[2].value) / Number(list.value[1].value);
|
||
list.value[3].value = Math.min((Number.isNaN(num) ? 0 : num) * 100, 100).toFixed(2) + '%';
|
||
}
|
||
|
||
handleEchartsData(ecartslist.value.find((item) => item.key === activeEchartsindex.value));
|
||
total.value = res.total;
|
||
loading.value = false;
|
||
};
|
||
|
||
onMounted(() => {
|
||
// getList();
|
||
handleQueryDay(7);
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.el-table {
|
||
height: calc(100% - 80px);
|
||
}
|
||
|
||
.rightSearch {
|
||
font-size: 0.85rem;
|
||
div {
|
||
min-width: 60px;
|
||
margin-right: 10px;
|
||
}
|
||
i {
|
||
margin: 0 10px;
|
||
display: block;
|
||
padding: 5px 5px;
|
||
font-style: normal;
|
||
cursor: pointer;
|
||
&:hover {
|
||
color: #409eff;
|
||
}
|
||
}
|
||
}
|
||
.overviewBox {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-top: 40px;
|
||
gap: 20px;
|
||
.overviewitem {
|
||
flex: 1;
|
||
min-width: 150px;
|
||
padding: 25px 15px;
|
||
width: 240px;
|
||
height: 112px;
|
||
border-radius: 10px;
|
||
background-color: rgba(255, 255, 255, 1);
|
||
box-shadow: 0 2px 6px 0 rgba(26, 117, 255, 0.2);
|
||
border: 1px solid rgba(26, 117, 255, 0.2);
|
||
|
||
.overviewitem_name {
|
||
color: rgba(102, 102, 102, 1);
|
||
font-size: 12px;
|
||
}
|
||
.overviewitem_value {
|
||
color: rgba(51, 51, 51, 1);
|
||
font-size: 24px;
|
||
font-weight: 500;
|
||
margin-top: 10px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.echartsbox {
|
||
.echartsactions {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
.echartsactions_item {
|
||
height: 40px;
|
||
line-height: 40px;
|
||
width: 100px;
|
||
text-align: center;
|
||
font-size: 0.75rem;
|
||
border: 1px solid rgba(220, 223, 230, 1);
|
||
color: rgba(16, 16, 16, 1);
|
||
border-left: none;
|
||
cursor: pointer;
|
||
&:first-of-type {
|
||
border-radius: 4px 0 0 0;
|
||
border-left: 1px solid rgba(220, 223, 230, 1);
|
||
}
|
||
|
||
&:last-of-type {
|
||
border-radius: 0 4px 0 0;
|
||
}
|
||
&.active {
|
||
color: #fff;
|
||
background-color: rgba(26, 117, 255, 1);
|
||
}
|
||
}
|
||
}
|
||
.echartsbody {
|
||
max-height: 500px;
|
||
}
|
||
}
|
||
</style>
|