Files
lc_frontend/src/views/screen/components/OverviewPanel.vue
2025-12-12 11:01:19 +08:00

295 lines
7.3 KiB
Vue

<template>
<div class="left-top">
<div class="panel-title">人员管理</div>
<img style="margin: 8px 0" src="@/assets/images/title_border_line_1.png" />
<div class="top-card">
<div class="top-card-left">
<div>
<img width="33px" src="@/assets/images/1_224520_821.png" />
</div>
<span>总计</span>
<div class="number-wrapper">
<span class="total-number" v-for="(digit, index) in totalCountDigits" :key="index">
{{ digit }}
</span>
</div>
<span></span>
</div>
<div class="top-card-right">
<div class="top-card-right-item">
<img width="18px" src="@/assets/images/v2_rel0n8.png" />
<span>正式员工</span>
<div class="type-number-wrapper" style="margin-left: 2vw">
<span class="type-number" v-for="(digit, index) in formalEmployeeDigits" :key="index">
{{ digit }}
</span>
</div>
<span></span>
</div>
<div class="top-card-right-item">
<img width="18px" src="@/assets/images/v2_rel0n23.png" />
<span>外协人员</span>
<div class="type-number-wrapper" style="margin-left: 1vw">
<span class="type-number" v-for="(digit, index) in externalStaffDigits" :key="index">
{{ digit }}
</span>
</div>
<span></span>
</div>
<div class="top-card-right-item">
<img width="18px" src="@/assets/images/24508_654.png" />
<span>访客</span>
<div class="type-number-wrapper">
<span class="type-number" v-for="(digit, index) in visitorDigits" :key="index">
{{ digit }}
</span>
</div>
<span></span>
</div>
</div>
</div>
<div class="bottom-card-overview">
<div class="bottom-card-title">
<span>各园区统计</span>
<img width="50%" style="margin: 8px 0" src="@/assets/images/line_1.png" />
</div>
<Echart :options="barChartOption" class="bar-chart" height="17.5vh" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch, computed } from 'vue'
import { rgbToHex } from '@/utils/color'
interface Props {
totalCount: number
formalEmployeeCount: number
externalStaffCount: number
visitorCount: number
parkStatistics?: Array<{
name: string
formal: number
external: number
visitor: number
}>
}
const props = defineProps<Props>()
const totalCountDigits = computed(() => String(props.totalCount).split('').map(Number))
const formalEmployeeDigits = computed(() => String(props.formalEmployeeCount).split('').map(Number))
const externalStaffDigits = computed(() => String(props.externalStaffCount).split('').map(Number))
const visitorDigits = computed(() => String(props.visitorCount).split('').map(Number))
// 图表引用
const barChartOption = ref({
legend: {
top: '10%',
right: '15%',
orient: 'vertical' as const,
textStyle: {
color: '#ffffff',
fontSize: '11px'
}
},
grid: {
left: '5%',
right: '30%',
top: '10%',
bottom: '15%'
},
xAxis: {
type: 'category' as const,
data: [],
axisLabel: {
color: '#ffffff',
fontSize: 10
},
axisLine: {
lineStyle: { color: '#334155' }
}
},
yAxis: {
type: 'value' as const,
axisLabel: {
color: '#ffffff',
fontSize: 10
},
axisLine: {
lineStyle: { color: '#334155' }
},
splitLine: {
lineStyle: { color: '#334155' }
}
},
series: [
{
name: '正式员工',
type: 'bar' as const,
data: [],
itemStyle: { color: rgbToHex(99, 196, 251) },
barWidth: '15%'
},
{
name: '外协人员',
type: 'bar' as const,
data: [],
itemStyle: { color: rgbToHex(251, 246, 85) },
barWidth: '15%'
},
{
name: '访客',
type: 'bar' as const,
data: [],
itemStyle: { color: rgbToHex(200, 69, 237) },
barWidth: '15%'
}
]
})
// 监听数据变化,更新图表
watch(() => props.parkStatistics, (newVal) => {
console.log('parkStatistics changed:', { newVal })
refreshCharts(newVal)
}, { deep: true })
// 更新图表数据
const refreshCharts = (parkStatistics): void => {
const option = { ...barChartOption.value }
option.xAxis.data = parkStatistics.map(park => park.name)
option.series[0].data = parkStatistics.map(park => park.formal)
option.series[1].data = parkStatistics.map(park => park.external)
option.series[2].data = parkStatistics.map(park => park.visitor)
barChartOption.value = option
}
// 组件挂载后初始化图表
onMounted(() => {
})
</script>
<style scoped lang="scss">
.left-top {
padding: 0 5px;
background-image: url('@/assets/images/screen/left_top_img.png'), url('@/assets/images/screen/left_center_img.png'), url('@/assets/images/screen/left_bottom_img.png');
background-position: top center, left center, bottom center;
background-repeat: no-repeat, no-repeat, no-repeat;
background-size: 100% 90px, cover, 100% 68px;
flex: 1;
.panel-title {
margin: 4px 20px 0;
font-size: 0.8rem;
font-weight: bold;
color: #fff;
}
.top-card {
display: flex;
padding: 0 20px;
column-gap: 15px;
font-size: 0.8rem;
.top-card-left {
display: flex;
height: 12vh;
min-width: 15vw;
padding: 0 10px;
background-image: url('@/assets/imgs/total_count_card_bg.png');
background-size: cover;
column-gap: 6px;
align-items: center;
.number-wrapper {
display: flex;
align-items: center;
gap: 2px;
font-size: 0.8rem;
color: #fff;
}
.total-number {
display: inline-block;
width: 26px;
height: 50px;
font-size: 0.8rem;
line-height: 50px;
color: #fff;
text-align: center;
background-color: rgb(177 74 201 / 100%);
border-radius: 4px;
transition: all 0.3s ease;
}
}
.top-card-right {
display: flex;
height: 12vh;
min-width: 20vw;
background-image: url('@/assets/imgs/staff_types_bg.png');
background-position: top center;
background-size: cover;
flex-direction: column;
justify-content: center;
row-gap: 4px;
.top-card-right-item {
display: flex;
align-items: center;
column-gap: 5px;
padding: 0 10px;
font-size: 0.7rem;
color: #fff;
.type-number-wrapper {
display: flex;
align-items: center;
gap: 2px;
font-size: 0.8rem;
color: #fff;
.type-number {
display: inline-block;
width: 14px;
height: 25px;
font-size: 0.9rem;
line-height: 25px;
color: #fff;
text-align: center;
background-color: #158e56;
border-radius: 2px;
transition: all 0.3s ease;
}
}
}
}
}
.bottom-card-overview {
display: flex;
flex-direction: column;
.bottom-card-title {
display: flex;
margin-top: 5px;
margin-left: -15%;
flex-direction: column;
align-items: center;
}
.bar-chart {
flex: 1;
width: 80%;
min-height: 17.5vh;
}
}
}
</style>