feat:完善功能

This commit is contained in:
zjc
2024-12-27 13:51:47 +08:00
parent c145b856d1
commit 02d5f4942d
10 changed files with 395 additions and 438 deletions

View File

@@ -0,0 +1,48 @@
<template>
<div class="select">
<el-select v-model="modelValue" clearable :placeholder="label" @change="onChange">
<el-option v-for="item in options" :key="item.id" :label="item.ssname" :value="item.id" />
</el-select>
</div>
</template>
<script setup>
defineProps({
label: {
type: String,
default: '选择排队景区'
},
options: {
type: Array,
default: () => []
}
})
let modelValue = defineModel()
let emit = defineEmits(['on-change'])
const onChange = (e) => {
emit('on-change', e)
}
</script>
<style scoped lang="scss">
.select {
:deep(.el-select__wrapper) {
width: vw(240);
padding: vh(10) vw(20);
border-radius: vw(54);
box-shadow: none;
border: 1px solid rgba(0, 114, 220, 0.3);
background: linear-gradient(270deg, rgba(8, 41, 86, 0.16) 0%, #0b61b4 100%);
}
:deep(.el-select__placeholder) {
font-weight: 400;
font-size: vw(20);
color: #ffffff;
}
}
.label {
font-weight: 400;
font-size: vw(24);
}
</style>

View File

@@ -13,58 +13,54 @@
watch( watch(
() => homeData.value?.userPortrait?.ageRate, () => homeData.value?.userPortrait?.ageRate,
() => { () => {
init() setOption({
} tooltip: {
) trigger: 'item'
const init = () => {
setOption({
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: '60%',
y: 'center',
itemHeight: fitChartSize(8),
itemWidth: fitChartSize(8),
itemGap: fitChartSize(10),
formatter: function (name) {
return '{name|' + name + '}'
}, },
textStyle: { legend: {
rich: { orient: 'vertical',
name: { left: '60%',
color: '#fff', y: 'center',
fontSize: fitChartSize(14) itemHeight: fitChartSize(8),
}, itemWidth: fitChartSize(8),
value: { itemGap: fitChartSize(10),
color: '#00D5F6', formatter: function (name) {
fontSize: fitChartSize(14) return '{name|' + name + '}'
},
textStyle: {
rich: {
name: {
color: '#fff',
fontSize: fitChartSize(14)
},
value: {
color: '#00D5F6',
fontSize: fitChartSize(14)
}
} }
} }
} },
}, series: [
series: [ {
{ type: 'pie',
type: 'pie', center: ['30%', '50%'],
center: ['30%', '50%'], radius: ['50%', '70%'],
radius: ['50%', '70%'], itemStyle: {
itemStyle: { borderColor: 'transparent',
borderColor: 'transparent', borderRadius: fitChartSize(2),
borderRadius: fitChartSize(2), borderWidth: fitChartSize(2)
borderWidth: fitChartSize(2) },
}, label: {
label: { show: false,
show: false, color: '#D3F0FE',
color: '#D3F0FE', fontSize: fitChartSize(12)
fontSize: fitChartSize(12) },
}, data: homeData.value?.userPortrait?.ageRate || []
data: homeData.value?.userPortrait?.ageRate || [] }
} ]
] })
}) }
} )
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -7,9 +7,10 @@
</Title1> </Title1>
<div class="flex pt-20"> <div class="flex pt-20">
<div <div
v-for="(item, index) in homeData?.scenicSpot"
class="item" class="item"
:class="{ core: index == 0, queue: index == 1, congestion: index == 2 }" :class="{ core: index == 0, queue: index == 1, congestion: index == 2 }"
v-for="(item, index) in homeData?.scenicSpot"
:key="index"
> >
<span class="label">{{ item.name }}</span> <span class="label">{{ item.name }}</span>
<div class="flex align-end"> <div class="flex align-end">
@@ -146,9 +147,9 @@
<Title2 title="景区购票数" /> <Title2 title="景区购票数" />
<div class="ticket-wrap"> <div class="ticket-wrap">
<img src="@/assets/images/ticket.png" /> <img src="@/assets/images/ticket.png" />
<div> <span class="label">当日购票量</span><countup endVal="768578" /> </div> <div v-for="(item, index) in homeData?.admission" :key="index">
<div> <span class="label">未来3天购票量</span><countup endVal="768578" /> </div> <span class="label">{{ item.name }}</span><countup :end-val="item.value" />
<div> <span class="label">3天后购票量</span><countup endVal="768578" /> </div> </div>
</div> </div>
</div> </div>
@@ -157,8 +158,12 @@
<div class="box-1"> <div class="box-1">
<Title3 title="年龄/性别占比" /> <Title3 title="年龄/性别占比" />
<age /> <age />
<div class="count">总人数<countup endVal="124563" /></div> <div class="count">总人数<countup :end-val="ageRateTotal" /></div>
<div class="cell pt-20" v-for="item in homeData?.userPortrait.genderRate"> <div
class="cell pt-20"
v-for="(item, index) in homeData?.userPortrait.genderRate"
:key="index"
>
<img class="icon" src="@/assets/images/man.png" /> <img class="icon" src="@/assets/images/man.png" />
<div class="bg"> <div class="bg">
<span class="text">{{ item.name }}</span> <span class="text">{{ item.name }}</span>
@@ -181,7 +186,7 @@
</div> </div>
<div class="box-1"> <div class="box-1">
<Title3 title="购票来源" /> <Title3 title="购票来源" />
<div class="count">总人数<countup endVal="124563" /></div> <div class="count">总人数<countup :end-val="channelTotal" /></div>
<ticket /> <ticket />
</div> </div>
</div> </div>
@@ -196,6 +201,19 @@
import ticket from './ticket.vue' import ticket from './ticket.vue'
const homeData = inject('homeData') const homeData = inject('homeData')
const ageRateTotal = computed(() => {
return homeData.value?.userPortrait.genderRate.reduce(
(total, current) => Number(current.count) + total,
0
)
})
const channelTotal = computed(() => {
return homeData.value?.userPortrait.channel.reduce(
(total, current) => Number(current.count) + total,
0
)
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -164,7 +164,7 @@
<div class="box-1"> <div class="box-1">
<div class="pt-10"> <div class="pt-10">
<Title3 title="拥堵路段总数" /> <Title3 title="拥堵路段总数" />
<traffic-flow /> <traffic />
</div> </div>
</div> </div>
<div class="box-1"> <div class="box-1">
@@ -188,7 +188,7 @@
<div class="car"> <div class="car">
<div class="label">车总数</div> <div class="label">车总数</div>
<div class="flex align-center"> <div class="flex align-center">
<countup class="value" :end-val="homeData?.carShipData.car.count" /> <countup class="value" :end-val="homeData?.carShipData?.car?.count || 0" />
<span class="unit"></span> <span class="unit"></span>
</div> </div>
</div> </div>
@@ -198,7 +198,7 @@
<div>调度</div> <div>调度</div>
<div>空余</div> <div>空余</div>
</div> </div>
<div class="cell" v-for="item in homeData?.carShipData.car.info" :key="index"> <div class="cell" v-for="(item, index) in homeData?.carShipData?.car.info" :key="index">
<div>{{ item.scenic_area }}</div> <div>{{ item.scenic_area }}</div>
<div>{{ item.started_count }}<span class="unit-1"></span></div> <div>{{ item.started_count }}<span class="unit-1"></span></div>
<div>{{ item.not_started_count }}<span class="unit-1"></span></div> <div>{{ item.not_started_count }}<span class="unit-1"></span></div>
@@ -209,7 +209,7 @@
<div class="ship"> <div class="ship">
<div class="label">车总数</div> <div class="label">车总数</div>
<div class="flex align-center"> <div class="flex align-center">
<countup class="value" :end-val="homeData?.carShipData.ship.count" /> <countup class="value" :end-val="homeData?.carShipData?.ship?.count || 0" />
<span class="unit"></span> <span class="unit"></span>
</div> </div>
<div class="table"> <div class="table">
@@ -259,7 +259,7 @@
</div> </div>
</div> </div>
<div> <div>
<div class="lodging"> <div class="occupancy">
<Title3 title="酒店入住人数及入住率" /> <Title3 title="酒店入住人数及入住率" />
</div> </div>
<occupancy /> <occupancy />
@@ -273,7 +273,7 @@
import jam from './jam.vue' import jam from './jam.vue'
import vacancy from './vacancy.vue' import vacancy from './vacancy.vue'
import occupancy from './occupancy.vue' import occupancy from './occupancy.vue'
import TrafficFlow from './traffic-flow.vue' import traffic from './traffic.vue'
import countup from 'vue-countup-v3' import countup from 'vue-countup-v3'
import icon1 from '@/assets/images/icon-1.png' import icon1 from '@/assets/images/icon-1.png'
import icon2 from '@/assets/images/icon-2.png' import icon2 from '@/assets/images/icon-2.png'
@@ -519,7 +519,7 @@
} }
} }
} }
.lodging { .occupancy {
:deep(.title-3) { :deep(.title-3) {
margin-top: vh(10); margin-top: vh(10);
} }

View File

@@ -1,174 +1,161 @@
<template> <template>
<div class="lodging-ratio" id="lodging-ratio" /> <div class="lodging-ratio" :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'
let topChart = null const { id, setOption } = useEchart()
let result = [
{ name: '酒店1', value: 86 }, const homeData = inject('homeData')
{ name: '酒店2', value: 83 }, watch(
{ name: '酒店3', value: 73 } () => homeData.value?.hotelData?.list,
] () => {
let option = { setOption({
backgroundColor: 'transparent', backgroundColor: 'transparent',
tooltip: { tooltip: {
show: false
},
legend: {
show: false
},
grid: {
left: '4%',
right: '4%',
top: '20%',
bottom: '-10%',
containLabel: true
},
xAxis: [{ max: 100, show: false }],
yAxis: [
{
splitLine: {
show: false show: false
}, },
axisLine: { legend: {
show: false show: false
}, },
type: 'category', grid: {
axisTick: { left: '4%',
show: false right: '4%',
top: '20%',
bottom: '-10%',
containLabel: true
}, },
data: result.map((item) => item.name), xAxis: [{ max: 100, show: false }],
axisLabel: { yAxis: [
show: false {
} splitLine: {
}, show: false
{
type: 'category',
axisTick: 'none',
axisLine: 'none',
show: true,
axisLabel: {
textStyle: {
color: '#fff',
fontSize: fitChartSize(12)
},
verticalAlign: 'bottom',
padding: [0, 0, fitChartSize(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: { axisLine: {
align: 'center', show: false
color: '#fff', },
fontSize: fitChartSize(14), type: 'category',
fontFamily: 'Source Han Sans CN' axisTick: {
show: false
},
data: homeData.value?.hotelData?.list.map((item) => item.hotel_name),
axisLabel: {
show: false
} }
} },
}, {
data: result.map((item) => item.value) type: 'category',
} axisTick: 'none',
], axisLine: 'none',
series: [ show: true,
{ axisLabel: {
name: '', textStyle: {
type: 'bar', color: '#fff',
barWidth: fitChartSize(4), fontSize: fitChartSize(12)
MaxSize: 0, },
showBackground: true, verticalAlign: 'bottom',
barBorderRadius: [30, 0, 0, 30], padding: [0, 0, fitChartSize(6), 0],
backgroundStyle: { inside: true,
color: 'rgba(0, 150, 255, 0.15)' formatter: function (value) {
}, return `{value|${value}}`
label: { },
show: true, rich: {
offset: [fitChartSize(10), -fitChartSize(20)], name: {
color: '#D3E5FF', align: 'center',
fontWeight: 500, color: '#D3E5FF',
position: 'left', fontSize: fitChartSize(14),
align: 'left', fontFamily: 'Source Han Sans CN'
fontSize: fitChartSize(14), },
fontFamily: 'Source Han Sans CN', value: {
formatter: function (params) { align: 'center',
return params.data.name color: '#fff',
} fontSize: fitChartSize(14),
}, fontFamily: 'Source Han Sans CN'
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)'
}
]
} }
} },
data: homeData.value?.hotelData?.list.map((item) => item.occupancy_rate)
} }
}) ],
}, series: [
{ {
name: '外圆', name: '',
type: 'scatter', type: 'bar',
emphasis: { barWidth: fitChartSize(4),
scale: false MaxSize: 0,
}, showBackground: true,
showSymbol: true, barBorderRadius: [30, 0, 0, 30],
symbol: 'circle', backgroundStyle: {
symbolSize: fitChartSize(10), color: 'rgba(0, 150, 255, 0.15)'
z: 2, },
data: result.map((item, index) => { label: {
return { show: true,
name: item.name, offset: [fitChartSize(10), -fitChartSize(20)],
value: item.value, color: '#D3E5FF',
itemStyle: { fontWeight: 500,
color: '#fff', position: 'left',
opacity: 1 align: 'left',
} fontSize: fitChartSize(14),
fontFamily: 'Source Han Sans CN',
formatter: function (params) {
return params.data.name
}
},
data: homeData.value?.hotelData?.list.map((item) => {
return {
name: item.hotel_name,
value: item.occupancy_rate,
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: homeData.value?.hotelData?.list.map((item) => {
return {
name: item.hotel_name,
value: item.occupancy_rate,
itemStyle: {
color: '#fff',
opacity: 1
}
}
}),
animationDelay: 500
} }
}), ]
animationDelay: 500 })
}
]
}
const init = () => {
topChart = echarts.init(document.getElementById('lodging-ratio'))
topChart.setOption(option)
}
const resize = () => {
if (topChart) {
topChart.dispose()
topChart = null
init()
} }
} )
onMounted(() => {
init()
window.addEventListener('resize', resize)
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -1,190 +1,73 @@
<template> <template>
<div class="ticket" id="ticket" /> <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'
let ticketChart = null const { id, setOption } = useEchart()
var qxzbData = [
{ const homeData = inject('homeData')
name: '处理中',
value: 500, let x = 15
full: 1000 // 最大值 let y = 25
},
{ watch(
name: '超期中', () => homeData.value?.userPortrait?.channel,
value: 756, () => {
full: 1000 setOption({
}, backgroundColor: 'transparent',
{ legend: {
name: '延期中', x: 'center',
value: 800, y: 'bottom',
full: 1000 itemHeight: fitChartSize(8),
} itemWidth: fitChartSize(8),
] itemGap: fitChartSize(20),
var returnData = function (qxzbData, name) { formatter: function (name) {
for (var a = 0; a < qxzbData.length; a++) { return '{title|' + name + '}'
if (qxzbData[a].name == name) {
return qxzbData[a].value
}
}
}
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: { textStyle: {
color: '#00D5F6', rich: {
fontSize: fitChartSize(14) title: {
} color: '#fff',
} fontSize: fitChartSize(14)
} },
}, value: {
color: ['#F15A25', '#01FEFE', '#12B5FD'], color: '#00D5F6',
series: [ fontSize: fitChartSize(14)
{
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: ['30%', '40%'],
center: ['50%', '40%'],
label: {
normal: {
show: false
}
}, },
labelLine: { color: ['#F15A25', '#01FEFE', '#12B5FD'],
show: false series: homeData.value?.userPortrait?.channel.map((item, index) => {
}, return {
itemStyle: { name: item.name,
emphasis: { type: 'pie',
show: false clockwise: false, //顺时加载
} radius: [`${x * (index + 1)}%`, `${y + index * 15}%`],
}, center: ['50%', '40%'],
data: [ label: { show: false },
{ labelLine: { show: false },
value: qxzbData[1].value, emphasis: { show: false },
name: qxzbData[1].name data: [
}, {
{ value: parseFloat(item.value),
value: qxzbData[1].full, name: item.name
itemStyle: { },
normal: { {
color: '#07439C' value: 100,
itemStyle: {
color: '#07439C'
}
} }
} ]
} }
] })
}, })
{
name: qxzbData[2].name, //最外层圈
type: 'pie',
clockWise: false, //顺时加载
radius: ['45%', '55%'],
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 = () => {
ticketChart = echarts.init(document.getElementById('ticket'))
ticketChart.setOption(option)
window.addEventListener('resize', resize)
}
const resize = () => {
if (ticketChart) {
ticketChart.dispose()
ticketChart = null
init()
} }
} )
onMounted(() => {
init()
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -10,13 +10,6 @@
const homeData = inject('homeData') const homeData = inject('homeData')
watch(
() => homeData.value?.userPortrait?.provinceRate,
() => {
init()
}
)
const init = () => { const init = () => {
setOption({ setOption({
backgroundColor: 'transparent', backgroundColor: 'transparent',
@@ -138,6 +131,13 @@
] ]
}) })
} }
watch(
() => homeData.value?.userPortrait?.provinceRate,
() => {
init()
}
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -1,54 +1,77 @@
<template> <template>
<div class="select-box">
<Select v-model="params.id" :options="options" @on-change="initChart" />
</div>
<div class="area" :id="id" /> <div class="area" :id="id" />
</template> </template>
<script setup> <script setup>
import { useEchart } from '@/hooks/echart' import { useEchart } from '@/hooks/echart'
import { fitChartSize } from '@/utils/dataUtil' import { fitChartSize } from '@/utils/dataUtil'
import { getAreaApi } from '@/api/sentiment.js' import { getAreaApi, getSpotApi } from '@/api/sentiment.js'
const { id, setOption } = useEchart() const { id, setOption } = useEchart()
let params = reactive({ id: '' })
let option = null
let options = ref([])
const initChart = async () => { const initChart = async () => {
let res = await getAreaApi() let res = await getAreaApi(params)
setOption({ if (option) {
legend: { option.series[0].data = res.data
top: 'top', } else {
itemWidth: fitChartSize(30), option = {
itemHeight: fitChartSize(30), legend: {
textStyle: { top: '10%',
fontSize: fitChartSize(14), itemWidth: fitChartSize(20),
color: '#fff' itemHeight: fitChartSize(20),
} textStyle: {
}, color: '#fff',
series: [ fontSize: fitChartSize(20)
{ }
name: 'Nightingale Chart', },
type: 'pie', series: [
radius: ['30%', '60%'], {
center: ['50%', '56%'], name: '',
roseType: 'area', type: 'pie',
labelLine: { radius: ['30%', '60%'],
normal: { center: ['50%', '56%'],
show: true, roseType: 'area',
length: 1 labelLine: {
} normal: {
}, show: true,
itemStyle: { length: 1
borderRadius: fitChartSize(10) }
}, },
data: res.data itemStyle: {
} borderRadius: fitChartSize(10)
] },
}) data: res.data
}
]
}
}
setOption(option)
}
const getStop = async () => {
let res = await getSpotApi()
options.value = res.data
} }
onMounted(() => { onMounted(() => {
getStop()
initChart() initChart()
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.select-box {
position: absolute;
z-index: 9999;
top: vh(40);
right: vw(10);
}
.area { .area {
width: vw(740); width: vw(840);
height: vh(400); height: vh(400);
} }
</style> </style>

View File

@@ -38,7 +38,9 @@
<div class="flex mt-10"> <div class="flex mt-10">
<div class="box-2 mr-10 rela"> <div class="box-2 mr-10 rela">
<Title1 title="舆情指数" /> <Title1 title="舆情指数" />
<div class="dropdown"> <Dropdown :options="options" @on-change="onChange" /> </div> <div class="select-box">
<Select v-model="params.id" :options="options" @on-change="getLineChart" />
</div>
<Line <Line
:width="1560" :width="1560"
:height="400" :height="400"
@@ -47,7 +49,7 @@
:seriesConfig="{ smooth: false, symbol: 'circle' }" :seriesConfig="{ smooth: false, symbol: 'circle' }"
/> />
</div> </div>
<div class="box-1 mr-10"> <div class="box-1 mr-10 rela">
<Title1 title="地域分析" /> <Title1 title="地域分析" />
<Area /> <Area />
</div> </div>
@@ -80,9 +82,8 @@
let unsensitive = ref(0) let unsensitive = ref(0)
let stateList = ref([]) let stateList = ref([])
let options = ref([]) let options = ref([])
const onChange = (e) => { let params = reactive({ id: '' })
getLineChart(e.id)
}
const getStop = async () => { const getStop = async () => {
let res = await getSpotApi() let res = await getSpotApi()
options.value = res.data options.value = res.data
@@ -97,8 +98,8 @@
let res = await getStateApi() let res = await getStateApi()
stateList.value = res.data stateList.value = res.data
} }
const getLineChart = async (id) => { const getLineChart = async () => {
let res = await getLineChartApi({ id }) let res = await getLineChartApi(params)
xAxisData.value = res.data.data xAxisData.value = res.data.data
seriesData.value = res.data.series seriesData.value = res.data.series
} }
@@ -116,9 +117,10 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.dropdown { .select-box {
position: absolute; position: absolute;
z-index: 9999; z-index: 9999;
top: vh(10);
right: vw(10); right: vw(10);
} }
.container { .container {