feat:对接景区数据和监控视频

This commit is contained in:
zjc
2025-01-16 19:41:46 +08:00
parent 0c00523114
commit fd8ede8a32
20 changed files with 534 additions and 541 deletions

View File

@@ -3,22 +3,22 @@ import request from './request'
// 部门列表 // 部门列表
export function getComDeptsApi() { export function getComDeptsApi() {
return request({ return request({
url: '/api/largeScreen/com/depts', url: '/fjtcc-api/api/largeScreen/com/depts',
method: 'get' method: 'get'
}) })
} }
// 部门成员 // 部门成员
export function getComDeptUsersApi(data) { export function getComDeptUsersApi(data) {
return request({ return request({
url: '/api/largeScreen/com/deptUsers', url: '/fjtcc-api/api/largeScreen/com/deptUsers',
method: 'get', method: 'get',
params: data params: data
}) })
} }
// 最近联系 // 最近联系
export function getComRecordApi() { export function getComRecordApi() {
return request({ return request({
url: '/api/largeScreen/com/record', url: '/fjtcc-api/api/largeScreen/com/record',
method: 'get' method: 'get'
}) })
} }

View File

@@ -4,7 +4,7 @@
<ul class="list"> <ul class="list">
<li <li
class="item" class="item"
:style="{ backgroundImage: `url(${index == 0 ? primary : error})` }" :style="{ backgroundImage: `url(${primary})` }"
v-for="(item, index) in list" v-for="(item, index) in list"
:key="index" :key="index"
@click.shop="handleItem(item)" @click.shop="handleItem(item)"

View File

