数字督察一体化平台-前端
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.
 
 
 
 

793 lines
23 KiB

<template>
<el-scrollbar height="100vh">
<div class="wrapper">
<datav-header/>
<main>
<el-row :gutter="16">
<el-col :span="6">
<datav-card title="内部视频">
<div class="row mt-4 mb-20">
<div class="col col-8">
<label>视频总数</label>
<span>107</span>
</div>
<div class="col col-8">
<label>在线数</label>
<span>97</span>
</div>
<div class="col col-8">
<label>在线率</label>
<span>91%</span>
</div>
</div>
<div class="mb-12">
<iframe :src="activeVideoUrl" style="height: 180px"></iframe>
</div>
<el-row :gutter="12">
<el-col :span="8" v-for="item in videos" :key="item" @click="activeVideoUrl = item">
<iframe :src="item" style="height: 90px"></iframe>
</el-col>
</el-row>
</datav-card>
<datav-card title="问题数排名" sub-title="查实问题数" style="height: 370px;">
<datav-tabs
type="bottom-button"
v-model="proRankTab"
ref="videoProRank"
>
<datav-tab-item label="分县市局" name="1">
<el-scrollbar height="280px">
<datav-chart-bar
:data="fxsjRankList"
size="large"
:color="colors"
/>
</el-scrollbar>
</datav-tab-item>
<datav-tab-item label="局属单位" name="2">
<el-scrollbar height="280px">
<datav-chart-bar
:data="jsdwRankList"
:max="11"
size="large"
:color="colors"
/>
</el-scrollbar>
</datav-tab-item>
</datav-tabs>
</datav-card>
</el-col>
<el-col :span="12">
<datav-date-picker v-model="time"/>
<div class="flex gap-42">
<datav-statistic
:value="overview.discoverProblem"
title="查实问题数"
style="width: 20%"
/>
<datav-statistic
:value="overview.changedProblem"
title="整改问题数"
style="width: 20%"
/>
<datav-statistic
:value="overview.relativeOrg"
title="涉及单位数"
style="width: 20%"
/>
<datav-statistic
:value="overview.relativePer"
title="涉及人数"
style="width: 20%"
/>
<datav-statistic
:value="overview.changedRate"
value-unit="%"
title="整改率"
style="width: 20%"
/>
</div>
<v-charts
style="height: 420px"
:option="option"
autoresize
@click="handleClick"
ref="videoMap"
/>
<datav-card title="问题趋势">
<v-charts
style="height: 250px"
:option="proTrend"
autoresize
ref="videoProTrend"
/>
<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>
</el-col>
<el-col :span="6">
<datav-card title="实时预警">
<el-scrollbar height="450px">
<div class="message message-error">
<div class="message-title">待处理</div>
<div>
芙蓉-定王台-办案区-问室 湖南省长沙市公安局芙蓉分局定王台派出所 &emsp; 民警单人询的违规问题
</div>
<div class="message-footer flex between">
<span>芙蓉分局定王台派出所</span>
<span>2024-11-18 20:12</span>
</div>
</div>
<div class="message message-info">
<div class="message-title">待处理</div>
<div>
望城-管理中心-办案区-询问室湖南省长沙市望城区公安局法制大队&emsp; 民警单人询的违规问题
</div>
<div class="message-footer flex between">
<span>望城区公安局法制大队</span>
<span>2024-11-18 19:12</span>
</div>
</div>
<div class="message">
<div class="message-title">待处理</div>
<div>
执法办案中心-询问室7湖南省长沙市公安局芙蓉分局蓉园派出所 &emsp;同时讯问多名嫌疑对象
</div>
<div class="message-footer flex between">
<span>芙蓉分局蓉园派出所</span>
<span>2024-11-18 16:12</span>
</div>
</div>
<div class="message">
<div class="message-title">待处理</div>
<div>
开福-直属-执法办案管理-讯询问室湖南省长沙市公安局开福分局法制大队 &emsp;民警单人询的违规问题
</div>
<div class="message-footer flex between">
<span>开福分局法制大队</span>
<span>2024-11-18 15:12</span>
</div>
</div>
</el-scrollbar>
</datav-card>
<datav-card title="问题类型占比">
<v-charts
style="height: 300px"
:option="ProblemTypeRateChart"
autoresize
ref="videoProblemTypeRate"
/>
</datav-card>
</el-col>
</el-row>
</main>
</div>
</el-scrollbar>
</template>
<script setup>
import changshaMap from "@/assets/data/changsha.json";
import vCharts from "vue-echarts";
import * as echarts from "echarts/core";
import {
getAllVideoSuperviseCount,
getVideoSuperviseProblemTypeRate,
getVideoSuperviseProblemRank, getVideoSuperviseMap, getVideoSuperviseTrend
} from "@/api/screen/videoSupervise.ts";
import moment from "moment/moment.js";
import {mapOrgNameMapping} from "@/enums/orgMapping.js";
// region 所有变量
const router = useRouter();
const time = ref([
moment().subtract(1, "year").format("YYYY-MM-DD"),
moment().format("YYYY-MM-DD"),
]);
const proRankTab = ref("1");
const fxsjRankList = ref([]);
const jsdwRankList = ref([]);
const overview = ref({
discoverProblem: 0,
changedProblem: 0,
relativeOrg: 0,
relativePer: 0,
changedRate: 0,
});
const mapIconList = ref([{
originalName: "浏阳市局",
name: "浏阳市",
discoverProblem: 135,
changedProblem: 135,
relativeOrg: 89,
relativePer: 152,
changedRate: 100
}])
const currentYear = new Date().getFullYear();
const years = ref([currentYear.toString(), (currentYear - 1).toString(), (currentYear - 2).toString()]); // 年份列表
const selectedYear = ref(currentYear); // 当前选中的年份
const videoProRank = ref(null)
const videoMap = ref(null);
const videoProTrend = ref(null);
const videoProblemTypeRate = ref(null);
let videoProRankIntervalId;
let videoMapIntervalId;
let videoTrendIntervalId;
let videoProblemTypeRateIntervalId;
const colors = [
{
color: "linear-gradient( 270deg, #FB002D 0%, #822232 100%)",
percentage: 80,
},
{
color: "linear-gradient( 270deg, #FFB90E 0%, #71501D 100%)",
percentage: 60,
}
];
// endregion
// region 所有图表
const proTrend = ref({
xAxis: {
type: "category",
boundaryGap: true,
data: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月",],
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,
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: [],
},
],
grid: {
left: '5%', // 图表距离容器左边的距离
right: '2%', // 图表距离容器右边的距离
top: '10%', // 图表距离容器上边的距离
bottom: '20%', // 图表距离容器下边的距离
containLabel: false // 是否包含坐标轴的标签
},
});
echarts.registerMap("changsha", changshaMap);
const option = ref({
geo: {
map: "changsha",
itemStyle: {
normal: {
opacity: 0 // 设置透明度
},
}
},
tooltip: {
trigger: 'item',
position: "bottom",
formatter: function (params) {
const dataItem = mapIconList.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.discoverProblem}</span></li>
<li>整改问题数 <span>${dataItem.changedProblem}</span></li>
<li>涉及单位数 <span>${dataItem.relativeOrg}</span></li>
<li>涉及人数 <span>${dataItem.relativePer}</span></li>
<li>整改率 <span>${dataItem.changedRate}%</span></li>
</ul>
</div>
</div>`;
} else {
return `<div class="tooltip">
<div class="tooltip-title">${params.name}</div>
<div class="tooltip-content">
<ul class="tooltip-ul"">
<li>查实问题数 <span>-</span></li>
<li>整改问题数 <span>-</span></li>
<li>涉及单位数 <span>-</span></li>
<li>涉及人数 <span>-</span></li>
<li>整改率 <span>-</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",
hoverAnimation: true,
roam: true,
label: {
show: true,
color: "white",
},
itemStyle: {
normal: {
areaColor: "#02215E",
borderColor: "#24D2EE",
borderWidth: 1 // 高亮时边框宽度
},
},
emphasis: {
areaColor: "#FFD700", // 高亮时区域颜色
borderColor: "#FF0000", // 高亮时边框颜色
borderWidth: 4 // 高亮时边框宽度
},
data: [],
}
],
})
const ProblemTypeRateChart = ref({
series: [
{
type: "pie",
radius: ["40%", "70%"],
label: {
color: "#fff",
},
data: [],
},
],
tooltip: {
trigger: "item",
},
}); // 问题类型占比
// endregion
// region 所有函数
const getVideoSuperviseProblemRankData = async (timeValue) => {
const res = await getVideoSuperviseProblemRank(timeValue);
fxsjRankList.value = res.fxsjVideoSuperviseProblemRankList
jsdwRankList.value = res.jsdwVideoSuperviseProblemRankList
}
const getAllVideoSuperviseCountData = async (timeValue) => {
const res = await getAllVideoSuperviseCount(timeValue);
overview.value = res.overview;
}
const getVideoSuperviseMapData = async (timeValue) => {
const res = await getVideoSuperviseMap(timeValue);
const mappedData = mapOrgNameMapping(res.videoSuperviseMapIconVoList, "discoverProblem");
// 总数
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 // 不用取整,小数也可以
mapIconList.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 getVideoSuperviseTrendData = async (year) => {
const res = await getVideoSuperviseTrend(year);
const temp = res.videoSuperviseTrendList;
const categories = temp.map(item => item.name);
const values = temp.map(item => item.value);
proTrend.value.xAxis.data = categories;
proTrend.value.series[0].data = values;
}
const getVideoSuperviseProblemTypeRateData = async (timeValue) => {
const res = await getVideoSuperviseProblemTypeRate(timeValue);
ProblemTypeRateChart.value.series[0].data = res.videoSuperviseProblemTypeRate
}
const getData = () => {
getVideoSuperviseProblemRankData(time.value)
getAllVideoSuperviseCountData(time.value);
getVideoSuperviseMapData(time.value)
getVideoSuperviseTrendData(selectedYear.value)
getVideoSuperviseProblemTypeRateData(time.value);
}
// endregion
// region 动画
// 问题数排名动画
const videoProRankAnimation = () => {
proRankTab.value = (parseInt(proRankTab.value) % 2 + 1).toString();
};
videoProRankIntervalId = setInterval(videoProRankAnimation, 3000);
// 地图动画
const videoMapAnimation = () => {
const myVideoMap = videoMap?.value?.chart;
if (!myVideoMap) return;
// 来个随机数,9个图 0~8的整数就ok
const randomNum = Math.floor(Math.random() * 9);
myVideoMap.dispatchAction({
type: "downplay", //downplay 取消高亮
seriesIndex: 0,
});
myVideoMap.dispatchAction({
type: "highlight", //highLight 高亮指定的数据图形
seriesIndex: 0, //系列index
dataIndex: randomNum, //数据index
});
myVideoMap.dispatchAction({
type: "showTip", //showTip 显示提示框
seriesIndex: 0,
dataIndex: randomNum,
});
};
// 启动定时器,每隔 2 秒执行一次
videoMapIntervalId = setInterval(videoMapAnimation, 2000);
// 问题趋势
const videoTrendAnimation = () => {
const trendTemp = videoProTrend?.value?.chart;
if (!trendTemp) return;
const randomNum = Math.floor(Math.random() * 12);
trendTemp.dispatchAction({
type: 'showTip',
seriesIndex: 0,
dataIndex: randomNum,
});
}
videoTrendIntervalId = setInterval(videoTrendAnimation, 2000);
// 问题类型占比动画
const videoProblemTypeRateAnimation = () => {
const temp = videoProblemTypeRate?.value?.chart;
if (!temp) return;
const length = ProblemTypeRateChart.value.series[0].data.length;
const randomNum = Math.floor(Math.random() * length);
temp?.dispatchAction({type: 'downplay', seriesIndex: 0});
temp?.dispatchAction({type: 'highlight', seriesIndex: 0, dataIndex: randomNum}); // 显示
temp?.dispatchAction({type: 'showTip', seriesIndex: 0, dataIndex: randomNum});
}
videoProblemTypeRateIntervalId = setInterval(videoProblemTypeRateAnimation, 2000);
// endregion
// region 事件||监听
onMounted(() => {
getData();
setupEventListeners();
})
watch(time, () => {
getData()
})
const handleCommand = (year) => {
selectedYear.value = year; // 更新当前选中的年份
getVideoSuperviseTrendData(selectedYear.value)
};
const handleClick = (params) => {
const departId = params.data.departId;
const url = router.resolve({
path: "/datav/subOneVideoInsp",
query: {departId: departId},
}).href;
window.open(url, "_blank");
}
// 鼠标经过地图事件
const setupEventListeners = () => {
videoProRankAnimationStop();
videoMapAnimationStop();
videoTrendAnimationStop();
videoProblemTypeRateAnimationStop();
};
const videoProRankAnimationStop = () => {
const temp = videoProRank.value?.$el;
temp.addEventListener('mouseenter', () => {
clearInterval(videoProRankIntervalId)
});
temp.addEventListener('mouseleave', () => {
clearInterval(videoProRankIntervalId); // 确保没有多个定时器同时运行
videoProRankIntervalId = setInterval(videoProRankAnimation, 3000);
});
}
const videoMapAnimationStop = () => {
const temp = videoMap?.value?.chart;
// 鼠标移入
temp.on('mousemove', (e) => {
clearInterval(videoMapIntervalId);
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(videoMapIntervalId); // 确保没有多个定时器同时运行
videoMapIntervalId = setInterval(videoMapAnimation, 2000);
});
}
// videoProTrend
const videoTrendAnimationStop = () => {
const temp = videoProTrend?.value.getDom();
temp.addEventListener('mouseenter', () => {
clearInterval(videoTrendIntervalId);
});
temp.addEventListener('mouseleave', () => {
clearInterval(videoTrendIntervalId);
videoTrendIntervalId = setInterval(videoTrendAnimation, 2000);
});
}
const videoProblemTypeRateAnimationStop = () => {
const temp = videoProblemTypeRate?.value?.chart;
temp.on('mousemove', (e) => {
clearInterval(videoProblemTypeRateIntervalId);
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(videoProblemTypeRateIntervalId);
videoProblemTypeRateIntervalId = setInterval(videoProblemTypeRateAnimation, 2000);
})
}
// endregion
const videos = [
'http://65.47.6.105:18080/#/play/wasm/ws%3A%2F%2F65.47.6.105%3A80%2Frtp%2F43012457021185000003_34020000001320000016.flv',
'http://65.47.6.105:18080/#/play/wasm/ws%3A%2F%2F65.47.6.105%3A80%2Frtp%2F43012457021185000003_34020000001320000003.flv',
'http://65.47.6.105:18080/#/play/wasm/ws%3A%2F%2F65.47.6.105%3A80%2Frtp%2F43012457021185000003_34020000001320000004.flv'
];
const activeVideoUrl = ref(videos[0])
</script>
<style lang="scss" scoped>
@import "@/style/datav.scss";
iframe {
border: none;
width: 100%;
min-height: 90px;
}
img {
width: 100%;
display: block;
border: 1px solid transparent;
&.active {
border-color: #1cd9ff;
}
}
.message {
padding: 10px;
margin-bottom: 10px;
background: linear-gradient(
180deg,
rgba(145, 145, 145, 0) 0%,
rgba(164, 164, 164, 0.26) 98%
);
border: 1px solid;
border-image: linear-gradient(
180deg,
rgba(255, 255, 255, 0.28),
rgba(255, 255, 255, 0.26)
) 1 1;
&.message-error {
background: linear-gradient(
180deg,
rgba(208, 18, 18, 0) 0%,
rgba(255, 0, 0, 0.26) 100%
);
border: 1px solid;
border-image: linear-gradient(
180deg,
rgba(255, 71, 71, 0.59),
rgba(251, 95, 95, 1)
) 1 1;
.message-title {
color: #ff0017;
}
}
&.message-info {
background: linear-gradient(
180deg,
rgba(18, 104, 208, 0) 0%,
rgba(0, 166, 255, 0.26) 100%
);
border: 1px solid;
border-image: linear-gradient(
180deg,
rgba(71, 209, 255, 0.28),
rgba(8, 177, 255, 1)
) 1 1;
.message-title {
color: #1cd9ff;
}
}
.message-title {
font-size: 23px;
font-weight: 700;
margin-bottom: 10px;
color: #597ae9;
}
.message-footer {
font-size: 14px;
color: #597ae9;
margin-top: 10px;
}
}
:deep() {
// 弹框整体
.tooltip {
position: relative;
width: 169.88px;
height: 178px;
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: 169.88px;
height: 43px;
background: linear-gradient(180deg, rgba(1, 4, 87, 0.8) 0%, rgba(3, 21, 119, 0.8) 100%);
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
text-align: center; /* 水平居中 */
font-weight: 400;
font-size: 16px;
color: #FFFFFF;
line-height: 22px;
border-bottom: 1px solid #253755; /* 设置下边框 */
}
.tooltip-content {
width: 170px;
height: 132px;
//font-size: 11px;
margin-top: -12px;
background: linear-gradient(180deg, #010457 0%, #031577 100%);
}
.tooltip-content ul {
list-style-type: none; /* 移除默认的小圆点 */
padding: 0;
}
.tooltip-content ul li {
margin-left: 5px;
height: 25px;
color: #597AE9;
font-size: 13px;
}
// 数字的span
.tooltip-ul span {
float: right;
width: 30px;
text-align: right;
margin-right: 15px;
color: #fff;
font-size: 13px;
}
/* 小尖角 */
.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; /* 右边颜色,与背景颜色相同 */
}
}
.gobal-dropdown-container {
position: absolute;
right: 20px;
top: 15px;
}
.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>