feat:完善首页功能

This commit is contained in:
zjc
2025-01-10 18:07:31 +08:00
parent 9ee304c8c2
commit 880db48579
18 changed files with 618 additions and 364 deletions

19
package-lock.json generated
View File

@@ -18,7 +18,8 @@
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-countup-v3": "^1.4.2", "vue-countup-v3": "^1.4.2",
"vue-echarts": "^7.0.3", "vue-echarts": "^7.0.3",
"vue-router": "^4.4.5" "vue-router": "^4.4.5",
"vue3-seamless-scroll": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^5.2.1",
@@ -3368,6 +3369,14 @@
"url": "https://opencollective.com/webpack" "url": "https://opencollective.com/webpack"
} }
}, },
"node_modules/throttle-debounce": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.0.tgz",
"integrity": "sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==",
"engines": {
"node": ">=12.22"
}
},
"node_modules/tinyglobby": { "node_modules/tinyglobby": {
"version": "0.2.10", "version": "0.2.10",
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.10.tgz", "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.10.tgz",
@@ -3887,6 +3896,14 @@
"vue": "^3.2.0" "vue": "^3.2.0"
} }
}, },
"node_modules/vue3-seamless-scroll": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/vue3-seamless-scroll/-/vue3-seamless-scroll-2.0.1.tgz",
"integrity": "sha512-mI3BaDU3pjcPUhVSw3/xNKdfPBDABTi/OdZaZqKysx4cSdNfGRbVvGNDzzptBbJ5S7imv5T55l6x/SqgnxKreg==",
"dependencies": {
"throttle-debounce": "5.0.0"
}
},
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.2.tgz", "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.2.tgz",

View File