@@ -1,20 +1,17 @@
<template> <template>
<div class="box-4-content"> <div class="correspondence">
<div class="top-box"> <div class="top-box">
<div class="title">融合通信</div> <div class="title">融合通信</div>
<div class="icon-box flex"> <div class="icon-box flex">
<div class="item" <div
@click="handleDepsUser(item.id,item)" class="item"
v-for="(item,index) in deptsList" v-for="item in deptsList"
:key="item.id"> :key="item.id"
<img src="/src/assets/images/r-icon-1.png" /> @click="handleDepsUser(item.id, item)"
<view class="item-name">{{item.name}}</view> >
</div> <img src="/src/assets/images/r-icon-1.png" />
<!-- <div class="item"><img src="/src/assets/images/r-icon-2.png" />三峡之颠</div> <view class="item-name">{{ item.name }}</view>
<div class="item"><img src="/src/assets/images/r-icon-3.png" />龙桥河</div> </div>
<div class="item"><img src="/src/assets/images/r-icon-4.png" />消防队</div>
<div class="item"><img src="/src/assets/images/r-icon-5.png" />文旅城</div>
<div class="item"><img src="/src/assets/images/r-icon-6.png" />交通城</div> -->
</div> </div>
</div> </div>
<div class="bom-box"> <div class="bom-box">
@@ -23,107 +20,99 @@
<div class="header"> <div class="header">
<div>人员姓名</div> <div>人员姓名</div>
<div>部门</div> <div>部门</div>
<div>职位</div>
<div>时间</div> <div>时间</div>
</div> </div>
<div class="list"> <div class="list">
<div class="item" v-for="(item, index) in recordList" :key="index"> <div class="item" v-for="(item, index) in recordList" :key="index">
<div>{{item.nickname}}</div> <div>{{ item.nickname }}</div>
<div>{{item.department}}</div> <div>{{ item.department }}</div>
<div>{{item.postion}}</div> <div>{{ item.time }}</div>
<div>{{item.time}}</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<el-dialog :title="gridTitle" center v-model="dialogTableVisible"> <el-dialog :title="gridTitle" center v-model="dialogTableVisible">
<div class="bom-box"> <div class="bom-box">
<div class="table"> <div class="table">
<div class="header"> <div class="header">
<div>姓名</div> <div>姓名</div>
<div>部门</div> <div>部门</div>
<div>职位</div> <div>职位</div>
<div>电话号码</div> <div>电话号码</div>
<div>操作</div> <div>操作</div>
</div> </div>
<div class="list"> <div class="list">
<div class="item" v-for="(item, index) in gridData" :key="index"> <div class="item" v-for="(item, index) in gridData" :key="index">
<div>{{item.nickName}}</div> <div>{{ item.nickName }}</div>
<div>{{item.depthName}}</div> <div>{{ item.depthName }}</div>
<div>{{item.postName}}</div> <div>{{ item.postName }}</div>
<div>{{item.phoneNumber}}</div> <div>{{ item.phoneNumber }}</div>
<div> <div>
<img class="sp-ico" src="@/assets/images/sp-icom.png" /> <img class="sp-ico" src="@/assets/images/sp-icom.png" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
<script setup> <script setup>
import { useRouter } from 'vue-router' import { getComDeptsApi, getComDeptUsersApi, getComRecordApi } from '@/api/common'
import { getComDeptsApi,getComDeptUsersApi,getComRecordApi } from '@/api/common'
let deptsList = ref([]) let deptsList = ref([])
let dialogTableVisible = ref(false) let dialogTableVisible = ref(false)
const getComDepts = async()=>{ const getComDepts = async () => {
let res = await getComDeptsApi() let res = await getComDeptsApi()
deptsList.value = res.data deptsList.value = res.data
} }
let recordList = ref([]) let recordList = ref([])
const getComRecord = async()=>{ const getComRecord = async () => {
let res = await getComRecordApi() let res = await getComRecordApi()
recordList.value = res.data recordList.value = res.data
} }
let gridData = ref([]) let gridData = ref([])
let gridTitle = ref('') let gridTitle = ref('')
const handleDepsUser = async(id,item)=>{ const handleDepsUser = async (id, item) => {
gridTitle.value = item.name gridTitle.value = item.name
let res = await getComDeptUsersApi({id}) dialogTableVisible.value = true
gridData.value = res.data let res = await getComDeptUsersApi({ id })
dialogTableVisible.value = true gridData.value = res.data
console.log(gridData.value,'getcomdepts')
} }
onMounted(() => { onMounted(() => {
getComDepts() getComDepts()
getComRecord() getComRecord()
}) })
</script> </script>
<style> <style>
.el-dialog { .el-dialog {
background:url('/src/assets/images/map-bg-2.png') no-repeat top center; background: url('/src/assets/images/map-bg-2.png') no-repeat top center;
background-size: 100% 100%; background-size: 100% 100%;
} }
.el-dialog__title{ .el-dialog__title {
color:#fff; color: #fff;
font-weight: bold; font-weight: bold;
} }
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
.sp-ico{ .sp-ico {
width:vw(50); width: vw(40);
// height:vh(30); }
} .item-name {
.item-name{ width: 90%;
overflow: hidden; text-align: center;
text-overflow: ellipsis; }
white-space: nowrap; .sp-img {
width:100%; width: vw(100);
} }
.sp-img{ .correspondence {
width:vw(100);
}
.box-4-content {
position: relative;
margin: vw(8); margin: vw(8);
width: vw(300); width: vw(300);
height: vh(1060); height: vh(1070);
background-image: url('@/assets/images/bg-1.png'); background-image: url('@/assets/images/bg-1.png');
background-size: 100% 100%; background-size: 100% 100%;
} }
.bom-box { .bom-box {
.table { .table {
@@ -211,7 +200,8 @@
line-height: vh(16); line-height: vh(16);
flex-wrap: wrap; flex-wrap: wrap;
.item { .item {
width: 33.33%; cursor: pointer;
width: 50%;
margin-top: vh(40); margin-top: vh(40);
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -71,13 +71,13 @@
formatter: (e) => { formatter: (e) => {
let valueStr = '' let valueStr = ''
e.map((item) => { e.map((item) => {
valueStr += `<div>${item.seriesName}${item.value}</div>` valueStr += `<div>${item.axisValueLabel}${item.value}</div>`
}) })
let str = `<div style=" let str = `<div style="
background: #07356B; background: #07356B;
border: 1px solid #0096FF; border: 1px solid #0096FF;
color: #fff; color: #fff;
font-weight: 600; font-weight: bold;
font-size: ${fitChartSize(16)}px; font-size: ${fitChartSize(16)}px;
border-radius: ${fitChartSize(4)}px; border-radius: ${fitChartSize(4)}px;
padding: ${fitChartSize(4)}px ${fitChartSize(12)}px;"> padding: ${fitChartSize(4)}px ${fitChartSize(12)}px;">

View File

@@ -5,7 +5,7 @@
class="video-item" class="video-item"
v-for="(item, index) in videoList" v-for="(item, index) in videoList"
:key="index" :key="index"
@click="handleItemVideo(item.hlsUrl, item.cameraName, item.updateTime)" @click="handleItemVideo(item.hlsUrl, 100)"
> >
<div class="video-item__inner"> <div class="video-item__inner">
<video <video
@@ -25,13 +25,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="video-detail" v-if="videoLog == 2"> <div class="video-detail" v-if="videoLog == 2">
<div class="video-detail__wrapper v-error-bg"> <div class="video-detail__wrapper">
<div class="video-detail__title">
<span>{{ videoTitle }}</span>
<span>{{ videoUpdateTime }}</span>
</div>
<video <video
class="video-detail__video" class="video-detail__video"
ref="videoRef" ref="videoRef"
@@ -43,16 +38,39 @@
<source src="" type="application/x-mpegURL" /> <source src="" type="application/x-mpegURL" />
</video> </video>
</div> </div>
<!-- <div class="video-rt"> <div class="video-right">
<div class="title"> <div class="flex justify-between">
<span>最近联系</span> <Title2 title="异常告警" />
</div> <div class="back-box" @click="handleBack">
<div class="rt-v-box"> <img class="icon" src="@/assets/images/back.png" />
<div class="rt-video v-error-bg" v-for="item in 8"> <span>返回</span>
<div class="desc">核心路段这是一条信息说明</div>
</div> </div>
</div> </div>
</div> --> <ul class="list">
<li
class="item"
v-for="(item, index) in videoList"
:key="index"
@click="handleItemVideo(item.hlsUrl, 101)"
>
<div>
<p class="item-title--primary">
{{ item.cameraName }}
</p>
<video
class="item-img"
:id="'monitorVideo' + index"
muted
autoplay
:controls="false"
style="object-fit: cover"
>
<source src="" type="application/x-mpegURL" />
</video>
</div>
</li>
</ul>
</div>
</div> </div>
</template> </template>
@@ -96,23 +114,41 @@
let videoLog = ref(1) let videoLog = ref(1)
let videoRef = ref() let videoRef = ref()
let videoTitle = ref('')
let videoUpdateTime = ref('')
const handleItemVideo = (url, title, time) => { const initVideo = () => {
nextTick(() => {
videoList.value.forEach(async (item, index) => {
const video = document.getElementById(`monitorVideo${index}`)
let res = await postRefreshApi({
type: 'hls',
businessVideoDisplayPosition: item.businessVideoDisplayPosition,
cameraIndexCode: item.cameraIndexCode
})
item.hlsUrl = res.data.hlsUrl
const hls = new Hls()
hls.loadSource(res.data.hlsUrl)
hls.attachMedia(video)
hls.on(Hls.Events.MANIFEST_PARSED, () => {
video.play()
})
})
})
}
const handleBack = () => {
videoLog.value = 1
initVideo()
}
const handleItemVideo = (url, type) => {
videoLog.value = 2 videoLog.value = 2
videoTitle.value = title
videoUpdateTime.value = time
nextTick(() => { nextTick(() => {
const hls = new Hls() const hls = new Hls()
console.log(url, '111')
console.log(videoRef.value, '222')
hls.loadSource(url) hls.loadSource(url)
hls.attachMedia(videoRef.value) hls.attachMedia(videoRef.value)
hls.on(Hls.Events.MANIFEST_PARSED, () => { hls.on(Hls.Events.MANIFEST_PARSED, () => {
videoRef.value.play() videoRef.value.play()
}) })
if (type == 100) initVideo()
}) })
} }
</script> </script>
@@ -149,7 +185,7 @@
} }
} }
&-item { &-item {
width: vw(404); width: vw(410);
height: vh(300); height: vh(300);
padding: vw(10); padding: vw(10);
background-image: url('/src/assets/images/item-primary.png'); background-image: url('/src/assets/images/item-primary.png');
@@ -195,8 +231,8 @@
&-detail__wrapper { &-detail__wrapper {
position: relative; position: relative;
padding: vh(40) vw(50); padding: vh(40) vw(50);
width: vw(1666); width: vw(1660);
height: vh(950); height: vh(960);
background-image: url('/src/assets/images/one-video-bg.png'); background-image: url('/src/assets/images/one-video-bg.png');
background-size: 100% 100%; background-size: 100% 100%;
} }
@@ -218,6 +254,90 @@
width: 100%; width: 100%;
height: vh(880); height: vh(880);
} }
&-right {
margin-left: vw(8);
width: vw(440);
height: vh(956);
background: #082f5a;
.back-box {
cursor: pointer;
padding-right: vw(20);
display: flex;
align-items: center;
.icon {
width: vw(30);
height: auto;
margin-right: vw(10);
}
& > span {
font-weight: bold;
font-size: vw(20);
color: #ffffff;
}
}
.list {
overflow-y: auto;
overflow-x: hidden;
height: vh(920);
padding: vw(8);
/* 滚动条整体样式 */
&::-webkit-scrollbar {
width: vw(0); /* 滚动条的宽度 */
}
/* 滚动条轨道 */
&::-webkit-scrollbar-track {
background: #f1f1f1; /* 轨道的背景色 */
}
/* 滚动条滑块 */
&::-webkit-scrollbar-thumb {
background: #888; /* 滑块的背景色 */
border-radius: 5px; /* 滑块的圆角 */
}
/* 当鼠标悬停在滚动条上时滑块的样式 */
&::-webkit-scrollbar-thumb:hover {
background: #555; /* 滑块的背景色 */
}
}
.item {
margin-bottom: vh(10);
padding: vw(10);
background-image: url('@/assets/images/item-primary.png');
background-size: 100% 100%;
& > div {
position: relative;
}
&-title {
position: absolute;
bottom: 0;
width: 100%;
padding: vw(10);
color: #fff;
font-size: vw(14);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
z-index: 999;
}
&-title--error {
@extend .item-title;
background-color: rgba(226, 27, 27, 0.72);
}
&-title--primary {
@extend .item-title;
background-color: rgba(4, 30, 69, 0.72);
}
&-img {
width: 100%;
height: vh(164);
display: block;
object-fit: cover;
}
}
}
.video-live { .video-live {
.video-rt { .video-rt {
width: vw(400); width: vw(400);

View File

@@ -44,6 +44,12 @@ export const useHomeStore = defineStore('home', () => {
let baiduMapData = ref(null) let baiduMapData = ref(null)
// 工单列表 // 工单列表
let wordkOrderList = ref([]) let wordkOrderList = ref([])
// 工单统计
let wordkOrderData = ref({
toDayData: { count: 15, end: 0, rate: '0.0' },
warnData: { count: 15, end: 0, rate: '0.0' }
})
// 交通信息 // 交通信息
let trafficInfoData = ref({ let trafficInfoData = ref({
data: { data: {
@@ -102,6 +108,9 @@ export const useHomeStore = defineStore('home', () => {
const setScenicQueueData = (val) => { const setScenicQueueData = (val) => {
scenicQueueData.value = val scenicQueueData.value = val
} }
const setWordkOrderData = (val) => {
wordkOrderData.value = val
}
const setWordkOrderList = (val) => { const setWordkOrderList = (val) => {
wordkOrderList.value = val wordkOrderList.value = val
} }
@@ -119,6 +128,7 @@ export const useHomeStore = defineStore('home', () => {
userPortraitData, userPortraitData,
baiduMapData, baiduMapData,
wordkOrderList, wordkOrderList,
wordkOrderData,
scenicQueueData, scenicQueueData,
scenicBearData, scenicBearData,
visitorInfoData, visitorInfoData,
@@ -131,6 +141,7 @@ export const useHomeStore = defineStore('home', () => {
setUserPortraitData, setUserPortraitData,
setBaiduMapData, setBaiduMapData,
setWordkOrderList, setWordkOrderList,
setWordkOrderData,
setScenicQueueData, setScenicQueueData,
setScenicBearData, setScenicBearData,
setVisitorInfoData, setVisitorInfoData,

View File

@@ -47,7 +47,12 @@ export const useScenicStore = defineStore('scenic', () => {
provinceRate: [] provinceRate: []
} }
}) })
// 工单列表
let wordkOrderList = ref([])
const setWordkOrderList = (val) => {
wordkOrderList.value = val
}
const setUserPortraitData = (val) => { const setUserPortraitData = (val) => {
userPortraitData.value = val userPortraitData.value = val
} }
@@ -70,6 +75,7 @@ export const useScenicStore = defineStore('scenic', () => {
scenicSpotData.value = val scenicSpotData.value = val
} }
return { return {
wordkOrderList,
scenicSpotData, scenicSpotData,
scenicQueueData, scenicQueueData,
scenicBearData, scenicBearData,
@@ -77,6 +83,7 @@ export const useScenicStore = defineStore('scenic', () => {
secureData, secureData,
trafficData, trafficData,
userPortraitData, userPortraitData,
setWordkOrderList,
setScenicSpotData, setScenicSpotData,
setScenicQueueData, setScenicQueueData,
setScenicBearData, setScenicBearData,

View File

@@ -1,5 +1,5 @@
export const baseUrl = 'http://36.138.38.16:8001' export const baseUrl = 'http://36.138.38.16:8001'
export const proBaseUrl = ' http://192.168.77.200' export const proBaseUrl = 'http://192.168.77.200'
export const socketBaseUrl = 'ws://36.138.38.16:81' export const socketBaseUrl = 'ws://36.138.38.16:81'
export const proSocketBaseUrl = 'ws://192.168.77.200:8060' export const proSocketBaseUrl = 'ws://192.168.77.200:8060'

View File

@@ -90,7 +90,7 @@
<div class="mt-8"> <div class="mt-8">
<age :list="homeStore.userPortraitData?.ageRate" /> <age :list="homeStore.userPortraitData?.ageRate" />
</div> </div>
<div v-if="ageRateTotal > 0" class="count">总人数:<countup :end-val="ageRateTotal" /></div> <div class="count">总人数<countup :end-val="ageRateTotal" /></div>
<div <div
class="cell pt-20" class="cell pt-20"
v-for="(item, index) in homeStore?.userPortraitData.genderRate" v-for="(item, index) in homeStore?.userPortraitData.genderRate"

View File

@@ -58,38 +58,44 @@
<div class="flex justify-center pt-10"> <div class="flex justify-center pt-10">
<div class="item"> <div class="item">
<p class="label">今日工单总条数</p> <p class="label">今日工单总条数</p>
<countup :endVal="1234" /> <countup :end-val="homeStore.wordkOrderData.toDayData.count" />
</div> </div>
<div class="item"> <div class="item">
<p class="label">工单完成数</p> <p class="label">工单完成数</p>
<countup :endVal="1234" /> <countup class="complete" :end-val="homeStore.wordkOrderData.toDayData.end" />
</div> </div>
</div> </div>
<div class="progress-box"> <div class="progress-box">
<span class="text">工单完成数</span> <span class="text">工单完成数</span>
<div class="progress-1"> <div class="progress-1">
<el-progress :percentage="50" :show-text="false" /> <el-progress
:percentage="homeStore.wordkOrderData.toDayData.rate"
:show-text="false"
/>
</div> </div>
<span class="value">50%</span> <span class="value">{{ homeStore.wordkOrderData.toDayData.rate }}%</span>
</div> </div>
</div> </div>
<div> <div>
<div class="flex justify-center pt-10"> <div class="flex justify-center pt-10">
<div class="item"> <div class="item">
<p class="label">紧急工单数</p> <p class="label">紧急工单数</p>
<countup :endVal="1234" /> <countup class="error" :end-val="homeStore.wordkOrderData.warnData.count" />
</div> </div>
<div class="item"> <div class="item">
<p class="label">紧急工单完成数</p> <p class="label">紧急工单完成数</p>
<countup :endVal="1234" /> <countup class="complete" :end-val="homeStore.wordkOrderData.warnData.end" />
</div> </div>
</div> </div>
<div class="progress-box"> <div class="progress-box">
<span class="text">工单完成数</span> <span class="text">工单完成数</span>
<div class="progress-2"> <div class="progress-2">
<el-progress :percentage="50" :show-text="false" /> <el-progress
:percentage="homeStore.wordkOrderData.warnData.rate"
:show-text="false"
/>
</div> </div>
<span class="value">50%</span> <span class="value">{{ homeStore.wordkOrderData.warnData.rate }}%</span>
</div> </div>
</div> </div>
</div> </div>
@@ -162,9 +168,10 @@
const getSpotList = async () => { const getSpotList = async () => {
let res = await getSpotListApi() let res = await getSpotListApi()
spotList.value = res.data spotList.value = res.data
// let res1 = await getBaiduMapCrowdedApi({ let res1 = await getBaiduMapCrowdedApi({
// nodeId: res.data[0].nodeid nodeId: res.data[0].nodeid
// }) })
console.log(res1, 'res1')
} }
watch( watch(
@@ -338,16 +345,22 @@
justify-content: center; justify-content: center;
} }
.label { .label {
margin-bottom: vh(10);
font-weight: 400; font-weight: 400;
font-size: vw(14); font-size: vw(14);
margin-bottom: vh(10); color: #ffffff;
color: rgba(255, 255, 255, 0.9);
} }
.countup-wrap { .countup-wrap {
color: #02f9fa; color: #02f9fa;
font-size: vw(28); font-size: vw(28);
font-weight: bold; font-weight: bold;
} }
.error {
color: #e21b1b;
}
.complete {
color: #ffffff;
}
.progress-box { .progress-box {
margin-top: vh(10); margin-top: vh(10);
display: flex; display: flex;
@@ -388,7 +401,7 @@
} }
} }
.right { .right {
margin-top: vh(8); margin-top: vh(4);
padding: vh(10) vw(10); padding: vh(10) vw(10);
height: vh(106); height: vh(106);
width: vw(1040); width: vw(1040);

View File

@@ -359,7 +359,7 @@
flex: 1; flex: 1;
& > div { & > div {
position: relative; position: relative;
height: vh(110); height: vh(114);
display: flex; display: flex;
align-items: center; align-items: center;
background-image: url('@/assets/images/bg-4.png'); background-image: url('@/assets/images/bg-4.png');

View File

@@ -127,7 +127,7 @@
fontSize: fitChartSize(12) fontSize: fitChartSize(12)
}, },
verticalAlign: 'bottom', verticalAlign: 'bottom',
padding: [0, 0, 6, 0], padding: [0, 0, 10, 0],
inside: true, inside: true,
formatter: function (value) { formatter: function (value) {
return `{value|${value}}{value|%}` return `{value|${value}}{value|%}`
@@ -156,7 +156,7 @@
}, },
label: { label: {
show: true, show: true,
offset: [10, -10], offset: [10, -14],
color: '#fff', color: '#fff',
fontWeight: 500, fontWeight: 500,
position: 'left', position: 'left',

View File

@@ -66,7 +66,7 @@
legend: { legend: {
show: true, show: true,
x: 'center', x: 'center',
y: 'bottom', y: '70%',
itemHeight: fitChartSize(12), itemHeight: fitChartSize(12),
itemWidth: fitChartSize(12), itemWidth: fitChartSize(12),
itemGap: fitChartSize(6), itemGap: fitChartSize(6),

View File

@@ -2,7 +2,6 @@
<box1 /> <box1 />
<box2 @switch-spot="switchSpot" /> <box2 @switch-spot="switchSpot" />
<box3 /> <box3 />
</template> </template>
<script setup> <script setup>
@@ -63,6 +62,9 @@
case 'wordkOrderlist': case 'wordkOrderlist':
homeStore.setWordkOrderList(val.data) homeStore.setWordkOrderList(val.data)
break break
case 'wordkOrderTotal':
homeStore.setWordkOrderData(val)
break
case 'trafficInformation': case 'trafficInformation':
homeStore.setTrafficInfoData(val) homeStore.setTrafficInfoData(val)
break break

View File

@@ -26,9 +26,9 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.box-2 { .box-2 {
margin-top: vh(120);
width: vw(2356);
height: vh(965);
display: flex; display: flex;
flex: 1;
margin-top: vh(120);
height: vh(965);
} }
</style> </style>

View File

@@ -67,29 +67,24 @@
</div> </div>
<div class="flex align-center justify-between"> <div class="flex align-center justify-between">
<div> <div>
<div class="cell"> <vue3-seamless-scroll
<span class="tag tag--success">普通</span> class="list"
<p class="content"> :list="scenicStore.wordkOrderList"
工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工工单信息工单信息工单信息工单信息工单信息 :limitScrollNum="3"
工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工工单信息工单信息工单信息工单信息工单信息 :hover="true"
</p> :step="0.2"
</div> :wheel="true"
<div class="cell"> :isWatch="true"
<span class="tag tag--error">普通</span> >
<p class="content"> <div class="cell" v-for="(item, index) in scenicStore.wordkOrderList" :key="index">
工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工工单信息工单信息工单信息工单信息工单信息 <span :class="`tag--${item.level}`">{{ item.level_text }}</span>
工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工工单信息工单信息工单信息工单信息工单信息 <p class="content">
</p> {{ item.title }}
</div> </p>
<div class="cell"> </div>
<span class="tag tag--primary">普通</span> </vue3-seamless-scroll>
<p class="content">
工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工工单信息工单信息工单信息工单信息工单信息
工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工单信息工工单信息工单信息工单信息工单信息工单信息
</p>
</div>
</div> </div>
<img class="more" src="@/assets/images/more-col.png" alt="查看更多" /> <img class="more" src="@/assets/images/more-col.png" alt="查看更多" @click="handleMore" />
</div> </div>
</div> </div>
</div> </div>
@@ -97,9 +92,16 @@
<script setup> <script setup>
import countup from 'vue-countup-v3' import countup from 'vue-countup-v3'
import { Vue3SeamlessScroll } from 'vue3-seamless-scroll'
import { useScenicStore } from '@/stores/scenic' import { useScenicStore } from '@/stores/scenic'
import { useRouter } from 'vue-router'
const router = useRouter()
const scenicStore = useScenicStore() const scenicStore = useScenicStore()
const handleMore = () => {
router.push('/workOrder')
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -205,6 +207,11 @@
} }
} }
} }
.list {
margin-top: vh(4);
height: vh(116);
overflow: hidden;
}
.cell { .cell {
display: flex; display: flex;
margin-left: vw(4); margin-left: vw(4);
@@ -216,30 +223,28 @@
padding: 0 vw(16); padding: 0 vw(16);
font-weight: bold; font-weight: bold;
font-size: vw(14); font-size: vw(14);
color: #fff;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: vw(2); border-radius: vw(2);
&--success { }
color: #02f9fa; .tag--important {
border: 1px solid #02f9fa; @extend .tag;
box-shadow: inset 0 0 vw(8) 0 #0be1ab; background: #feae00;
} }
&--error { .tag--warn {
color: #ee2c2c; @extend .tag;
border: 1px solid #ee2c2c; background: #d9011b;
box-shadow: inset 0 0 vw(8) 0 #ee2c2c; }
} .tag--normal {
&--primary { @extend .tag;
color: #00aaff; background: #2380fb;
border: 1px solid #00aaff;
box-shadow: inset 0 0 vw(8) 0 #00aaff;
}
} }
.content { .content {
margin-left: vw(4); margin-left: vw(4);
padding: 0 vw(10); padding: 0 vw(10);
width: vw(660); width: vw(760);
height: vh(24); height: vh(24);
line-height: vh(24); line-height: vh(24);
white-space: nowrap; white-space: nowrap;

View File

@@ -115,7 +115,7 @@
label="安全告警占比" label="安全告警占比"
:dataList="scenicStore.secureData.dataList" :dataList="scenicStore.secureData.dataList"
:total="alarmTotal" :total="alarmTotal"
:width="350" :width="460"
:height="300" :height="300"
/> />
</div> </div>
@@ -141,26 +141,10 @@
<div class="border mr-8"> <div class="border mr-8">
<Title3 title="拥堵次数占比" /> <Title3 title="拥堵次数占比" />
<jam :width="220" :height="160" /> <jam :width="220" :height="160" />
<!-- <div class="legend">
<ul class="legend__wrapper">
<li class="legend-item" v-for="(item, index) in 6" :key="index">
<p class="legend-item-label">路段1</p>
<p class="legend-item-value">10%</p>
</li>
</ul>
</div> -->
</div> </div>
<div class="border"> <div class="border">
<Title3 title="拥堵时长占比" /> <Title3 title="拥堵时长占比" />
<jam :width="220" :height="160" /> <jam :width="220" :height="160" />
<!-- <div class="legend">
<ul class="legend__wrapper">
<li class="legend-item" v-for="(item, index) in 6" :key="index">
<p class="legend-item-label">路段1</p>
<p class="legend-item-value">10%</p>
</li>
</ul>
</div> -->
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,195 +1,106 @@
<!-- 购票来源 -->
<template> <template>
<div class="ticket" :id="id" /> <div class="ticket" :id="id" />
</template> </template>
<script setup> <script setup>
import { fitChartSize } from '@/utils/dataUtil' import { fitChartSize } from '@/utils/dataUtil'
import * as echarts from 'echarts' import { useEchart } from '@/hooks/echart'
import { guid } from '@/utils/util'
let id = ref(guid()) import { useScenicStore } from '@/stores/scenic'
let ticketChart = null
var qxzbData = [ const scenicStore = useScenicStore()
{
name: '处理中', const { id, setOption } = useEchart()
value: 500,
full: 1000 // 最大值 let x = 15
let y = 25
let params = null
watch(
() => scenicStore.userPortraitData.data.channel,
(val) => {
if (val.length > 0) {
setTimeout(() => {
init()
}, 1000)
}
}, },
{ {
name: '超期中', immediate: true
value: 756,
full: 1000
},
{
name: '延期中',
value: 800,
full: 1000
} }
] )
var returnData = function (qxzbData, name) {
for (var a = 0; a < qxzbData.length; a++) { const setSeries = () => {
if (qxzbData[a].name == name) { return scenicStore.userPortraitData.data.channel.map((item, index) => {
return qxzbData[a].value return {
name: item.name,
type: 'pie',
clockwise: false,
silent: true,
radius: [`${x * (index + 1)}%`, `${y + index * 15}%`],
center: ['50%', '40%'],
label: { show: false },
labelLine: { show: false },
emphasis: { show: false },
data: [
{
value: parseFloat(item.value),
name: item.name
},
{
value: 100,
itemStyle: {
color: '#07439C'
}
}
]
} }
} })
}
let option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'item',
formatter: function (rsp) {
if (rsp.name != '') {
return rsp.name + '<br/>' + rsp.marker + ' ' + rsp.value + ' ' + rsp.percent + ''
}
}
},
legend: {
x: 'center',
y: 'bottom',
data: qxzbData,
itemHeight: fitChartSize(8),
itemWidth: fitChartSize(8),
itemGap: fitChartSize(20),
formatter: function (name) {
return '{title|' + name + '} {value|' + returnData(qxzbData, name) + '}'
},
textStyle: {
rich: {
title: {
color: '#fff',
fontSize: fitChartSize(14)
},
value: {
color: '#00D5F6',
fontSize: fitChartSize(14)
}
}
}
},
color: ['#F15A25', '#01FEFE', '#12B5FD'],
series: [
{
name: qxzbData[0].name, //最里面的圈
type: 'pie',
clockWise: false, //顺时加载
radius: ['15%', '25%'],
center: ['50%', '40%'],
label: {
normal: {
show: false
}
},
labelLine: {
show: false
},
itemStyle: {
emphasis: {
show: false
}
},
data: [
{
value: qxzbData[0].value,
name: qxzbData[0].name
},
{
value: qxzbData[0].full,
itemStyle: {
normal: {
color: '#07439C'
}
}
}
]
},
{
name: qxzbData[1].name, //第二个圈
type: 'pie',
clockWise: false, //顺时加载
radius: ['35%', '45%'],
center: ['50%', '40%'],
label: {
normal: {
show: false
}
},
labelLine: {
show: false
},
itemStyle: {
emphasis: {
show: false
}
},
data: [
{
value: qxzbData[1].value,
name: qxzbData[1].name
},
{
value: qxzbData[1].full,
itemStyle: {
normal: {
color: '#07439C'
}
}
}
]
},
{
name: qxzbData[2].name, //最外层圈
type: 'pie',
clockWise: false, //顺时加载
radius: ['55%', '65%'],
center: ['50%', '40%'],
label: {
normal: {
show: false
}
},
labelLine: {
show: false
},
itemStyle: {
emphasis: {
show: false
}
},
data: [
{
value: qxzbData[2].value,
name: qxzbData[2].name
},
{
value: qxzbData[2].full,
itemStyle: {
normal: {
color: '#07439C'
}
}
}
]
}
]
} }
const init = () => { const init = () => {
ticketChart = echarts.init(document.getElementById(id.value)) if (!params) {
ticketChart.setOption(option) params = {
} backgroundColor: 'transparent',
const resize = () => { legend: {
if (ticketChart) { show: true,
ticketChart.resize() x: 'center',
y: '70%',
itemHeight: fitChartSize(12),
itemWidth: fitChartSize(12),
itemGap: fitChartSize(6),
formatter: function (name) {
let obj = scenicStore.userPortraitData.data.channel.find((item) => item.name == name)
return '{name|' + name + '} {value|' + obj?.value + '}{value|%}'
},
textStyle: {
rich: {
name: {
color: '#fff',
fontSize: fitChartSize(12)
},
value: {
color: '#00D5F6',
fontWeight: 600,
fontSize: fitChartSize(12)
}
}
}
},
color: ['#F15A25', '#01FEFE', '#12B5FD'],
series: setSeries()
}
} else {
params.series = setSeries()
} }
setOption(params)
} }
onMounted(() => {
init()
window.addEventListener('resize', resize)
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.ticket { .ticket {
width: vw(250); width: vw(260);
height: vh(300); height: vh(336);
} }
</style> </style>

View File

@@ -1,187 +1,134 @@
<!-- 客源地分析TOP5 -->
<template> <template>
<div class="top" :id="id" /> <div class="top" :id="id" />
</template> </template>
<script setup> <script setup>
import { fitChartSize } from '@/utils/dataUtil' import { fitChartSize } from '@/utils/dataUtil'
import * as echarts from 'echarts' import { useEchart } from '@/hooks/echart'
import { guid } from '@/utils/util' import { useScenicStore } from '@/stores/scenic'
const scenicStore = useScenicStore()
const { id, setOption } = useEchart()
let params = null
let id = ref(guid())
let topChart = null
let colorList = [
'rgba(255, 38, 38, 1)',
'rgba(255, 96, 0, 1)',
'rgba(255, 165, 7, 1)',
'rgba(0, 234, 255, 1)',
'rgba(0, 132, 255, 1)',
'#2379FF'
]
let colorListA = [
'rgba(255, 38, 38, 0.1)',
'rgba(255, 96, 0, 0.1)',
'rgba(255, 165, 7, 0.1)',
'rgba(0, 234, 255, 0.1)',
'rgba(0, 132, 255, 0.1)',
'#49B1FF'
]
let result = [
{ name: '河北', value: 86 },
{ name: '山西', value: 83 },
{ name: '河南', value: 73 },
{ name: '内蒙', value: 61 },
{ name: '辽宁', value: 61 }
]
let option = {
color: colorList,
backgroundColor: 'transparent',
tooltip: {
show: false
},
legend: {
show: false
},
grid: {
left: '4%',
right: '4%',
top: '6%',
bottom: '4%',
containLabel: true
},
xAxis: [
{
splitLine: {
show: false
},
type: 'value',
show: false
}
],
yAxis: [
{
splitLine: {
show: false
},
axisLine: {
show: false
},
type: 'category',
axisTick: {
show: false
},
data: result.map((item) => item.name),
axisLabel: {
show: false
}
},
{
type: 'category',
inverse: true,
axisTick: 'none',
axisLine: 'none',
show: true,
axisLabel: {
textStyle: {
color: '#fff',
fontSize: fitChartSize(12)
},
verticalAlign: 'bottom',
padding: [0, 0, 10, 0],
inside: true,
formatter: function (value) {
return `{value|${value}}`
},
rich: {
name: {
align: 'center',
color: '#D3E5FF',
fontSize: fitChartSize(14),
fontFamily: 'Source Han Sans CN'
},
value: {
align: 'center',
color: '#fff',
fontSize: fitChartSize(14),
fontFamily: 'Source Han Sans CN'
}
}
},
data: result.map((item) => item.value)
}
],
series: [
{
name: '',
type: 'bar',
barWidth: fitChartSize(12),
MaxSize: 0,
showBackground: true,
barBorderRadius: [30, 0, 0, 30],
backgroundStyle: {
color: 'rgba(0, 150, 255, 0.15)'
},
label: {
show: true,
offset: [10, -17],
color: '#D3E5FF',
fontWeight: 500,
position: 'left',
align: 'left',
fontSize: fitChartSize(14),
fontFamily: 'Source Han Sans CN',
formatter: function (params) {
return params.data.name
}
},
data: result.map((item, index) => {
return {
name: item.name,
value: item.value,
itemStyle: {
barBorderRadius: [3, 0, 0, 3],
color: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 1,
colorStops: [
{
offset: 0,
color: colorListA[index]
},
{
offset: 1,
color: colorList[index]
}
]
}
}
}
})
}
]
}
const init = () => { const init = () => {
topChart = echarts.init(document.getElementById(id.value)) if (!params) {
topChart.setOption(option) params = {
} backgroundColor: 'transparent',
const resize = () => { tooltip: {
if (topChart) { show: false
topChart.dispose() },
topChart = null legend: {
init() show: false
},
grid: {
left: '4%',
right: '4%',
top: '10%',
bottom: '-4%',
containLabel: false
},
xAxis: [{ max: 100, show: false }],
yAxis: [
{
type: 'category',
splitLine: {
show: false
},
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: false
}
},
{
type: 'category',
axisTick: 'none',
axisLine: 'none',
show: true,
axisLabel: {
textStyle: {
color: '#fff',
fontSize: fitChartSize(18)
},
verticalAlign: 'bottom',
padding: [0, -fitChartSize(10), fitChartSize(10), 0],
inside: true,
formatter: function (value) {
return `{value|${value}}%`
},
rich: {
value: {
align: 'center',
color: '#fff',
fontSize: fitChartSize(14)
}
}
},
data: scenicStore.userPortraitData.data.provinceRate.map((item) => Number(item.value))
}
],
series: [
{
type: 'bar',
barWidth: fitChartSize(12),
showBackground: true,
barBorderRadius: [0, 0, 0, 0],
backgroundStyle: {
color: 'rgba(0, 150, 255, 0.15)'
},
label: {
show: true,
offset: [fitChartSize(12), -fitChartSize(20)],
color: '#D3E5FF',
fontWeight: 500,
position: 'left',
align: 'left',
fontSize: fitChartSize(14),
formatter: function (params) {
return params.data.name ?? '其他'
}
},
data: scenicStore.userPortraitData.data.provinceRate.map((item) => {
return {
name: item.name,
value: Number(item.value),
itemStyle: {
barBorderRadius: [0, 0, 0, 0],
color: parseFloat(item.value) > 50 ? '#FF7021' : '#00CCFF'
}
}
})
}
]
}
} }
setOption(params)
} }
onMounted(() => {
init() watch(
window.addEventListener('resize', resize) () => scenicStore.userPortraitData.data.provinceRate,
}) (val) => {
if (val.length > 0) {
setTimeout(() => {
init()
}, 1000)
}
},
{
immediate: true
}
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.top { .top {
width: vw(240); width: vw(250);
height: vh(360); height: vh(366);
} }
</style> </style>

View File

@@ -30,6 +30,9 @@
if (val) { if (val) {
console.log(val, '接受消息') console.log(val, '接受消息')
switch (val.type) { switch (val.type) {
case 'wordkOrderlist':
scenicStore.setWordkOrderList(val.data)
break
case 'scenicSpotData': case 'scenicSpotData':
scenicStore.setScenicSpotData(val.data) scenicStore.setScenicSpotData(val.data)
break break