Files
estate/src/views/system/inspection/Overview/index.vue

415 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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"
value-format="YYYY/MM/DD"
format="YYYY/MM/DD"
@update:model-value="handleStateDate"
type="date"
placeholder="选择日期"
:disabled-date="disabledDate"
/>
<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 class="overviewitem" v-for="(item, index) in list" :key="index">
<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
@click="handleEcartsData(item)"
:class="{ active: activeEchartsindex === item.key }"
class="echartsactions_item"
v-for="(item, index) in ecartslist"
:key="index"
>
{{ 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" border :data="itemList">
<el-table-column fixed="left" label="日期" width="100" align="center" prop="statDate" />
<el-table-column label="计划巡检任务" width="110" align="center" prop="totalPlans" />
<el-table-column label="按时完成" align="center" prop="completedTasks">
<template #default="scope">
{{ scope.row.completedTasks - scope.row.outCompletedTasks }}
</template>
</el-table-column>
<el-table-column label="超时完成" align="center" prop="outCompletedTasks" />
<el-table-column label="超时未完成" width="110" align="center" prop="outUncompletedTasks" />
<el-table-column label="进行中" align="center" prop="activeTask" />
<el-table-column label="未开始" align="center" prop="pendingTask" />
</el-table>
<pagination
:pagerCount="pagerCount"
v-show="total > 0"
:total="total"
:layout="layout"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script setup name="Item" lang="ts">
import EchartsCompontent from '@/components/Echarts/index.vue';
import { listItem, type Request } from '@/api/system/Inspection/index';
import { getNaturalDayDiff, getLast7Days, getLastNDays, diffDays, formatDate } 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 queryFormRef = ref<ElFormInstance>();
const pagerCount = document.body.clientWidth <= 1500 ? 1 : 7;
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 handleEcartsOptions(data, name, colorlist = ['#1a75ff', '#f2f9ff']) {
activeSeries.value = null;
const obj = {
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'
}
};
activeSeries.value = obj;
}
// echarts tabs switch
function handleEcartsData(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);
console.log(xAxisDates, dataMap);
xAxislist.value = xAxisDates;
handleEcartsOptions(arr, row.name, [row.color]);
}
}
// 选择日期
function handleStateDate(val) {
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;
});
itemList.value.forEach((item) => {
list.value[0].value += item.totalPlans ?? 0;
list.value[1].value += item.totalTasks ?? 0;
list.value[2].value += item.completedTasks ?? 0;
list.value[4].value += item.outUncompletedTasks ?? 0;
});
if (!Number.isNaN(+list.value[2].value) && !Number.isNaN(+list.value[1].value)) {
list.value[3].value = Math.min((Number(list.value[2].value) / Number(list.value[1].value)) * 100, 100).toFixed(2) + '%';
}
handleEcartsData(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: 0px 2px 6px 0px 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 0px 0px 0px;
border-left: 1px solid rgba(220, 223, 230, 1);
}
&:last-of-type {
border-radius: 0px 4px 0px 0px;
}
&.active {
color: #fff;
background-color: rgba(26, 117, 255, 1);
}
}
}
.echartsbody {
max-height: 500px;
}
}
</style>