- 在 companyScreen.vue 中隐藏“隐患排查治理”标题 - 在 HiddenDangerPanel.vue 中将“分类风险”更改为“隐患等级” - 在 mainScreenV1.vue 中同步修改标题和子标题显示逻辑 - 在 parkScreen.vue 中隐藏“隐患排查治理”标题
1537 lines
37 KiB
Vue
1537 lines
37 KiB
Vue
<template>
|
||
<div class="dashboard-container">
|
||
<!-- 顶部标题栏 -->
|
||
<div class="header-container">
|
||
<div class="header-left">
|
||
<div class="back-button" @click="openRegionSelector"> 总部
|
||
<span>···</span>
|
||
</div>
|
||
</div>
|
||
<h1 class="header-title">总部综合监控大屏</h1>
|
||
<div class="header-right">
|
||
{{ currentTime }}
|
||
</div>
|
||
</div>
|
||
<!-- 天气预报 -->
|
||
<WeatherWarning />
|
||
<!-- 主内容区 -->
|
||
<div class="content-container">
|
||
<div class="left-wrapper">
|
||
<OverviewPanel
|
||
:totalCount="totalCount" :formalEmployeeCount="formalEmployeeCount"
|
||
:externalStaffCount="externalStaffCount" :visitorCount="visitorCount" :parkStatistics="parkStatistics" />
|
||
<RiskStatisticsPanel :riskStatistics="riskStatistics" @tab-change="handleRiskTabChange" />
|
||
</div>
|
||
<div class="right-wrapper">
|
||
<HighRiskAlertPanel
|
||
:alertData="dashboardData?.alertData" :alertDetails="sourceAcitve"
|
||
:sourceIndex="sourceIndex" />
|
||
<TimeoutWorkOrderPanel
|
||
:timeoutWorkOrders="dashboardData?.timeoutWorkOrders" :alertDetails="sourceAcitve"
|
||
:sourceIndex="sourceIndex" />
|
||
</div>
|
||
|
||
<div class="center-container">
|
||
<div class="center-content">
|
||
<!-- 隐患排查治理 -->
|
||
<span class="title"></span>
|
||
<img class="bottom-border-line" src="@/assets/images/title_border_line_1.png" />
|
||
<span class="sub-title">隐患等级</span>
|
||
<img width="50%" src="@/assets/images/line_1.png" />
|
||
<div class="type-wrapper">
|
||
<div class="type-item">
|
||
<span class="type-btn">重大</span>
|
||
<span class="type-num">{{ dashboardData?.hiddenDangerData.major || 0 }}</span>
|
||
</div>
|
||
<div class="type-item">
|
||
<span class="type-btn active">一般</span>
|
||
<span class="type-num">{{ dashboardData?.hiddenDangerData.general || 0 }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="clasic-wrapper">
|
||
<div class="clasic-item">
|
||
<span>处理进度</span>
|
||
<img width="100%" src="@/assets/images/line_1.png" />
|
||
</div>
|
||
<div class="clasic-item">
|
||
<span>Top3隐患类</span>
|
||
<img width="100%" src="@/assets/images/line_1.png" />
|
||
</div>
|
||
</div>
|
||
<div class="echart-wrapper">
|
||
<div class="lf-rt">
|
||
<Echart v-if="progressChart" :options="progressChart" class="progress-chart" height="80%" />
|
||
<div class="progress-legend">
|
||
<div class="legend-item"><span class="dot red"></span>已逾期</div>
|
||
<div class="legend-item"><span class="dot green"></span>已处理</div>
|
||
</div>
|
||
<div class="progress-legend">
|
||
<div class="legend-item"><span class="dot yellow"></span>待排查</div>
|
||
<div class="legend-item"><span class="dot blue"></span>处理中</div>
|
||
</div>
|
||
</div>
|
||
<div class="lf-rt">
|
||
<Echart v-if="classicChart" :options="classicChart" class="progress-chart" height="80%" />
|
||
<div class="progress-legend">
|
||
<div class="legend-item"><span class="dot red"></span>门禁</div>
|
||
<div class="legend-item"><span class="dot green"></span>消费</div>
|
||
<div class="legend-item"><span class="dot yellow"></span>巡检</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="safe-wrapper">
|
||
<span class="safe-title">
|
||
<img width="22" style="margin: 3px 5px 0 0" src="@/assets/images/ybp_icon.png" />
|
||
安全指数:
|
||
</span>
|
||
<span class="pending-count">{{ dashboardData?.hiddenDangerData.safetyIndex || 0 }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 区域选择弹窗 -->
|
||
<RegionSelector
|
||
v-model="regionSelectorVisible" :modelSelected="selectedRegion" :regions="regionOption"
|
||
@change="onRegionChange" />
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { batchGetTableList } from '@/api/design/report'
|
||
import { ref, onMounted, watch } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import type { EChartsOption } from 'echarts'
|
||
import RegionSelector from './components/RegionSelector.vue'
|
||
import WeatherWarning from './components/WeatherWarning.vue'
|
||
import { Echart } from '@/components/Echart'
|
||
import { getDashboardData, updateDashboardData, getAlertDetails, type DashboardData } from '@/api/dashboard'
|
||
|
||
import OverviewPanel from './components/OverviewPanel.vue'
|
||
import RiskStatisticsPanel from './components/RiskStatisticsPanel.vue'
|
||
import HighRiskAlertPanel from './components/HighRiskAlertPanel.vue'
|
||
import TimeoutWorkOrderPanel from './components/TimeoutWorkOrderPanel.vue'
|
||
|
||
// 类型定义
|
||
interface AlertItem {
|
||
text: string
|
||
error?: boolean
|
||
warn?: boolean
|
||
}
|
||
|
||
interface RegionItem {
|
||
name: string
|
||
code: string
|
||
}
|
||
|
||
type TabType = '高危作业' | '安全培训考试' | '安全培训考试'
|
||
|
||
// 响应式数据
|
||
const currentTime = ref<string>('')
|
||
const regionSelectorVisible = ref<boolean>(false)
|
||
const selectedRegion = ref<string>('')
|
||
const activeTab = ref<TabType>('高危作业')
|
||
const sourceIndex = ref<number>(1)
|
||
|
||
// 大屏数据
|
||
const dashboardData = ref<DashboardData | null>(null)
|
||
|
||
// 总体概览-总计数量数据
|
||
const totalCount = ref<number>(0)
|
||
const formalEmployeeCount = ref<number>(0)
|
||
const externalStaffCount = ref<number>(0)
|
||
const visitorCount = ref<number>(0)
|
||
// 总体概览-各园区统计
|
||
const parkStatistics = ref<any>()
|
||
// 高危作业/安全培训考试/安全培训考试
|
||
const riskStatistics = ref<any>()
|
||
|
||
// 动画相关的状态
|
||
const isAnimating = ref<boolean>(false)
|
||
const animationDuration = 2000 // 动画持续时间(毫秒)
|
||
|
||
// 数字变化时的闪烁效果
|
||
const flashNumbers = () => {
|
||
const numberElements = document.querySelectorAll('.total-number, .type-number')
|
||
numberElements.forEach((el) => {
|
||
el.classList.add('flash')
|
||
setTimeout(() => {
|
||
el.classList.remove('flash')
|
||
}, 300)
|
||
})
|
||
}
|
||
|
||
// 数字滚动动画方法
|
||
const animateNumber = (startValue: number, endValue: number, duration: number, callback: (value: number) => void) => {
|
||
const startTime = Date.now()
|
||
const difference = endValue - startValue
|
||
|
||
const animate = () => {
|
||
const currentTime = Date.now()
|
||
const elapsed = currentTime - startTime
|
||
const progress = Math.min(elapsed / duration, 1)
|
||
|
||
// 使用缓动函数让动画更自然
|
||
const easeOutQuart = 1 - Math.pow(1 - progress, 4)
|
||
const currentValue = Math.round(startValue + difference * easeOutQuart)
|
||
|
||
callback(currentValue)
|
||
|
||
if (progress < 1) {
|
||
requestAnimationFrame(animate)
|
||
}
|
||
}
|
||
|
||
animate()
|
||
}
|
||
|
||
// 批量更新所有数字的方法
|
||
const updateAllCounts = (counts: {
|
||
total?: number
|
||
formal?: number
|
||
external?: number
|
||
visitor?: number
|
||
}) => {
|
||
if (isAnimating.value) return
|
||
|
||
isAnimating.value = true
|
||
const promises: Promise<void>[] = []
|
||
|
||
if (counts.total !== undefined) {
|
||
promises.push(new Promise<void>((resolve) => {
|
||
const startValue = totalCount.value
|
||
animateNumber(startValue, counts.total || 0, animationDuration, (value) => {
|
||
totalCount.value = value
|
||
})
|
||
setTimeout(resolve, animationDuration)
|
||
}))
|
||
}
|
||
|
||
if (counts.formal !== undefined) {
|
||
promises.push(new Promise<void>((resolve) => {
|
||
const startValue = formalEmployeeCount.value
|
||
animateNumber(startValue, counts.formal || 0, animationDuration, (value) => {
|
||
formalEmployeeCount.value = value
|
||
})
|
||
setTimeout(resolve, animationDuration)
|
||
}))
|
||
}
|
||
|
||
if (counts.external !== undefined) {
|
||
promises.push(new Promise<void>((resolve) => {
|
||
const startValue = externalStaffCount.value
|
||
animateNumber(startValue, counts.external || 0, animationDuration, (value) => {
|
||
externalStaffCount.value = value
|
||
})
|
||
setTimeout(resolve, animationDuration)
|
||
}))
|
||
}
|
||
|
||
if (counts.visitor !== undefined) {
|
||
promises.push(new Promise<void>((resolve) => {
|
||
const startValue = visitorCount.value
|
||
animateNumber(startValue, counts.visitor || 0, animationDuration, (value) => {
|
||
visitorCount.value = value
|
||
})
|
||
setTimeout(resolve, animationDuration)
|
||
}))
|
||
}
|
||
|
||
Promise.all(promises).then(() => {
|
||
isAnimating.value = false
|
||
flashNumbers() // 更新完成后闪烁效果
|
||
})
|
||
}
|
||
|
||
|
||
|
||
// 图表引用
|
||
const barChart = ref<EChartsOption | null>(null)
|
||
const donutChart = ref<EChartsOption | null>(null)
|
||
const progressChart = ref<EChartsOption | null>(null)
|
||
const classicChart = ref<EChartsOption | null>(null)
|
||
|
||
// 告警数据
|
||
const sourceAcitve = ref<AlertItem[]>([])
|
||
|
||
// 路由
|
||
const router = useRouter()
|
||
|
||
// 监听器
|
||
watch(sourceIndex, async () => {
|
||
console.log('sourceIndex', sourceIndex.value)
|
||
try {
|
||
const type = sourceIndex.value === 2 ? 'timeout' : 'risk'
|
||
const details = await getAlertDetails(type)
|
||
sourceAcitve.value = details
|
||
} catch (error) {
|
||
console.error('获取告警详情失败:', error)
|
||
}
|
||
})
|
||
|
||
const regionOption = ref<RegionItem[]>([])
|
||
// 生命周期
|
||
onMounted(async () => {
|
||
updateTime()
|
||
setInterval(updateTime, 1000)
|
||
|
||
try {
|
||
|
||
let { park_info_list } = await batchGetTableList(
|
||
'park_info_list'
|
||
)
|
||
console.log("park_info_list>>>>", park_info_list.records.map(el => el.region));
|
||
// 更新为新的数据格式
|
||
regionOption.value = park_info_list.records.map(el => ({
|
||
name: el.region,
|
||
code: el.region_code || el.code || String(Math.random()).slice(2, 6) // 如果没有code字段,生成一个随机code
|
||
}))
|
||
|
||
// 初始化数据
|
||
await loadDashboardData()
|
||
|
||
// 初始化图表
|
||
initCharts()
|
||
|
||
// 启动定时器
|
||
timeOut1()
|
||
|
||
// 添加周期性动画演示
|
||
setInterval(async () => {
|
||
await loadDashboardData()
|
||
}, 15000) // 每15秒更新一次
|
||
|
||
} catch (error) {
|
||
console.error('初始化大屏数据失败:', error)
|
||
}
|
||
})
|
||
|
||
// 数据初始化方法
|
||
const loadDashboardData = async (): Promise<void> => {
|
||
try {
|
||
const data = await getDashboardData()
|
||
dashboardData.value = data
|
||
|
||
let { generalTotal, parkscreen_user_info, hidden_danger_investigation } = await batchGetTableList(
|
||
'parkscreen_user_info,generalTotal,hidden_danger_investigation'
|
||
)
|
||
// -----------------------设置总体概览数据-----------------------
|
||
// 使用动画更新数字
|
||
updateAllCounts({
|
||
total: Number(generalTotal.records[0].totalCount),
|
||
formal: Number(generalTotal.records[0].formalEmployeeCount),
|
||
external: Number(generalTotal.records[0].externalStaffCount),
|
||
visitor: Number(generalTotal.records[0].visitorCount)
|
||
})
|
||
// 更新echart图表
|
||
parkStatistics.value = parkscreen_user_info.records
|
||
// -----------------------设置高危作业数据-----------------------
|
||
handleRiskTabChange('高危作业')
|
||
// -----------------------隐患排查治理-----------------------
|
||
dashboardData.value.hiddenDangerData.general = hidden_danger_investigation.records[0].general
|
||
dashboardData.value.hiddenDangerData.major = hidden_danger_investigation.records[0].major
|
||
dashboardData.value.hiddenDangerData.safetyIndex = hidden_danger_investigation.records[0].safetyIndex
|
||
let { hidden_danger_process_progress } = await batchGetTableList(
|
||
'hidden_danger_process_progress'
|
||
)
|
||
dashboardData.value.hiddenDangerData.progress = hidden_danger_process_progress.records[0]
|
||
|
||
|
||
|
||
} catch (error) {
|
||
console.error('初始化大屏数据失败:', error)
|
||
}
|
||
}
|
||
|
||
// 处理风险统计tab切换
|
||
const handleRiskTabChange = async (tab: TabType) => {
|
||
console.log('Tab changed to:', tab)
|
||
try {
|
||
let code = ''
|
||
switch (tab) {
|
||
case '高危作业':
|
||
code = 'hazardous_operations'
|
||
break
|
||
case '安全培训考试':
|
||
code = 'safety_assessment'
|
||
break
|
||
case '安全培训考试':
|
||
code = 'security_training'
|
||
break
|
||
default:
|
||
code = 'hazardous_operations'
|
||
}
|
||
// 根据不同的tab请求不同的接口
|
||
let { [code]: { records } } = await batchGetTableList(
|
||
code,
|
||
{ activeTab: tab }
|
||
)
|
||
// 更新风险统计数据
|
||
if (records && records.length > 0) {
|
||
riskStatistics.value = records[0]
|
||
} else {
|
||
// 如果没有数据,设置默认值
|
||
riskStatistics.value = {
|
||
overdue: 0,
|
||
processed: 0,
|
||
pending: 0,
|
||
processing: 0
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('获取风险统计数据失败:', error)
|
||
}
|
||
}
|
||
|
||
// 方法定义
|
||
const onRegionChange = (item: RegionItem): void => {
|
||
selectedRegion.value = item.name
|
||
router.push({
|
||
path: '/screen/region',
|
||
query: { region: item.name, regionCode: item.code }
|
||
})
|
||
}
|
||
|
||
const openRegionSelector = (): void => {
|
||
regionSelectorVisible.value = true
|
||
}
|
||
|
||
const updateTime = (): void => {
|
||
const now = new Date()
|
||
const year = now.getFullYear()
|
||
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||
const day = String(now.getDate()).padStart(2, '0')
|
||
const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
|
||
const weekday = weekdays[now.getDay()]
|
||
const hours = String(now.getHours()).padStart(2, '0')
|
||
const minutes = String(now.getMinutes()).padStart(2, '0')
|
||
const seconds = String(now.getSeconds()).padStart(2, '0')
|
||
|
||
currentTime.value = `${year}年${month}月${day}日 ${weekday} ${hours}:${minutes}:${seconds}`
|
||
}
|
||
|
||
const timeOut1 = (): void => {
|
||
setInterval(() => {
|
||
if (sourceIndex.value === 2) {
|
||
sourceIndex.value--
|
||
} else {
|
||
sourceIndex.value++
|
||
}
|
||
}, 3000)
|
||
}
|
||
|
||
// 图表初始化方法
|
||
const initCharts = (): void => {
|
||
if (dashboardData.value) {
|
||
initDonutChart()
|
||
initProgressChart()
|
||
initClassicChart()
|
||
}
|
||
}
|
||
|
||
// 总体概览-风险统计-饼图
|
||
const initDonutChart = (): void => {
|
||
if (dashboardData.value) {
|
||
const option: EChartsOption = {
|
||
legend: {
|
||
bottom: '10%',
|
||
right: '0%',
|
||
orient: 'vertical' as const,
|
||
textStyle: {
|
||
color: '#ffffff',
|
||
fontSize: '11px'
|
||
}
|
||
},
|
||
series: [
|
||
{
|
||
type: 'pie' as const,
|
||
radius: ['25%', '55%'],
|
||
center: ['50%', '50%'],
|
||
data: [
|
||
{ value: dashboardData.value.riskStatistics.overdue, name: '已逾期', itemStyle: { color: '#ef4444' } },
|
||
{ value: dashboardData.value.riskStatistics.processed, name: '已处理', itemStyle: { color: '#10b981' } },
|
||
{ value: dashboardData.value.riskStatistics.pending, name: '待排查', itemStyle: { color: '#eab308' } },
|
||
{ value: dashboardData.value.riskStatistics.processing, name: '处理中', itemStyle: { color: '#3b82f6' } },
|
||
{ value: dashboardData.value.riskStatistics.other, name: '其他', itemStyle: { color: '#f59e0b' } }
|
||
],
|
||
label: {
|
||
show: true,
|
||
position: 'outside' as const,
|
||
formatter: function (params: any) {
|
||
return params.value + ' ' + params.percent + '%'
|
||
},
|
||
fontSize: 12,
|
||
color: '#ffffff',
|
||
fontWeight: 'bold'
|
||
},
|
||
labelLine: {
|
||
show: true,
|
||
length: 15,
|
||
length2: 10,
|
||
lineStyle: {
|
||
color: '#64748b',
|
||
width: 1
|
||
}
|
||
}
|
||
}
|
||
]
|
||
}
|
||
|
||
donutChart.value = option
|
||
}
|
||
}
|
||
|
||
// 隐患处理进度
|
||
const initProgressChart = (): void => {
|
||
if (dashboardData.value) {
|
||
const progressOption: EChartsOption = {
|
||
series: [
|
||
{
|
||
type: 'pie' as const,
|
||
radius: '60%',
|
||
center: ['50%', '50%'],
|
||
left: 0,
|
||
top: 0,
|
||
bottom: 0,
|
||
data: [
|
||
{ value: dashboardData.value.hiddenDangerData.progress.overdue, name: '已逾期' },
|
||
{ value: dashboardData.value.hiddenDangerData.progress.processed, name: '已处理' },
|
||
{ value: dashboardData.value.hiddenDangerData.progress.pending, name: '待排查' },
|
||
{ value: dashboardData.value.hiddenDangerData.progress.processing, name: '处理中' },
|
||
],
|
||
label: {
|
||
alignTo: 'edge' as const,
|
||
formatter: '{time|{c} %}\n',
|
||
minMargin: 5,
|
||
edgeDistance: 10,
|
||
lineHeight: 15,
|
||
rich: {
|
||
time: {
|
||
fontSize: 10,
|
||
color: '#fff'
|
||
}
|
||
}
|
||
},
|
||
labelLine: {
|
||
length: 5,
|
||
length2: 0,
|
||
maxSurfaceAngle: 10
|
||
},
|
||
labelLayout: function (params: any) {
|
||
const isLeft = params.labelRect.x < 200; // 使用固定宽度值
|
||
const points = params.labelLinePoints;
|
||
points[2][0] = isLeft
|
||
? params.labelRect.x
|
||
: params.labelRect.x + params.labelRect.width;
|
||
return {
|
||
labelLinePoints: points
|
||
};
|
||
},
|
||
}
|
||
]
|
||
}
|
||
progressChart.value = progressOption
|
||
}
|
||
}
|
||
|
||
// Top3隐患类
|
||
const initClassicChart = (): void => {
|
||
if (dashboardData.value) {
|
||
const classicOption: EChartsOption = {
|
||
series: [
|
||
{
|
||
type: 'pie' as const,
|
||
roseType: 'radius' as const,
|
||
radius: [30, 50],
|
||
center: ['50%', '50%'],
|
||
left: 0,
|
||
top: 0,
|
||
bottom: 0,
|
||
data: [
|
||
{ value: dashboardData.value.hiddenDangerData.top3Types.access, name: '门禁' },
|
||
{ value: dashboardData.value.hiddenDangerData.top3Types.consumption, name: '消费' },
|
||
{ value: dashboardData.value.hiddenDangerData.top3Types.inspection, name: '巡检' },
|
||
],
|
||
label: {
|
||
alignTo: 'edge' as const,
|
||
formatter: '{time|{c}}\n',
|
||
minMargin: 5,
|
||
edgeDistance: 10,
|
||
lineHeight: 15,
|
||
rich: {
|
||
time: {
|
||
fontSize: 10,
|
||
color: '#fff'
|
||
}
|
||
}
|
||
},
|
||
labelLine: {
|
||
length: 5,
|
||
length2: 0,
|
||
maxSurfaceAngle: 10
|
||
},
|
||
labelLayout: function (params: any) {
|
||
const isLeft = params.labelRect.x < 200; // 使用固定宽度值
|
||
const points = params.labelLinePoints;
|
||
points[2][0] = isLeft
|
||
? params.labelRect.x
|
||
: params.labelRect.x + params.labelRect.width;
|
||
return {
|
||
labelLinePoints: points
|
||
};
|
||
},
|
||
}
|
||
]
|
||
}
|
||
classicChart.value = classicOption
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
/* 响应式设计 - 自动适应不同屏幕尺寸 */
|
||
@media (width <=1200px) {
|
||
.dashboard-container {
|
||
.content-container {
|
||
column-gap: 4vw;
|
||
|
||
.center-container {
|
||
width: 60vh;
|
||
height: 60vh;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@media (width <=1024px) {
|
||
.dashboard-container {
|
||
.header-container {
|
||
.header-title {
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.header-left .back-button {
|
||
min-width: 8vw;
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.header-right {
|
||
font-size: 0.8rem;
|
||
}
|
||
}
|
||
|
||
.content-container {
|
||
column-gap: 3vw;
|
||
padding: 0 8px 15px;
|
||
|
||
.left-wrapper {
|
||
.left-top .top-card {
|
||
.top-card-left {
|
||
min-width: 18vw;
|
||
}
|
||
|
||
.top-card-right {
|
||
min-width: 25vw;
|
||
}
|
||
}
|
||
|
||
.left-bottom .donut-chart-with-labels {
|
||
width: 35vw;
|
||
height: 35vh;
|
||
}
|
||
}
|
||
|
||
.center-container {
|
||
width: 55vh;
|
||
height: 55vh;
|
||
|
||
.center-content {
|
||
.title {
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.sub-title {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.type-wrapper {
|
||
width: 85%;
|
||
|
||
.type-item .type-btn {
|
||
padding: 2px 25px;
|
||
font-size: 0.65rem;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@media (width <=768px) {
|
||
.dashboard-container {
|
||
font-size: 0.7rem;
|
||
|
||
.header-container {
|
||
height: 70px;
|
||
|
||
.header-left {
|
||
line-height: 70px;
|
||
|
||
.back-button {
|
||
min-width: 12vw;
|
||
padding: 3px 12px;
|
||
font-size: 0.7rem;
|
||
}
|
||
}
|
||
|
||
.header-title {
|
||
font-size: 1rem;
|
||
line-height: 35px;
|
||
}
|
||
|
||
.header-right {
|
||
margin-right: 15px;
|
||
font-size: 0.7rem;
|
||
line-height: 70px;
|
||
}
|
||
}
|
||
|
||
.weather-warning {
|
||
margin-left: 5%;
|
||
font-size: 0.7rem;
|
||
line-height: 35px;
|
||
}
|
||
|
||
.content-container {
|
||
column-gap: 2vw;
|
||
padding: 0 5px 10px;
|
||
|
||
.panel-title {
|
||
margin: 3px 15px 0;
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.left-wrapper {
|
||
.left-top .top-card {
|
||
padding: 0 15px;
|
||
column-gap: 10px;
|
||
|
||
.top-card-left {
|
||
height: 10vh;
|
||
min-width: 20vw;
|
||
|
||
.total-number {
|
||
width: 22px;
|
||
height: 40px;
|
||
font-size: 0.7rem;
|
||
line-height: 40px;
|
||
}
|
||
}
|
||
|
||
.top-card-right {
|
||
height: 10vh;
|
||
min-width: 30vw;
|
||
|
||
.top-card-right-item {
|
||
font-size: 0.65rem;
|
||
|
||
.type-number-wrapper .type-number {
|
||
width: 12px;
|
||
height: 20px;
|
||
font-size: 0.8rem;
|
||
line-height: 20px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.left-bottom {
|
||
.tabs .tab {
|
||
padding: 1px 8px;
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.donut-chart-with-labels {
|
||
width: 40vw;
|
||
height: 40vh;
|
||
margin-left: 1vw;
|
||
}
|
||
}
|
||
}
|
||
|
||
.right-wrapper {
|
||
|
||
.right-top,
|
||
.right-bottom {
|
||
.tip-container {
|
||
width: 80%;
|
||
height: 70px;
|
||
|
||
.tip-image img {
|
||
width: 60px;
|
||
height: 60px;
|
||
}
|
||
|
||
.tip-content .col-item {
|
||
font-size: 0.65rem;
|
||
}
|
||
}
|
||
|
||
.list-content {
|
||
width: 75%;
|
||
|
||
.list .list-item {
|
||
padding: 0.4vh 0.3vw;
|
||
font-size: 0.65rem;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.center-container {
|
||
top: 60%;
|
||
width: 50vh;
|
||
height: 50vh;
|
||
|
||
.center-content {
|
||
.title {
|
||
margin-top: 1.5vh;
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.sub-title {
|
||
font-size: 0.65rem;
|
||
}
|
||
|
||
.type-wrapper {
|
||
width: 90%;
|
||
margin-top: 0.3vh;
|
||
|
||
.type-item {
|
||
.type-btn {
|
||
padding: 2px 20px;
|
||
font-size: 0.6rem;
|
||
}
|
||
|
||
.type-num {
|
||
font-size: 0.9rem;
|
||
}
|
||
}
|
||
}
|
||
|
||
.clasic-wrapper {
|
||
width: 90%;
|
||
margin-top: 1vh;
|
||
|
||
.clasic-item {
|
||
font-size: 0.7rem;
|
||
}
|
||
}
|
||
|
||
.echart-wrapper {
|
||
width: 90%;
|
||
|
||
.lf-rt .progress-legend .legend-item {
|
||
font-size: 0.6rem;
|
||
}
|
||
}
|
||
|
||
.safe-wrapper {
|
||
width: 50%;
|
||
|
||
.safe-title {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.pending-count {
|
||
font-size: 1.4rem;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@media (width <=480px) {
|
||
.dashboard-container {
|
||
.header-container {
|
||
height: 60px;
|
||
|
||
.header-left {
|
||
line-height: 60px;
|
||
|
||
.back-button {
|
||
min-width: 15vw;
|
||
padding: 2px 10px;
|
||
font-size: 0.65rem;
|
||
}
|
||
}
|
||
|
||
.header-title {
|
||
font-size: 0.9rem;
|
||
line-height: 30px;
|
||
}
|
||
|
||
.header-right {
|
||
margin-right: 10px;
|
||
font-size: 0.65rem;
|
||
line-height: 60px;
|
||
}
|
||
}
|
||
|
||
.weather-warning {
|
||
margin-left: 3%;
|
||
font-size: 0.65rem;
|
||
line-height: 30px;
|
||
}
|
||
|
||
.content-container {
|
||
column-gap: 1vw;
|
||
padding: 0 3px 8px;
|
||
|
||
.left-wrapper {
|
||
.left-top .top-card {
|
||
padding: 0 10px;
|
||
column-gap: 8px;
|
||
|
||
.top-card-left {
|
||
height: 8vh;
|
||
min-width: 25vw;
|
||
|
||
.total-number {
|
||
width: 18px;
|
||
height: 35px;
|
||
font-size: 0.65rem;
|
||
line-height: 35px;
|
||
}
|
||
}
|
||
|
||
.top-card-right {
|
||
height: 8vh;
|
||
min-width: 35vw;
|
||
|
||
.top-card-right-item {
|
||
font-size: 0.6rem;
|
||
|
||
.type-number-wrapper .type-number {
|
||
width: 10px;
|
||
height: 18px;
|
||
font-size: 0.7rem;
|
||
line-height: 18px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.left-bottom .donut-chart-with-labels {
|
||
width: 45vw;
|
||
height: 45vh;
|
||
margin-left: 0.5vw;
|
||
}
|
||
}
|
||
|
||
.center-container {
|
||
top: 65%;
|
||
width: 45vh;
|
||
height: 45vh;
|
||
|
||
.center-content {
|
||
.title {
|
||
margin-top: 1vh;
|
||
font-size: 0.65rem;
|
||
}
|
||
|
||
.sub-title {
|
||
font-size: 0.6rem;
|
||
}
|
||
|
||
.type-wrapper {
|
||
width: 95%;
|
||
|
||
.type-item .type-btn {
|
||
padding: 1px 15px;
|
||
font-size: 0.55rem;
|
||
}
|
||
}
|
||
|
||
.echart-wrapper {
|
||
width: 95%;
|
||
}
|
||
|
||
.safe-wrapper {
|
||
width: 60%;
|
||
|
||
.safe-title {
|
||
font-size: 0.65rem;
|
||
}
|
||
|
||
.pending-count {
|
||
font-size: 1.2rem;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.dashboard-container {
|
||
position: relative;
|
||
display: flex;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
font-size: 0.8rem;
|
||
color: #fff;
|
||
background: url('@/assets/images/v2_rel0n6.png');
|
||
background-color: #030e22;
|
||
background-size: cover;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.dot.red {
|
||
background-color: #ef4444;
|
||
}
|
||
|
||
.dot.green {
|
||
background-color: #10b981;
|
||
}
|
||
|
||
.dot.yellow {
|
||
background-color: #eab308;
|
||
}
|
||
|
||
.dot.blue {
|
||
background-color: #3b82f6;
|
||
}
|
||
|
||
.dot.cyan {
|
||
background-color: #06b6d4;
|
||
}
|
||
|
||
.dot.purple {
|
||
background-color: #8b5cf6;
|
||
}
|
||
|
||
.dot.orange {
|
||
background-color: #f59e0b;
|
||
}
|
||
|
||
.header-container {
|
||
display: flex;
|
||
height: 80px;
|
||
background: url('@/assets/images/top_bg.png') no-repeat;
|
||
background-size: 100%;
|
||
|
||
.header-left {
|
||
display: flex;
|
||
padding-left: 1vw;
|
||
line-height: 80px;
|
||
flex: 1;
|
||
align-items: center;
|
||
|
||
.back-img {
|
||
height: 3vh;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.back-button {
|
||
display: inline-flex;
|
||
height: 2vh;
|
||
min-width: 6vw;
|
||
padding: 4px 16px;
|
||
margin-left: 0.5vw;
|
||
font-size: 0.9rem;
|
||
cursor: pointer;
|
||
background: rgb(13 24 84 / 80%);
|
||
border: 1px solid rgb(59 130 246 / 40%);
|
||
transition: all 0.3s ease;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.back-button:hover {
|
||
background: rgb(59 130 246 / 30%);
|
||
border-color: rgb(59 130 246 / 60%);
|
||
}
|
||
}
|
||
|
||
.header-right {
|
||
margin-right: 20px;
|
||
font-size: 0.9rem;
|
||
line-height: 80px;
|
||
text-align: right;
|
||
flex: 1;
|
||
}
|
||
|
||
.header-title {
|
||
font-size: 1.3rem;
|
||
font-weight: bold;
|
||
line-height: 40px;
|
||
color: #fff;
|
||
text-align: center;
|
||
flex: 1;
|
||
}
|
||
}
|
||
|
||
.weather-warning {
|
||
margin-left: 10%;
|
||
line-height: 45px;
|
||
}
|
||
|
||
.content-container {
|
||
position: relative;
|
||
display: flex;
|
||
width: calc(100vw - 20px);
|
||
height: calc(100vh - 105px);
|
||
padding: 0 10px 20px;
|
||
column-gap: 7vw;
|
||
|
||
.panel-title {
|
||
margin: 4px 20px 0;
|
||
font-size: 0.8rem;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
}
|
||
|
||
.bottom-card {
|
||
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;
|
||
}
|
||
}
|
||
|
||
.left-wrapper {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
row-gap: 1rem;
|
||
height: 100%;
|
||
|
||
.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;
|
||
|
||
/* 设置重复方式 */
|
||
.top-card {
|
||
display: flex;
|
||
padding: 0 20px;
|
||
column-gap: 15px;
|
||
font-size: 0.8rem;
|
||
|
||
.top-card-left {
|
||
@keyframes numberGlow {
|
||
0% {
|
||
box-shadow: 0 0 5px rgb(177 74 201 / 50%);
|
||
}
|
||
|
||
100% {
|
||
box-shadow: 0 0 20px rgb(177 74 201 / 90%);
|
||
}
|
||
}
|
||
|
||
@keyframes flashEffect {
|
||
0% {
|
||
background-color: rgb(177 74 201 / 100%);
|
||
}
|
||
|
||
50% {
|
||
background-color: rgb(255 255 255 / 100%);
|
||
}
|
||
|
||
100% {
|
||
background-color: rgb(177 74 201 / 100%);
|
||
}
|
||
}
|
||
|
||
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;
|
||
animation: numberGlow 2s ease-in-out infinite alternate;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.total-number:hover {
|
||
transform: scale(1.1);
|
||
box-shadow: 0 0 15px rgb(177 74 201 / 80%);
|
||
}
|
||
|
||
.flash {
|
||
animation: flashEffect 0.3s ease-in-out;
|
||
}
|
||
}
|
||
|
||
.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 {
|
||
@keyframes typeNumberPulse {
|
||
0% {
|
||
transform: scale(1);
|
||
}
|
||
|
||
50% {
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
100% {
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
|
||
@keyframes typeFlashEffect {
|
||
0% {
|
||
background-color: #1afb8f;
|
||
}
|
||
|
||
50% {
|
||
background-color: rgb(255 255 255 / 100%);
|
||
}
|
||
|
||
100% {
|
||
background-color: #1afb8f;
|
||
}
|
||
}
|
||
|
||
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: #1afb8f;
|
||
border-radius: 2px;
|
||
animation: typeNumberPulse 1.5s ease-in-out infinite;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.type-number:hover {
|
||
background-color: #16d47a;
|
||
transform: scale(1.2);
|
||
}
|
||
|
||
.flash {
|
||
animation: typeFlashEffect 0.3s ease-in-out;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
.left-bottom {
|
||
background-image:
|
||
url('@/assets/images/screen/left_top_2_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;
|
||
|
||
/* 设置重复方式 */
|
||
.tabs {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
column-gap: 8px;
|
||
}
|
||
|
||
.tab {
|
||
padding: 2px 10px;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
border-radius: 4px;
|
||
transition: all 0.2s ease-in-out;
|
||
user-select: none;
|
||
}
|
||
|
||
.tab:hover {
|
||
color: #1afb8f;
|
||
}
|
||
|
||
.tab.active {
|
||
color: #1afb8f;
|
||
background: rgb(26 251 143 / 12%);
|
||
border: 1px solid rgb(26 251 143 / 35%);
|
||
}
|
||
|
||
.divider {
|
||
margin: 0 2px;
|
||
color: #94a3b8;
|
||
}
|
||
|
||
.donut-chart-with-labels {
|
||
width: 30vw;
|
||
height: 30vh;
|
||
margin-left: 2vw;
|
||
}
|
||
}
|
||
}
|
||
|
||
.right-wrapper {
|
||
display: flex;
|
||
height: 100%;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
row-gap: 1rem;
|
||
|
||
|
||
|
||
|
||
}
|
||
|
||
.center-container {
|
||
position: fixed;
|
||
top: 55%;
|
||
left: 50%;
|
||
z-index: 1;
|
||
display: flex;
|
||
width: 65vh;
|
||
height: 65vh;
|
||
color: #fff;
|
||
background-image: url('@/assets/images/circle_bg.png');
|
||
background-size: cover;
|
||
transform: translate(-50%, -50%);
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.center-content {
|
||
display: flex;
|
||
// background: rgba(255, 22, 1, 0.3);
|
||
width: 77%;
|
||
height: 77%;
|
||
overflow: hidden;
|
||
border-radius: 50%;
|
||
align-items: center;
|
||
flex-direction: column;
|
||
|
||
.title {
|
||
margin-top: 2vh;
|
||
font-size: 0.9rem;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.bottom-border-line {
|
||
width: 20%;
|
||
margin: 1vh 0 1.2vh;
|
||
}
|
||
|
||
.sub-title {
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.type-wrapper {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
width: 80%;
|
||
font-size: 0.8rem;
|
||
color: #fff;
|
||
|
||
.type-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-top: 0.5vh;
|
||
|
||
.type-btn {
|
||
padding: 2px 30px;
|
||
margin-bottom: 5px;
|
||
font-size: 0.7rem;
|
||
background-color: #d97706;
|
||
border-radius: 15px;
|
||
|
||
&.active {
|
||
background-color: #059669;
|
||
}
|
||
}
|
||
|
||
.type-num {
|
||
font-size: 1rem;
|
||
}
|
||
}
|
||
}
|
||
|
||
.clasic-wrapper {
|
||
display: flex;
|
||
width: 80%;
|
||
margin-top: 1.2vh;
|
||
font-size: 0.8rem;
|
||
color: #fff;
|
||
justify-content: space-around;
|
||
|
||
.clasic-item {
|
||
display: flex;
|
||
width: 45%;
|
||
margin-top: 0.6vh;
|
||
margin-bottom: -1vh;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
|
||
img {
|
||
width: 100%;
|
||
height: 2px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.echart-wrapper {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 85%;
|
||
height: 75%;
|
||
|
||
.lf-rt {
|
||
display: flex;
|
||
width: 50%;
|
||
height: 100%;
|
||
flex-direction: column;
|
||
|
||
.progress-chart {
|
||
display: flex;
|
||
width: 100%;
|
||
height: 80%;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.progress-legend {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 0.7rem;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.safe-wrapper {
|
||
display: flex;
|
||
width: 40%;
|
||
height: 20%;
|
||
margin-bottom: 5%;
|
||
align-items: center;
|
||
column-gap: 1vw;
|
||
|
||
.safe-title {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-left: 1vw;
|
||
}
|
||
|
||
.pending-count {
|
||
margin-bottom: 0.7vh;
|
||
font-size: 1.6rem;
|
||
font-weight: 500;
|
||
color: yellow;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 全局样式 */
|
||
</style>
|