@@ -19,7 +19,8 @@
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-countup-v3": "^1.4.2", "vue-countup-v3": "^1.4.2",
"vue-echarts": "^7.0.3", "vue-echarts": "^7.0.3",
"vue-router": "^4.4.5" "vue-router": "^4.4.5",
"vue3-seamless-scroll": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^5.2.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -8,6 +8,7 @@
:style="{ backgroundImage: `url(${index == 0 ? primary : error})` }" :style="{ backgroundImage: `url(${index == 0 ? primary : error})` }"
v-for="(item, index) in list" v-for="(item, index) in list"
:key="index" :key="index"
@click.shop="handleItem(item)"
> >
<div> <div>
<p :class="[index % 2 === 0 ? 'item-title--primary' : 'item-title--error']"> <p :class="[index % 2 === 0 ? 'item-title--primary' : 'item-title--error']">
@@ -15,11 +16,11 @@
</p> </p>
<video <video
class="item-img" class="item-img"
:ref="(el) => getRefs(el, item, index)" :id="'video' + index"
muted muted
autoplay autoplay
controls :controls="false"
style="object-fit: fill" style="object-fit: cover"
> >
<source src="" type="application/x-mpegURL" /> <source src="" type="application/x-mpegURL" />
</video> </video>
@@ -27,6 +28,7 @@
</li> </li>
</ul> </ul>
</div> </div>
<VideoDialog v-model="videoShow" :src="src" />
</template> </template>
<script setup> <script setup>
@@ -36,27 +38,39 @@
import Hls from 'hls.js' import Hls from 'hls.js'
let list = ref([]) let list = ref([])
let src = ref('')
let videoShow = ref(false)
const getRefs = async (el, item) => { const handleItem = (item) => {
if (el) { src.value = item.hlsUrl
let res = await postRefreshApi({ videoShow.value = true
businessVideoDisplayPosition: item.businessVideoDisplayPosition,
cameraIndexCode: item.cameraIndexCode
})
let hlsUrl = res.data.hlsUrl.replace('http://172.22.15.170:8050', 'http://36.138.38.16:6150')
const hls = new Hls()
hls.loadSource(hlsUrl)
hls.attachMedia(el)
hls.on(Hls.Events.MANIFEST_PARSED, () => {
el.play()
})
}
} }
const getVideoList = async () => { const getVideoList = async () => {
let res = await getVideoListApi({ let res = await getVideoListApi({
businessVideoDisplayPosition: '' businessVideoDisplayPosition: ''
}) })
list.value = res.data list.value = res.data
nextTick(() => {
list.value.forEach(async (item, index) => {
var video = document.getElementById(`video${index}`)
let res1 = await postRefreshApi({
businessVideoDisplayPosition: item.businessVideoDisplayPosition,
cameraIndexCode: item.cameraIndexCode
})
let hlsUrl = res1.data.hlsUrl.replace(
'http://172.22.15.170:8050',
'http://36.138.38.16:6150'
)
item.hlsUrl = hlsUrl
const hls = new Hls()
hls.loadSource(hlsUrl)
hls.attachMedia(video)
hls.on(Hls.Events.MANIFEST_PARSED, () => {
video.play()
})
})
})
} }
onMounted(() => { onMounted(() => {
getVideoList() getVideoList()
@@ -128,6 +142,7 @@
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
z-index: 999;
} }
&-title--error { &-title--error {
@extend .item-title; @extend .item-title;

View File

@@ -0,0 +1,73 @@
<template>
<div class="dialog">
<el-dialog v-model="modelValue" align-center :modal="false" :show-close="false">
<video class="video" ref="videoRef" muted autoplay controls style="object-fit: cover">
<source src="" type="application/x-mpegURL" />
</video>
<img class="close" src="@/assets/images/close.png" @click="modelValue = false" />
</el-dialog>
</div>
</template>
<script setup>
import Hls from 'hls.js'
const props = defineProps({
src: {
type: String,
default: ''
}
})
let modelValue = defineModel()
let videoRef = ref()
watch(
() => modelValue.value,
(val) => {
if (val) {
nextTick(() => {
init()
})
}
},
{
immediate: true
}
)
const init = () => {
const hls = new Hls()
hls.loadSource(props.src)
hls.attachMedia(videoRef.value)
hls.on(Hls.Events.MANIFEST_PARSED, () => {
videoRef.value.play()
})
}
</script>
<style scoped lang="scss">
.dialog {
z-index: 9999;
:deep(.el-dialog) {
width: vw(2540);
height: vh(900);
padding: 0;
}
:deep(.el-dialog__header) {
padding-bottom: 0 !important;
}
.video {
width: vw(2540);
height: vh(900);
}
.close {
cursor: pointer;
position: absolute;
right: vw(20);
top: vw(20);
width: vw(60);
z-index: 9999;
}
}
</style>

View File

@@ -7,6 +7,9 @@ export function useMap() {
map.value = new BMapGL.Map(id) map.value = new BMapGL.Map(id)
map.value.centerAndZoom(new BMapGL.Point(lat, lng), scale) map.value.centerAndZoom(new BMapGL.Point(lat, lng), scale)
map.value.enableScrollWheelZoom(true) map.value.enableScrollWheelZoom(true)
map.value.setMapStyleV2({
styleId: '23c9fb8e1c604995f97f0f1cebd7036f'
})
if (satellite) map.value.setMapType(BMAP_SATELLITE_MAP) if (satellite) map.value.setMapType(BMAP_SATELLITE_MAP)
} }

View File

@@ -5,7 +5,6 @@ import router from './router'
import '@/styles/reset.css' import '@/styles/reset.css'
import '@/styles/common.scss' import '@/styles/common.scss'
import '@/assets/fonts/index.css' import '@/assets/fonts/index.css'
const app = createApp(App) const app = createApp(App)
app.use(createPinia()) app.use(createPinia())

View File

@@ -21,6 +21,7 @@
const init = () => { const init = () => {
if (!params) { if (!params) {
params = { params = {
color: ['#00B1FF', '#00FFFF', '#FF3737', '#DD5627', '#D3F0FE'],
legend: { legend: {
orient: 'vertical', orient: 'vertical',
left: '54%', left: '54%',

View File

@@ -186,7 +186,7 @@
<div class="box-1"> <div class="box-1">
<Title3 title="购票来源" /> <Title3 title="购票来源" />
<div class="count">游客总数<countup :end-val="channelTotal" /></div> <div class="count">游客总数<countup :end-val="channelTotal" /></div>
<ticket /> <ticket :list="channelData" />
</div> </div>
</div> </div>
</div> </div>
@@ -210,6 +210,7 @@
{ value: 0, name: '低感景区总数' } { value: 0, name: '低感景区总数' }
] ]
}) })
// 景区购票数
const admission = computed(() => { const admission = computed(() => {
if (homeData.value) return homeData.value?.admission if (homeData.value) return homeData.value?.admission
return [ return [
@@ -225,6 +226,11 @@
0 0
) )
}) })
// 年龄占比
const channelData = computed(() => {
if (homeData.value) return homeData.value?.userPortrait.channel
return []
})
const channelTotal = computed(() => { const channelTotal = computed(() => {
return homeData.value?.userPortrait?.channel.reduce( return homeData.value?.userPortrait?.channel.reduce(
(total, current) => Number(current.count) + total, (total, current) => Number(current.count) + total,

View File

@@ -5,15 +5,15 @@
<div class="left"> <div class="left">
<div class="item"> <div class="item">
<div class="label">今年总游客数</div> <div class="label">今年总游客数</div>
<ScrollNumber :count="count" prefix="1" /> <scroll-number :count="visitorInfo.total_count_this_year" prefix="1" />
</div> </div>
<div class="item"> <div class="item">
<div class="label">全县景区总游客人数</div> <div class="label">全县景区总游客人数</div>
<ScrollNumber :count="count" prefix="2" /> <scroll-number :count="visitorInfo.total_count_today" prefix="2" />
</div> </div>
<div class="item"> <div class="item">
<div class="label">总在园人数</div> <div class="label">总在园人数</div>
<ScrollNumber :count="count" prefix="3" /> <scroll-number :count="visitorInfo.total_count_today_within_three_hours" prefix="3" />
</div> </div>
</div> </div>
<div class="right"> <div class="right">
@@ -149,7 +149,15 @@
const homeData = inject('homeData') const homeData = inject('homeData')
let count = ref(69459) const visitorInfo = computed(() => {
if (homeData.value) return homeData.value?.visitorInfo.data
return {
total_count_this_year: 0,
total_count_today: 0,
total_count_today_within_three_hours: 0
}
})
let spotList = ref([]) let spotList = ref([])
let list = ref([ let list = ref([
@@ -180,7 +188,6 @@
let res1 = await getBaiduMapCrowdedApi({ let res1 = await getBaiduMapCrowdedApi({
nodeId: res.data[0].nodeid nodeId: res.data[0].nodeid
}) })
console.log(res1, 'res1')
} }
watch( watch(
@@ -200,7 +207,7 @@
arr.push(new BMapGL.Point(j[0], j[1])) arr.push(new BMapGL.Point(j[0], j[1]))
}) })
var polyline = new BMapGL.Polyline(arr, { var polyline = new BMapGL.Polyline(arr, {
strokeColor: '#1EBA29', strokeColor: '#38DBFF',
strokeWeight: 4, strokeWeight: 4,
strokeOpacity: 0.8 strokeOpacity: 0.8
}) })
@@ -280,7 +287,7 @@
position: absolute; position: absolute;
top: vw(20); top: vw(20);
left: vw(20); left: vw(20);
z-index: 99999; z-index: 99;
.alarm-item { .alarm-item {
width: vw(110); width: vw(110);
height: vh(40); height: vh(40);
@@ -303,7 +310,7 @@
position: absolute; position: absolute;
bottom: vw(20); bottom: vw(20);
left: vw(20); left: vw(20);
z-index: 99999; z-index: 999;
} }
.spot-list { .spot-list {
display: flex; display: flex;

View File

@@ -2,51 +2,36 @@
<div class="box-4"> <div class="box-4">
<Title1 title="交通信息" /> <Title1 title="交通信息" />
<div class="traffic-info flex justify-evenly pt-10 pb-20"> <div class="traffic-info flex justify-evenly pt-10 pb-20">
<div v-for="item in list" class="cell"> <div class="cell">
<img class="icon" :src="item.icon" alt="" width="64" height="64" /> <img class="icon" :src="icon1" alt="" />
<div> <div>
<countup :end-val="item.value" /> <countup :end-val="countItems.now_yongdu_sum" />
<div class="label">{{ item.label }}</div> <div class="label">路段总数</div>
</div>
</div>
<div class="cell">
<img class="icon" :src="icon2" alt="" />
<div>
<countup :end-val="countItems.yongdu_luduan_count" />
<div class="label">当前拥堵路段</div>
</div>
</div>
<div class="cell">
<img class="icon" :src="icon3" alt="" />
<div>
<countup :end-val="countItems.yongdu_sum" />
<div class="label">总拥堵次数</div>
</div>
</div>
<div class="cell">
<img class="icon" :src="icon4" alt="" />
<div>
<countup :end-val="countItems.max_congestion_duration" />
<div class="label">最大拥堵时长 </div>
</div> </div>
</div> </div>
</div> </div>
<div class="flex"> <div class="flex">
<div class="box">
<div class="pt-10">
<Title3 title="拥堵路段总数" />
<div class="">
<Line
:width="250"
:height="150"
:config="{ legend: false }"
:data="[
{
name: '企业数',
data: [64, 159, 112, 86, 151, 131, 118, 232, 23, 64, 159, 112, 86, 151, 131, 118]
}
]"
:xAxisData="[
'12-16 10:00',
'12-16 14:00',
'12-16 16:00',
'12-16 22:00',
'12-17 02:00',
'12-17 06:00',
'12-17 10:00',
'12-17 14:00',
'12-17 16:00',
'12-16 22:00',
'12-18 02:00',
'12-18 06:00',
'12-8 10:00',
'12-18 14:00',
'12-18 16:00',
'12-18 20:00'
]"
/>
</div>
</div>
</div>
<div class="box"> <div class="box">
<div class="pt-10"> <div class="pt-10">
<Title3 title="拥堵路段总数" /> <Title3 title="拥堵路段总数" />
@@ -54,37 +39,21 @@
:width="250" :width="250"
:height="150" :height="150"
:config="{ legend: false }" :config="{ legend: false }"
:data="[ :data="congestionData"
{ :xAxisData="congestionXAxisData"
name: '企业数',
data: [64, 159, 112, 86, 151, 131, 118, 232, 23, 64, 159, 112, 86, 151, 131, 118]
}
]"
:xAxisData="[
'12-16 10:00',
'12-16 14:00',
'12-16 16:00',
'12-16 22:00',
'12-17 02:00',
'12-17 06:00',
'12-17 10:00',
'12-17 14:00',
'12-17 16:00',
'12-16 22:00',
'12-18 02:00',
'12-18 06:00',
'12-8 10:00',
'12-18 14:00',
'12-18 16:00',
'12-18 20:00'
]"
/> />
</div> </div>
</div> </div>
<div class="box"> <div class="box">
<div class="pt-10"> <div class="pt-10">
<Title3 title="拥堵路段总数" /> <Title3 title="拥堵次数占比" />
<jam :width="250" :height="200" /> <jam-count :list="countRate" />
</div>
</div>
<div class="box">
<div class="pt-10">
<Title3 title="拥堵时长" />
<jam-duration :list="timeRate" />
</div> </div>
</div> </div>
</div> </div>
@@ -127,11 +96,11 @@
<div class="flex pt-10"> <div class="flex pt-10">
<div class="box-1"> <div class="box-1">
<div class="pt-10"> <div class="pt-10">
<Title3 title="拥堵路段总数" /> <Title3 title="停车场车流量" />
<div class="pt-20"> <div class="pt-10">
<Line <Line
:width="250" :width="250"
:height="120" :height="150"
:config="{ legend: false }" :config="{ legend: false }"
:data="[ :data="[
{ {
@@ -163,7 +132,7 @@
</div> </div>
<div class="box-1"> <div class="box-1">
<div class="pt-10"> <div class="pt-10">
<Title3 title="拥堵路段总数" /> <Title3 title="车源地" />
<traffic /> <traffic />
</div> </div>
</div> </div>
@@ -199,15 +168,24 @@
<div>空余</div> <div>空余</div>
</div> </div>
<div class="content"> <div class="content">
<div <vue3-seamless-scroll
class="cell" :list="homeData?.carShipData?.car.info"
v-for="(item, index) in homeData?.carShipData?.car.info" :limitScrollNum="3"
:key="index" :hover="true"
:step="0.2"
:wheel="true"
:isWatch="true"
> >
<div>{{ item.name }}</div> <div
<div>{{ item.started_count }}<span class="unit-1"></span></div> class="cell"
<div>{{ item.not_started_count }}<span class="unit-1"></span></div> v-for="(item, index) in homeData?.carShipData?.car.info"
</div> :key="index"
>
<div>{{ item.name }}</div>
<div>{{ item.started_count }}<span class="unit-1"></span></div>
<div>{{ item.not_started_count }}<span class="unit-1"></span></div>
</div>
</vue3-seamless-scroll>
</div> </div>
</div> </div>
</div> </div>
@@ -226,15 +204,24 @@
<div>空余</div> <div>空余</div>
</div> </div>
<div class="content"> <div class="content">
<div <vue3-seamless-scroll
class="cell" :list="homeData?.carShipData?.car.info"
v-for="(item, index) in homeData?.carShipData.ship.info" :limitScrollNum="3"
:key="index" :hover="true"
:step="0.2"
:wheel="true"
:isWatch="true"
> >
<div>{{ item.name }}</div> <div
<div>{{ item.started_count }}<span class="unit-1"></span></div> class="cell"
<div>{{ item.not_started_count }}<span class="unit-1"></span></div> v-for="(item, index) in homeData?.carShipData?.ship.info"
</div> :key="index"
>
<div>{{ item.name }}</div>
<div>{{ item.started_count }}<span class="unit-1"></span></div>
<div>{{ item.not_started_count }}<span class="unit-1"></span></div>
</div>
</vue3-seamless-scroll>
</div> </div>
</div> </div>
</div> </div>
@@ -279,7 +266,8 @@
</template> </template>
<script setup> <script setup>
import jam from './jam.vue' import jamDuration from './jam-duration.vue'
import jamCount from './jam-count.vue'
import vacancy from './vacancy.vue' import vacancy from './vacancy.vue'
import occupancy from './occupancy.vue' import occupancy from './occupancy.vue'
import traffic from './traffic.vue' import traffic from './traffic.vue'
@@ -289,30 +277,45 @@
import icon3 from '@/assets/images/icon-3.png' import icon3 from '@/assets/images/icon-3.png'
import icon4 from '@/assets/images/icon-4.png' import icon4 from '@/assets/images/icon-4.png'
import { Vue3SeamlessScroll } from 'vue3-seamless-scroll'
const homeData = inject('homeData') const homeData = inject('homeData')
let list = ref([ const congestionData = computed(() => {
{ if (homeData.value) {
label: '路段总数', return [{ data: homeData.value?.trafficInformation?.congestion.map((item) => item.value) }]
value: '1234',
icon: icon1
},
{
label: '当前拥堵路段',
value: '1234',
icon: icon2
},
{
label: '总拥堵次数',
value: '1234',
icon: icon3
},
{
label: '最大拥堵时长',
value: '1234',
icon: icon4
} }
]) return []
})
const congestionXAxisData = computed(() => {
if (homeData.value) {
return homeData.value?.trafficInformation?.congestion.map((item) => item.name)
}
return []
})
const countItems = computed(() => {
if (homeData.value) {
return homeData.value?.trafficInformation?.countItem
}
return {
max_congestion_duration: 0, // 最大拥堵时长
now_yongdu_sum: 0,
yongdu_luduan_count: 0,
yongdu_sum: 0
}
})
const countRate = computed(() => {
if (homeData.value) {
return homeData.value?.trafficInformation?.countRate
}
return []
})
const timeRate = computed(() => {
if (homeData.value) {
return homeData.value?.trafficInformation?.timeRate
}
return []
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -338,6 +341,7 @@
color: #02f9fa; color: #02f9fa;
font-size: vw(24); font-size: vw(24);
font-weight: bold; font-weight: bold;
text-shadow: 0 0 9px #0096ff;
} }
.label { .label {
font-weight: 400; font-weight: 400;
@@ -466,21 +470,21 @@
} }
} }
.content { .content {
overflow-y: auto; overflow-y: hidden;
height: vh(82); height: vh(82);
/* 滚动条整体样式 */ // /* 滚动条整体样式 */
&::-webkit-scrollbar { // &::-webkit-scrollbar {
width: vw(4); /* 滚动条的宽度 */ // width: vw(4); /* 滚动条的宽度 */
} // }
/* 滚动条轨道 */ // /* 滚动条轨道 */
&::-webkit-scrollbar-track { // &::-webkit-scrollbar-track {
background: 'transparent'; /* 轨道的背景色 */ // background: 'transparent'; /* 轨道的背景色 */
} // }
/* 滚动条滑块 */ // /* 滚动条滑块 */
&::-webkit-scrollbar-thumb { // &::-webkit-scrollbar-thumb {
background: rgba(0, 150, 255, 0.63); /* 滑块的背景色 */ // background: rgba(0, 150, 255, 0.63); /* 滑块的背景色 */
border-radius: 5px; /* 滑块的圆角 */ // border-radius: 5px; /* 滑块的圆角 */
} // }
} }
.cell { .cell {
display: flex; display: flex;

View File

@@ -0,0 +1,125 @@
<template>
<div class="jam-count" :id="id" />
</template>
<script setup>
import { fitChartSize } from '@/utils/dataUtil'
import { useEchart } from '@/hooks/echart'
const props = defineProps({
list: {
type: Array,
default: () => []
}
})
const { id, setOption } = useEchart()
var colorList = ['#FDC40A', '#FF5232', '#50F0A6']
watch(
() => props.list,
(val) => {
if (val.length > 0) init()
},
{
immediate: true
}
)
const setSeriesData = () => {
return props.list.map((item) => {
return {
name: item.name,
value: parseFloat(item.count)
}
})
}
const calcCount = () => {
return props.list.reduce((total, currentValue) => {
return total + parseFloat(currentValue.count)
}, 0)
}
const init = () => {
setOption({
color: colorList,
grid: {
left: '4%',
right: '4%',
top: '4%',
bottom: '4%',
containLabel: true
},
legend: {
orient: 'horizontal',
x: 'center',
bottom: '-2%',
itemHeight: fitChartSize(16),
itemWidth: fitChartSize(16),
itemGap: fitChartSize(10),
formatter: (name) => {
let obj = props.list.find((item) => item.name == name)
return '{name|' + name + '} {value|' + obj?.count + '}{value|%}'
},
textStyle: {
rich: {
name: {
color: '#fff',
fontSize: fitChartSize(12)
},
value: {
color: '#00D5F6',
fontSize: fitChartSize(12)
}
}
}
},
series: [
{
type: 'pie',
center: ['50%', '40%'],
radius: ['45%', '60%'],
itemStyle: {
borderWidth: fitChartSize(4),
borderColor: '#093672'
},
label: {
show: true,
position: 'center',
fontWeight: 'bold',
formatter: function (o) {
return `{label|拥堵次数}` + '\n' + `{value|${calcCount()}}`
},
rich: {
label: {
color: '#7894A8',
padding: [0, 0, 5, 0],
fontSize: fitChartSize(12)
},
value: {
color: '#fff',
fontSize: fitChartSize(18),
fontWeight: 'bold'
}
}
},
labelLine: {
show: false
},
data: setSeriesData()
}
]
})
}
onMounted(() => {
init()
})
</script>
<style scoped lang="scss">
.jam-count {
width: vw(250);
height: vh(150);
}
</style>

View File

@@ -0,0 +1,195 @@
<template>
<!-- 拥堵时长 -->
<div class="jam" :id="id" />
</template>
<script setup>
import { fitChartSize } from '@/utils/dataUtil'
import { useEchart } from '@/hooks/echart'
const { id, setOption } = useEchart()
let props = defineProps({
list: {
type: Array,
default: () => []
}
})
watch(
() => props.list,
(val) => {
if (val.length > 0) init()
},
{ immediate: true }
)
let params = null
const setSeriesData = () => {
return props.list.map((item) => {
return {
name: item.name,
value: item.count,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgba(255, 112, 33, 0)'
},
{
offset: 1,
color: '#FF7021'
}
]
}
}
}
})
}
const setScatterData = () => {
return props.list.map((item) => {
return {
name: item.name,
value: item.count,
itemStyle: {
color: '#fff',
opacity: 1
}
}
})
}
const setYAxisData = () => {
return props.list.map((item) => item.count)
}
const init = () => {
if (!params) {
params = {
backgroundColor: 'transparent',
tooltip: {
show: false
},
legend: {
show: false
},
grid: {
left: '4%',
right: '4%',
top: '16%',
bottom: '-10%',
containLabel: true
},
xAxis: [
{
splitLine: {
show: false
},
type: 'value',
show: false
}
],
yAxis: [
{
splitLine: {
show: false
},
axisLine: {
show: false
},
type: 'category',
axisTick: {
show: false
},
data: [],
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, 6, 0],
inside: true,
formatter: function (value) {
return `{value|${value}}{value|分钟}`
},
rich: {
value: {
align: 'center',
color: '#fff',
fontWeight: 600,
fontSize: fitChartSize(14)
}
}
},
data: setYAxisData()
}
],
series: [
{
name: '',
type: 'bar',
barWidth: fitChartSize(4),
showBackground: true,
barBorderRadius: [0, 0, 0, 0],
backgroundStyle: {
color: 'rgba(0, 150, 255, 0.15)'
},
label: {
show: true,
offset: [10, -10],
color: '#fff',
fontWeight: 500,
position: 'left',
align: 'left',
fontSize: fitChartSize(14),
formatter: function (params) {
return params.data.name
}
},
data: setSeriesData()
},
{
name: '外圆',
type: 'scatter',
emphasis: {
scale: false
},
showSymbol: true,
symbol: 'circle',
symbolSize: fitChartSize(10),
z: 2,
data: setScatterData(),
animationDelay: 500
}
]
}
} else {
params.series[0].data = setSeriesData()
params.series[1].data = setScatterData()
}
setOption(params)
}
</script>
<style scoped lang="scss">
.jam {
width: vw(250);
height: vh(160);
}
</style>

View File

@@ -1,190 +0,0 @@
<template>
<div class="top" id="jam" />
</template>
<script setup>
import { fitChartSize } from '@/utils/dataUtil'
import * as echarts from 'echarts'
let topChart = null
let result = [
{ name: '路段1', value: 86 },
{ name: '路段1', value: 83 },
{ name: '路段1', value: 73 },
{ name: '路段1', value: 61 },
{ name: '路段1', value: 61 }
]
let option = {
backgroundColor: 'transparent',
tooltip: {
show: false
},
legend: {
show: false
},
grid: {
left: '4%',
right: '4%',
top: '16%',
bottom: '-10%',
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, 6, 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(4),
MaxSize: 0,
showBackground: true,
barBorderRadius: [30, 0, 0, 30],
backgroundStyle: {
color: 'rgba(0, 150, 255, 0.15)'
},
label: {
show: true,
offset: [10, -10],
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: 'rgba(255, 112, 33, 0)'
},
{
offset: 1,
color: 'rgba(255, 112, 33, 1)'
}
]
}
}
}
})
},
{
name: '外圆',
type: 'scatter',
emphasis: {
scale: false
},
showSymbol: true,
symbol: 'circle',
symbolSize: fitChartSize(10),
z: 2,
data: result.map((item, index) => {
return {
name: item.name,
value: item.value,
itemStyle: {
color: '#fff',
opacity: 1
}
}
}),
animationDelay: 500
}
]
}
const init = () => {
topChart = echarts.init(document.getElementById('jam'))
topChart.setOption(option)
}
const resize = () => {
if (topChart) {
topChart.dispose()
topChart = null
init()
}
}
onMounted(() => {
init()
window.addEventListener('resize', resize)
})
</script>
<style scoped lang="scss">
.top {
width: 100%;
height: vh(160);
}
</style>

