数字督察一体化平台-前端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

823 lines
26 KiB

<template>
<el-scrollbar height="100vh">
<div class="wrapper">
<datav-header />
<main>
<el-row :gutter="16">
<el-col :span="6">
<datav-card title="机构问题排名" sub-title="问题数">
<datav-tabs
v-model="activeOrgTab"
type="bottom-button"
ref="cardProRank"
>
<datav-tab-item label="分县市局" name="1">
<el-scrollbar height="340px">
<datav-chart-bar
:data="fxsjlist"
size="bigNumber"
:color="colors"
/>
</el-scrollbar>
</datav-tab-item>
<datav-tab-item label="局属单位" name="2">
<el-scrollbar height="340px">
<datav-chart-bar
:data="jsdwlist"
size="bigNumber"
:color="colors"
/>
</el-scrollbar>
</datav-tab-item>
</datav-tabs>
</datav-card>
<datav-card title="业务类型占比">
<v-charts
style="height: 360px"
:option="ywlxPieOption"
autoresize
ref="ywlxzbOption"
/>
</datav-card>
</el-col>
<el-col :span="12">
<datav-date-picker v-model="time" />
<div class="flex gap-42">
<datav-statistic
:value="overview.supervisionPro"
title="现场督察"
style="width: 20%;"
@click="open('/datav/sceneInsp')"
/>
<datav-statistic
:value="overview.numSupervisionPro"
:title="`数字督察`"
style="width: 20%;"
/>
<datav-statistic
:value="overview.caseVerificationPro"
:title="`案件核查`"
style="width: 20%; margin-left: 10px"
@click="open('/datav/caseVerif')"
/>
<datav-statistic
:value="overview.mailboxNumber"
:title="`局长信箱`"
style="width: 20%; margin-left: 10px"
/>
<datav-statistic
:value="overview.complaintPro"
:title="`信访投诉`"
style="width: 20%; margin-left: 10px"
@click="open('/datav/mailVisits')"
/>
<!-- <datav-statistic
:value="overview.talkPro"
:title="`下发不满意\n样本`"
style="width: 16.66%; margin-left: 10px"
@click="open('/datav/jwpy')"
/> -->
<datav-statistic
:value="overview.auditPro"
:title="`审计项目`"
style="width: 20%; margin-left: 10px"
@click="open('/datav/auditSuper')"
/>
</div>
<v-charts
style="height: 360px"
:option="option"
autoresize
@click="handleClick"
ref="globalMap"
/>
<div>
<datav-card title="问题趋势">
<v-charts
style="height: 280px"
:option="proTrend"
autoresize
ref="globalTrend"
/>
<div class="gobal-dropdown-container">
<el-dropdown
class="test"
@command="handleCommand"
>
<span
class="el-dropdown-link my-gobal-yearselect"
>
{{ selectedYear + " 年" }}
</span>
<template #dropdown>
<el-dropdown-menu
style="width: 90px"
>
<el-dropdown-item
v-for="year in years"
:key="year"
:command="year"
>{{ year + " 年" }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</datav-card>
</div>
</el-col>
<el-col :span="6">
<datav-card title="突出问题排名" sub-title="问题数">
<el-scrollbar height="390px">
<datav-chart-bar-pro
:data="tcwtlist"
size="small"
:color="colors"
:labelWidth="40"
/>
</el-scrollbar>
</datav-card>
<datav-card title="问题涉及方面分布">
<v-charts
style="height: 370px"
:option="wtlxPieOption"
autoresize
ref="wtlxzbOption"
/>
</datav-card>
</el-col>
</el-row>
</main>
</div>
</el-scrollbar>
</template>
<script setup>
import vCharts from "vue-echarts";
import changshaMap from "@/assets/data/changsha.json";
import * as echarts from "echarts/core";
import moment from "moment/moment";
import {
getBusinessRate,
getOrganizationRank,
getProblemBusinessRate,
getStrongProblemRate,
getGlobalMap,
getAllGlobalCount,
getGlobalTrend,
} from "@/api/screen/gobal.ts";
import { mapOrgNameMapping, orgMapping } from "@/enums/orgMapping.js";
// region 各种变量
const router = useRouter();
const fxsjlist = ref([
{
label: "天心分局",
value: 0,
},
]); // 分县市区机构问题排名
const jsdwlist = ref([]); // 局属单位机构问题排名
const tcwtlist = ref([]); // 突出问题排名
const overview = ref({
totalPro: 0,
supervisionPro: 0,
numSupervisionPro:0,
caseVerificationPro: 0,
complaintPro: 0,
talkPro: 0,
auditPro: 0,
mailboxNumber: 0
}); // 中央的概况数据
const time = ref([
moment().startOf("year").format("YYYY-MM-DD"),
moment().format("YYYY-MM-DD"),
]);
const globalTempMapVoList = ref([
{
name: "天心分局",
totalPro: 11,
supervisePro: 11,
caseVerifyPro: 11,
mailPro: 11,
policePro: 11,
reviewPro: 11,
value: 200,
},
]); // 地图临时数据
const currentYear = new Date().getFullYear();
const years = ref([
currentYear.toString(),
(currentYear - 1).toString(),
(currentYear - 2).toString(),
]); // 年份列表
const selectedYear = ref(currentYear); // 当前选中的年份
const colors = [
{
color: "linear-gradient( 270deg, #FB002D 0%, #822232 100%)",
percentage: 80,
},
{
color: "linear-gradient( 270deg, #FFB90E 0%, #71501D 100%)",
percentage: 60,
},
];
const activeOrgTab = ref("1");
const cardProRank = ref(null);
const globalMap = ref(null);
const globalTrend = ref(null);
const ywlxzbOption = ref(null);
const wtlxzbOption = ref(null);
let cardProRankIntervalId;
let wtlxPieCircularIntervalId; // 问题涉及方面分布定时器
let globalMapIntervalId; // 地图定时器
let globalTrendIntervalId; // 问题趋势定时器
let ywlxPieCircularIntervalId; // 业务类型占比定时器
// endregion
// region 图表变量
// 业务类型占比
const ywlxPieOption = ref({
series: [
{
type: "pie",
radius: ["40%", "70%"],
label: {
color: "#fff",
},
data: [],
},
],
tooltip: {
trigger: "item",
},
});
// 问题涉及方面分布
const wtlxPieOption = ref({
series: [
{
type: "pie",
radius: ["40%", "70%"],
label: {
color: "#fff",
},
data: [],
},
],
tooltip: {
trigger: "item",
},
});
echarts.registerMap("changsha", changshaMap);
const option = ref({
geo: {
map: "changsha",
itemStyle: {
opacity: 0, // 设置透明度
},
},
tooltip: {
trigger: "item",
position: "bottom",
formatter: function (params) {
const dataItem = globalTempMapVoList.value.find((item) =>
item.name.includes(params.name.substring(0, 2))
);
if (dataItem) {
return `
<div class="tooltip">
<div class="tooltip-title">${dataItem.originalName}</div>
<div class="tooltip-content">
<ul class="tooltip-ul" >
<li>问题总数 <span>${dataItem.totalPro}</span></li>
<li>现场督导检查问题 <span>${dataItem.supervisePro}</span></li>
<li>数字督察检查问题<span>${dataItem.numSupervisePro}</span></li>
<li>案件核查问题 <span>${dataItem.caseVerifyPro}</span></li>
<li>信访投诉问题 <span>${dataItem.mailPro}</span></li>
<li>民意感知问题 <span>${dataItem.policePro}</span></li>
<li>审计监督问题 <span>${dataItem.reviewPro}</span></li>
</ul>
</div>
</div>`;
} else {
return `
<div class="tooltip">
<div class="tooltip-title">${dataItem.originalName}</div>
<div class="tooltip-content">
<ul class="tooltip-ul"">
<li>问题总数 <span>0000</span></li>
<li>现场督导检查问题 <span>0000</span></li>
<li>数字督导检查问题 <span>0000</span></li>
<li>案件核查问题 <span>0000</span></li>
<li>信访投诉问题 <span>0000</span></li>
<li>民意感知问题 <span>0000</span></li>
<li>审计监督问题 <span>0000</span></li>
</ul>
</div>
</div>`;
}
},
},
visualMap: {
type: "piecewise",
bottom: 10,
pieces: [
{ gte: 0, lte: 200, label: "低于平均问题30%", color: "#4987F6" },
{
gte: 200,
lte: 400,
label: "平均问题上下浮动30%内",
color: "#F6A149",
},
{ gte: 400, label: "高于平均问题30%", color: "#D34343" },
],
right: 10,
realtime: false,
orient: "horizontal",
textStyle: {
color: "#fff",
},
calculable: true,
inRange: {
color: ["#4987F6", "#F6A149", "#D34343"],
},
},
series: [
{
name: "长沙",
type: "map",
map: "changsha",
roam: true,
label: {
show: true,
color: "white",
},
itemStyle: {
areaColor: "#02215E",
borderColor: "#24D2EE",
borderWidth: 1, // 高亮时边框宽度
opacity: 1, // 设置透明度
},
emphasis: {
areaColor: "#FFD700", // 高亮时区域颜色
borderColor: "#FF0000", // 高亮时边框颜色
borderWidth: 4, // 高亮时边框宽度
},
data: [],
},
],
});
// 问题趋势线状图
const proTrend = ref({
grid: {
left: "5%", // 图表距离容器左边的距离
right: "2%", // 图表距离容器右边的距离
top: "10%", // 图表距离容器上边的距离
bottom: "20%", // 图表距离容器下边的距离
containLabel: false, // 是否包含坐标轴的标签
},
xAxis: {
type: "category",
data: [],
boundaryGap: true,
axisTick: {
// 去掉X轴刻度线
show: false,
},
},
yAxis: {
type: "value",
splitLine: {
show: true,
lineStyle: {
color: "#193775",
},
},
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "line",
label: {
backgroundColor: "#6a7985",
},
},
},
series: [
{
type: "line",
smooth: true,
label: {
show: false,
},
lineStyle: {
color: "#28E6FF",
width: 4,
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: "rgba(40,230,255,0.47)", // 渐变起始颜色
},
{
offset: 1,
color: "rgba(40,230,255,0)", // 渐变结束颜色
},
]),
},
data: [],
},
],
});
// endregion
// region 所有函数
const getOrganizationRankData = async (timeValue = time.value) => {
const res = await getOrganizationRank(timeValue);
fxsjlist.value = res.fxsjlist;
// 5, 3, 2, 1
// console.log(fxsjlist.value);
jsdwlist.value = res.jsdwlist;
};
const getBusinessRateData = async (timeValue = time.value) => {
const res = await getBusinessRate(timeValue);
ywlxPieOption.value.series[0].data = res.ywzblist;
};
const getAllGlobalCountData = async (timeValue = time.value) => {
const res = await getAllGlobalCount(timeValue);
overview.value = res.overview;
overview.value.auditPro = 119
};
const getGlobalMapData = async (timeValue = time.value) => {
const res = await getGlobalMap(timeValue);
const mappedData = mapOrgNameMapping(res.globalTempMapVoList, "totalPro");
// 所有问题总数
const maxItem = mappedData.reduce(
(max, item) => (Number(item.value) > Number(max.value) ? item : max),
mappedData[0]
);
const range60Percent = maxItem.value * 0.6; // 不用取整,小数也可以
const range80Percent = maxItem.value * 0.8; // 不用取整,小数也可以
globalTempMapVoList.value = mappedData;
option.value.series[0].data = mappedData;
option.value.visualMap.pieces = [
{
gte: 0,
lte: range60Percent,
label: "低于最大问题的60%",
color: "#4987F6",
},
{
gte: range60Percent,
lte: range80Percent,
label: "介于最大问题的60%~80%",
color: "#F6A149",
},
{ gte: range80Percent, label: "高于最大问题80%", color: "#D34343" },
];
};
const getGlobalTrendData = async (year = selectedYear.value) => {
const res = await getGlobalTrend(year);
const globalRecentlyTrendList = res.globalRecentlyTrendList;
const categories = globalRecentlyTrendList.map((item) => item.name);
const values = globalRecentlyTrendList.map((item) => item.value);
// 更新图表数据
proTrend.value.xAxis.data = categories;
proTrend.value.series[0].data = values;
};
const getStrongProblemRateData = async (timeValue = time.value) => {
const res = await getStrongProblemRate(timeValue);
tcwtlist.value = res.tcwtlist;
};
const getProblemBusinessRateData = async (timeValue = time.value) => {
const res = await getProblemBusinessRate(timeValue);
wtlxPieOption.value.series[0].data = res.wtlxlist;
};
function getData() {
getOrganizationRankData();
getBusinessRateData();
getAllGlobalCountData();
getGlobalMapData();
getGlobalTrendData();
getStrongProblemRateData();
getProblemBusinessRateData();
}
// endregion
// region 动画
// 卡片动画
const cardAnimation = () => {
activeOrgTab.value = ((parseInt(activeOrgTab.value) % 2) + 1).toString();
};
cardProRankIntervalId = setInterval(cardAnimation, 5000);
// 业务类型占比环形图
const ywlxzbCircularAnimation = () => {
const ywlxzbOptionTemp = ywlxzbOption?.value?.chart;
if (!ywlxzbOptionTemp) return;
const length = ywlxPieOption.value.series[0].data.length;
const randomNum = Math.floor(Math.random() * length);
ywlxzbOptionTemp?.dispatchAction({ type: "downplay", seriesIndex: 0 });
ywlxzbOptionTemp?.dispatchAction({
type: "highlight",
seriesIndex: 0,
dataIndex: randomNum,
}); // 显示
ywlxzbOptionTemp?.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex: randomNum,
});
};
ywlxPieCircularIntervalId = setInterval(ywlxzbCircularAnimation, 2000);
// 地图动画
const mapAnimation = () => {
const myGlobalMap = globalMap?.value?.chart;
if (!myGlobalMap) return;
// 来个随机数,9个图 0~8的整数就ok
const randomNum = Math.floor(Math.random() * 9);
myGlobalMap.dispatchAction({
type: "downplay", //downplay 取消高亮
seriesIndex: 0,
});
myGlobalMap.dispatchAction({
type: "highlight", //highLight 高亮指定的数据图形
seriesIndex: 0, //系列index
dataIndex: randomNum, //数据index
});
myGlobalMap.dispatchAction({
type: "showTip", //showTip 显示提示框
seriesIndex: 0,
dataIndex: randomNum,
});
};
// 启动定时器,每隔 2 秒执行一次
globalMapIntervalId = setInterval(mapAnimation, 2000);
// 趋势动画
const globalTrendAnimation = () => {
const myGlobalTrend = globalTrend?.value?.chart;
if (!myGlobalTrend) return;
const randomNum = Math.floor(Math.random() * 12);
myGlobalTrend.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex: randomNum,
});
};
globalTrendIntervalId = setInterval(globalTrendAnimation, 2000);
// 问题涉及方面分布环形图
const wtlxzbCircularAnimation = () => {
const wtlxzbOptionTemp = wtlxzbOption?.value?.chart;
if (!wtlxzbOptionTemp) return;
const length = ywlxPieOption.value.series[0].data.length;
const randomNum = Math.floor(Math.random() * length);
wtlxzbOptionTemp?.dispatchAction({ type: "downplay", seriesIndex: 0 });
wtlxzbOptionTemp?.dispatchAction({
type: "highlight",
seriesIndex: 0,
dataIndex: randomNum,
}); // 显示
wtlxzbOptionTemp?.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex: randomNum,
});
};
wtlxPieCircularIntervalId = setInterval(wtlxzbCircularAnimation, 2000);
// endregion
// region 事件||监听
onMounted(() => {
getData();
setupEventListeners();
});
// 更换年份事件
const handleCommand = (year) => {
selectedYear.value = year; // 更新当前选中的年份
getGlobalTrendData();
};
// 更换时间事件
watch(time, () => {
getData();
});
// 点击地图事件
const handleClick = (params) => {
const departId = params.data.departId;
const url = router.resolve({
path: "/datav/subOneGlobal",
query: { departId: departId },
}).href;
window.open(url, "_blank");
};
// 鼠标经过||进入事件 用于停止动画,清除定时器
const setupEventListeners = () => {
cartProRankAnimtionStop();
ywlxzbAnimationStop();
mapAnimationStop();
trendAnimationStop();
wtlxzbAnimationStop();
};
const cartProRankAnimtionStop = () => {
const temp = cardProRank.value?.$el;
temp.addEventListener("mouseenter", () => {
clearInterval(cardProRankIntervalId);
});
temp.addEventListener("mouseleave", () => {
cardProRankIntervalId = setTimeout(() => {
cardAnimation();
}, 5000);
});
};
const ywlxzbAnimationStop = () => {
const temp = ywlxzbOption?.value?.chart;
temp.on("mousemove", (e) => {
clearInterval(ywlxPieCircularIntervalId);
temp.dispatchAction({ type: "downplay", seriesIndex: 0 });
temp.dispatchAction({
type: "highlight",
seriesIndex: 0,
dataIndex: e.dataIndex,
});
temp.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex: e.dataIndex,
});
});
temp.on("mouseout", () => {
clearInterval(ywlxPieCircularIntervalId);
ywlxPieCircularIntervalId = setInterval(ywlxzbCircularAnimation, 2000);
});
};
const mapAnimationStop = () => {
const temp = globalMap.value.chart;
temp.on("mousemove", (e) => {
clearInterval(globalMapIntervalId);
temp.dispatchAction({ type: "downplay", seriesIndex: 0 });
temp.dispatchAction({
type: "highlight",
seriesIndex: 0,
dataIndex: e.dataIndex,
});
temp.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex: e.dataIndex,
});
});
temp.on("mouseout", () => {
clearInterval(globalMapIntervalId); // 确保没有多个定时器同时运行
globalMapIntervalId = setInterval(mapAnimation, 2000);
});
};
const trendAnimationStop = () => {
const temp = globalTrend?.value.getDom();
temp.addEventListener("mouseenter", () => {
clearInterval(globalTrendIntervalId);
});
temp.addEventListener("mouseleave", () => {
clearInterval(globalTrendIntervalId);
globalTrendIntervalId = setInterval(globalTrendAnimation, 2000);
});
};
const wtlxzbAnimationStop = () => {
const temp = wtlxzbOption?.value?.chart;
temp.on("mousemove", (e) => {
clearInterval(wtlxPieCircularIntervalId);
temp.dispatchAction({ type: "downplay", seriesIndex: 0 });
temp.dispatchAction({
type: "highlight",
seriesIndex: 0,
dataIndex: e.dataIndex,
});
temp.dispatchAction({
type: "showTip",
seriesIndex: 0,
dataIndex: e.dataIndex,
});
});
temp.on("mouseout", () => {
clearInterval(wtlxPieCircularIntervalId);
wtlxPieCircularIntervalId = setInterval(wtlxzbCircularAnimation, 2000);
});
};
function open(path) {
router.push(path)
}
</script>
<style lang="scss" scoped>
@import "@/style/datav.scss";
:deep() {
// 弹框整体
.tooltip {
position: relative;
width: 160px;
height: 194px;
background: linear-gradient(
180deg,
rgba(1, 4, 87, 0.8) 0%,
rgba(3, 21, 119, 0.8) 100%
);
border: 1px solid #4e8fff;
margin: -10px -10px -10px -10px;
}
////浏阳市局
.tooltip-title {
width: 160px;
height: 43px;
background: linear-gradient(180deg, #0a2f86 0%, #04154e 100%);
box-shadow: inset 0px -1px 0px 0px #253755;
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
font-weight: 400;
font-size: 16px;
color: #ffffff;
border-bottom: 1px solid #253755; /* 设置下边框 */
}
.tooltip-content {
width: 160px;
height: 150px;
background: linear-gradient(180deg, #010457 0%, #031577 100%);
}
.tooltip-content ul {
list-style-type: none; /* 移除默认的小圆点 */
padding-left: 5px;
margin: 0;
}
.tooltip-content ul li {
display: flex;
justify-content: space-around;
height: 26px;
color: #597AE9;
font-weight: 400;
font-size: 14px;
}
.tooltip-ul span {
display: inline-block;
float: right;
color: #fff;
font-size: 14px;
//text-align: center; /* 水平居中 */
}
///* 小尖角 */
//.tooltip::before {
// content: '';
// position: absolute;
// top: 90px; /* 调整尖角的垂直位置 */
// left: -10px; /* 调整尖角的水平位置 */
// width: 0;
// height: 0;
// //border-top: 10px solid red; /* 顶边颜色 */
// //border-bottom: 10px solid green; /* 底边颜色 */
// //border-right: 10px solid blue; /* 右边颜色,与背景颜色相同 */
//}
}
.test {
width: 95px;
height: 25px;
background: #1c3472;
}
.gobal-dropdown-container {
position: absolute;
right: 20px;
top: 15px;
}
.my-gobal-yearselect {
font-size: 14px;
padding-top: 6px;
padding-left: 20px;
width: 90px;
color: #fff;
}
</style>