预警分析

This commit is contained in:
给我
2026-01-14 18:30:19 +08:00
parent 9aa7078024
commit cc7f225199
8 changed files with 760 additions and 78 deletions

View File

@ -0,0 +1,98 @@
<template>
<div style="height: 100%; width: 100%" :id="echartsId"></div>
</template>
<script setup>
import * as echarts from "echarts";
import {
onMounted,
ref,
reactive,
defineProps,
onUnmounted,
watch,
nextTick
} from "vue";
const props = defineProps({
echartsId: {
type: String,
default: "lineId"
},
color: {
type: String,
default: "#fff"
},
data: {
type: Object,
default: {}
}
});
function chartFn() {
var myChart = echarts.init(document.getElementById(props.echartsId));
var option = {
legend: {
data: ['出城车辆', '入城车辆'],
textStyle: { color: "#fff" },
},
grid: {
top: "10%",
right: "0",
left: "0",
bottom: "10%",
containLabel: true
},
tooltip: {
trigger: "axis",
axisPointer: {
lineStyle: {
color: "#ddd"
}
},
backgroundColor: "rgba(255,255,255,1)",
padding: [5, 10],
textStyle: {
color: "#7588E4"
},
extraCssText: "box-shadow: 0 0 5px rgba(0,0,0,0.3)"
},
xAxis: {
type: "category",
boundaryGap: false,
data: props.data.rzcl.map((el) => el.time)
},
yAxis: {
type: "value"
},
series: [
{
name: "出城车辆",
type: "line",
stack: "Total",
smooth: true,
data: props.data.rzcl.map((el) => el.num)
},
{
name: "入城车辆",
type: "line",
stack: "Total",
smooth: true,
data: props.data.czcl.map((el) => el.num)
},
]
};
option && myChart.setOption(option);
window.addEventListener("resize", function () {
myChart.resize();
});
}
onMounted(() => {
// chartFn();
});
watch(() => props.data, (val) => {
if (val) {
chartFn();
}
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,80 @@
<template>
<div style="height: 100%; width: 100%" :id="echartsId"></div>
</template>
<script setup>
import * as echarts from "echarts";
import {
onMounted,
ref,
reactive,
defineProps,
onUnmounted,
watch,
nextTick
} from "vue";
const props = defineProps({
echartsId: {
type: String,
default: "pieId"
},
color: {
type: String,
default: "#fff"
},
data: {
type: Array,
default: []
}
});
function chartFn() {
var myChart = echarts.init(document.getElementById(props.echartsId));
var option = {
legend: {
bottom: '5%',
left: 'center',
textStyle: { color: "#fff" },
},
grid: {
top: "0",
right: "0",
left: "0",
bottom: "12%",
containLabel: true
},
tooltip: {
trigger: "item",
},
series: [
{
name: '',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
labelLine: {
show: false
},
data: props.data
}
]
};
option && myChart.setOption(option);
window.addEventListener("resize", function () {
myChart.resize();
});
}
onMounted(() => {
// chartFn();
});
watch(() => props.data, (val) => {
if (val) {
chartFn();
}
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,359 @@
<template>
<div>
<div class="dashboard-container">
<!-- 顶部数据卡片区域 -->
<div class="top-cards">
<el-card class="card-item" v-for="item in topData" :key="item">
<div class="card-title">{{ item.bt }}</div>
<div class="card-value">{{ item.num }}</div>
<div class="card-desc">{{ item.nr }}</div>
</el-card>
</div>
<!-- 中间图表+数据区域 -->
<div class="middle-section">
<!-- 左侧车流量折线图 -->
<el-card class="section-item">
<div class="section-title flex just-between align-center">
<div>进出镇区主干道卡口数据监测</div>
<!-- <el-select v-model="value" placeholder="请选择" style="width: 240px">
<el-option label="今日数量" value="0" />
</el-select> -->
</div>
<LineEchart :data="clData" style="height: 200px" />
<div class="traffic-footer">
<div>入镇口{{ clData.rck }}</div>
<div>出镇口{{ clData.cck }}</div>
<div>重点卡口{{ clData.zdkk }}</div>
</div>
</el-card>
<!-- 右侧人口结构环形图 -->
<el-card class="section-item">
<div class="section-title">入镇人员分析模型-人口数据统计</div>
<PieEcharts style="height: 210px" :data="yjfxData" />
<div class="population-footer">
<div v-for="item in yjfxData" :key="item">{{ item.bt }}{{ item.num }} ({{ item.bfb }}%)</div>
</div>
</el-card>
</div>
<!-- 底部数据+地图区域 -->
<div class="bottom-section">
<!-- 左侧预警人员表格 -->
<el-card class="section-item_small">
<div class="section-title">人员数据比对分析-预警人员信息</div>
<el-row>
<el-col :span="2">
<div>#</div>
</el-col>
<el-col :span="10">人员信息</el-col>
<el-col :span="8">预警类型</el-col>
<el-col :span="4">风险等级</el-col>
</el-row>
<el-row v-for="(el, index) in tableData" :key="el.id">
<el-col :span="2">
<div>{{ index + 1 }}</div>
</el-col>
<el-col :span="10"> {{ el.yjRyxm }} </el-col>
<el-col :span="8"> <dict-tag :options="D_BZ_YJLX" :value="el.yjLx" :tag="false" /> </el-col>
<el-col :span="4" style="text-align: center;"> <dict-tag :options="D_BZ_YJJB" :value="el.yjJb"
:tag="false" /> </el-col>
</el-row>
<el-button type="primary" style="width: 100%; margin-top: 10px">查看全部预警人员(63)</el-button>
</el-card>
<!-- 右侧地图+监控说明 -->
<el-card class="section-item map-card">
<div class="section-title flex just-between align-center">
<div>预警人员轨迹追踪系统-多源数据联动</div>
<div class="flex">
<el-input readonly @click="chooseUserVisible = true" v-model="listQuery.yjRyxm" placeholder="请选择预警人员"
clearable />
<!-- <el-button type="primary">轨迹跟踪</el-button> -->
</div>
</div>
<div class="map-container">
<div class="mapbox">
<GdMap />
</div>
</div>
</el-card>
</div>
</div>
</div>
<ChooseYjUser v-model="chooseUserVisible" @choosedUsers="saveUsers" :Single="true"></ChooseYjUser>
</template>
<script setup>
import GdMap from "@/components/GdMap/index.vue";
import { qcckGet, qcckPost } from "@/api/qcckApi.js";
import ChooseYjUser from "@/components/MyComponents/ChooseYjUser";
import LineEchart from "./components/lineEcharts.vue";
import PieEcharts from "./components/pieEcharts.vue";
import { reactive, ref, onMounted, getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
const { D_BZ_YJLX, D_BZ_YJJB } = proxy.$dict("D_BZ_YJLX", "D_BZ_YJJB");
// 预警人员列表数据
const listQuery = ref({
pageCurrent: 1,
pageSize: 10
});
const topData = ref([]);
const clData = ref({});
const yjfxData = ref(null);
const value = ref();
const chooseUserVisible = ref(false);
const tableData = ref([]);
onMounted(() => {
Promise.all([getTopTj(), getclTj(), getYjfxTj(), getListData()]);
});
//查询条件
const queryCondition = ref({});
// 统计
const getTopTj = () => {
qcckGet({}, "/mosty-jcz/kktj/getKkgkfx").then((res) => {
topData.value = res;
});
};
const getclTj = () => {
qcckGet({}, "/mosty-jcz/kktj/getClkksjtj")
.then((res) => {
clData.value = res;
})
};
const getYjfxTj = () => {
qcckGet({}, "/mosty-jcz/kktj/getRjryfxmx")
.then((res) => {
yjfxData.value = res.map((el) => {
return { value: el.num, name: el.bt, ...el }
});
})
};
const getListData = () => {
qcckPost(listQuery.value, '/mosty-jcz/tbJczYjxx/getPageList').then((res) => {
tableData.value =
listQuery.value.pageCurrent == 1
? res.records
: tableData.value.concat(res.records);
});
};
const columnInfo = (val) => {
// 过站类型01 入林 02 出林) 01 入林 02 出林
dialogSearch.value.kkId = val.row.kkId;
switch (val.column.property) {
case "gzryNum":
dialogSearch.value.gzlx = "";
showGjry.value = true;
break;
case "rlclNum":
dialogSearch.value.gzlx = "01";
showGjry.value = true;
break;
case "clryNum":
dialogSearch.value.gzlx = "02";
showGjry.value = true;
break;
case "gzclNum":
dialogSearch.value.gzlx = "";
showGjcl.value = true;
break;
case "rlclNum":
dialogSearch.value.gzlx = "01";
showGjcl.value = true;
break;
case "clclNum":
dialogSearch.value.gzlx = "02";
showGjcl.value = true;
break;
case "gzwpNum":
dialogSearch.value.gzlx = "";
showGjwp.value = true;
break;
case "rlwpNum":
dialogSearch.value.gzlx = "01";
showGjwp.value = true;
break;
case "clwpNum":
dialogSearch.value.gzlx = "02";
showGjwp.value = true;
break;
}
};
//选择预警人员
const saveUsers = (users) => {
listQuery.value.yjRyxm = users[0].yjRyxm;
listQuery.value.yjRysfzh = users[0].yjRysfzh;
qcckPost({ yjRysfzh: listQuery.value.yjRysfzh }, '/mosty-jcz/tbJczYjxx/getPageList').then((res) => {
let list = res.records.filter((item) => {
return item.jd && item.wd
})
emitter.emit("echoPlane", {
fontColor: "#12fdb8",
coords: list,
type: "line",
flag: "yjry_gj",
color: "rgba(2,20,51,0.5)",
linecolor: "#1C97FF"
});
});
};
</script>
<style lang="scss" scoped>
.dashboard-container {
width: 100%;
height: 100vh;
box-sizing: border-box;
}
/* 顶部卡片 */
.top-cards {
display: flex;
gap: 20px;
margin-bottom: 10px;
}
.card-item {
flex: 1;
background-color: #1e1e30;
border: none;
color: #fff;
}
.card-item.warning {
border: 1px solid #f56c6c;
}
.card-title {
font-size: 14px;
color: #ccc;
margin-bottom: 8px;
}
.card-value {
font-size: 24px;
font-weight: bold;
}
.card-desc {
font-size: 12px;
color: #999;
margin-top: 8px;
}
/* 中间区域 */
.middle-section {
display: flex;
gap: 20px;
margin-bottom: 10px;
height: 300px;
}
.section-item {
flex: 1;
background-color: #1e1e30;
border: none;
color: #fff;
display: flex;
flex-direction: column;
}
.section-item_small {
width: 33%;
background-color: #1e1e30;
border: none;
color: #fff;
display: flex;
flex-direction: column;
}
.section-title {
font-size: 16px;
margin-bottom: 10px;
padding: 0 10px;
}
.chart-container {
flex: 1;
width: 100%;
}
.traffic-footer,
.population-footer {
display: flex;
justify-content: space-around;
padding: 10px 0;
font-size: 14px;
color: #ccc;
}
/* 底部区域 */
.bottom-section {
display: flex;
gap: 20px;
height: 340px;
/* 适配剩余高度 */
}
.map-card {
flex: 1;
}
.map-container {
width: 100%;
height: 100%;
position: relative;
.mapbox {
width: 100%;
padding: 0 10px;
height: 250px;
box-sizing: border-box;
background: #000;
}
}
.map-bg {
width: 100%;
height: 100%;
background: url("https://picsum.photos/800/400") center/cover;
opacity: 0.7;
}
.monitor-tip {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(20, 20, 30, 0.8);
padding: 20px;
border-radius: 8px;
}
.tip-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.tip-desc {
font-size: 14px;
color: #ccc;
margin-bottom: 10px;
}
.tip-features {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 5px;
font-size: 12px;
color: #ccc;
}
::v-deep .el-col {
text-align: center;
}
</style>

View File

@ -27,10 +27,8 @@ const open = async (row) => {
visible.value = true
await nextTick()
emitter.emit('map-resize')
const res = await RyGjTrajectory({ sfzh: sfzh.value, days: 30 })
const list = Array.isArray(res) ? res : (res?.data?.data || [])
if (!list || list.length === 0) {
ElMessage({ message: '近30天无轨迹数据', type: 'info' })
emitter.emit('removeElement', 'rygj')
@ -46,7 +44,7 @@ const open = async (row) => {
// 撒点显示历史轨迹
const pointObjs = list.map(it => ({ jd: Number(it.jd), wd: Number(it.wd) }))
emitter.emit('addPointArea', { coords: pointObjs, icon: require('@/assets/images/bi/gzy.png'), flag: 'rygj_points' })
}
defineExpose({ open })