View File

@@ -1,18 +1,6 @@
<template> <template>
<div> <div>
<div class="ticket" :id="id" /> <div class="ticket" :id="id" />
<!-- <div class="legend">
<ul class="legend__wrapper">
<li
class="legend-item"
v-for="(item, index) in homeData?.userPortrait?.channel"
:key="index"
>
<p class="legend-item-label">{{ item.name }}</p>
<p class="legend-item-value">{{ item.value }}%</p>
</li>
</ul>
</div> -->
</div> </div>
</template> </template>
@@ -20,6 +8,13 @@
import { fitChartSize } from '@/utils/dataUtil' import { fitChartSize } from '@/utils/dataUtil'
import { useEchart } from '@/hooks/echart' import { useEchart } from '@/hooks/echart'
let props = defineProps({
list: {
type: Array,
default: () => []
}
})
const { id, setOption } = useEchart() const { id, setOption } = useEchart()
const homeData = inject('homeData') const homeData = inject('homeData')
@@ -29,9 +24,9 @@
let params = null let params = null
watch( watch(
() => homeData.value?.userPortrait?.channel, () => props.list,
(val) => { (val) => {
if (val) init() if (val.length > 0) init()
}, },
{ {
immediate: true immediate: true
@@ -39,11 +34,12 @@
) )
const setSeries = () => { const setSeries = () => {
return homeData.value?.userPortrait?.channel.map((item, index) => { return props.list.map((item, index) => {
return { return {
name: item.name, name: item.name,
clockwise: false,
type: 'pie', type: 'pie',
clockwise: false,
silent: true,
radius: [`${x * (index + 1)}%`, `${y + index * 15}%`], radius: [`${x * (index + 1)}%`, `${y + index * 15}%`],
center: ['50%', '40%'], center: ['50%', '40%'],
label: { show: false }, label: { show: false },
@@ -73,20 +69,22 @@
show: true, show: true,
x: 'center', x: 'center',
y: 'bottom', y: 'bottom',
itemHeight: fitChartSize(8), itemHeight: fitChartSize(12),
itemWidth: fitChartSize(8), itemWidth: fitChartSize(12),
itemGap: fitChartSize(20), itemGap: fitChartSize(10),
formatter: function (name) { formatter: function (name) {
return '{title|' + name + '}' let obj = props.list.find((item) => item.name == name)
return '{name|' + name + '} {value|' + obj?.value + '}{value|%}'
}, },
textStyle: { textStyle: {
rich: { rich: {
title: { name: {
color: '#fff', color: '#fff',
fontSize: fitChartSize(14) fontSize: fitChartSize(14)
}, },
value: { value: {
color: '#00D5F6', color: '#00D5F6',
fontWeight: 600,
fontSize: fitChartSize(14) fontSize: fitChartSize(14)
} }
} }

View File

@@ -24,8 +24,8 @@
<div class="flex justify-center"> <div class="flex justify-center">
<div class="top flex justify-evenly"> <div class="top flex justify-evenly">
<count-item label="今日舆情总数" :count="total" suffix="条" color="#ffffff" /> <count-item label="今日舆情总数" :count="total" suffix="条" color="#ffffff" />
<count-item label="今日正面舆情" :count="sensitive" suffix="条" color="#ffffff" /> <count-item label="今日正面舆情" :count="unsensitive" suffix="条" color="#ffffff" />
<count-item label="今日负面舆情" :count="unsensitive" suffix="条" color="#ffffff" /> <count-item label="今日负面舆情" :count="sensitive" suffix="条" color="#ffffff" />
</div> </div>
</div> </div>
<div class="flex mt-20 gap-8 ml-8 mr-8"> <div class="flex mt-20 gap-8 ml-8 mr-8">