Files
lc_frontend/src/views/screen/regionScreen.vue
2025-10-17 10:31:13 +08:00

1626 lines
43 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="dashboard-container">
<!-- 顶部标题栏 -->
<div class="header-container">
<div class="header-left">
<img class="back-img" @click="returnToHeadquarters" src="@/assets/images/screen/back_image.png" />
<div class="back-button" @click="openRegionSelector"> {{ selectedRegion }}
<span>···</span>
</div>
</div>
<h1 class="header-title">{{ selectedRegion }}综合监控大屏</h1>
<div class="date-wrapper">
<span style="margin-top: 6%;font-size: 0.9rem;">{{ currentDate }}</span>
<span style="margin-top: 6%;font-size: 0.9rem;">{{ currentWeek }}</span>
<span style="margin-top: 6%;font-size: 0.9rem;">{{ currentTime }}</span>
</div>
</div>
<!-- 天气预报 -->
<WeatherWarning />
<!-- 主内容区 -->
<div class="content-container">
<div class="left-wrapper">
<OverviewPanel :totalCount="dashboardData?.totalCount || 0"
:formalEmployeeCount="dashboardData?.formalEmployeeCount || 0"
:externalStaffCount="dashboardData?.externalStaffCount || 0" :visitorCount="dashboardData?.visitorCount || 0"
:parkStatistics="dashboardData?.parkStatistics" />
<RiskStatisticsPanel :riskStatistics="riskStatistics" :dangerDetail="dangerDetail" :park="parkValue"
@tab-change="handleRiskTabChange" :campus_id="query.campus_id" />
</div>
<div class="right-wrapper">
<HighRiskAlertPanel :alertData="dashboardData?.alertData" :alertDetails="dashboardData?.alertData.details"
:sourceIndex="sourceIndex" />
<TimeoutWorkOrderPanel :timeoutWorkOrders="dashboardData?.timeoutWorkOrders"
:alertDetails="dashboardData?.timeoutWorkOrders.details" :sourceIndex="sourceIndex" />
</div>
<HiddenDangerPanel :hiddenDangerData="dashboardData?.hiddenDangerData" />
</div>
</div>
<!-- 区域选择弹窗 -->
<RegionSelector v-model="regionSelectorVisible" :modelSelected="selectedPark" :regions="regionOption"
@change="onRegionChange" />
</template>
<script setup lang="ts">
import { getTableList, getTableData } from './report'
import { ref, onMounted, watch, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import RegionSelector from './components/RegionSelector.vue'
import WeatherWarning from './components/WeatherWarning.vue'
import { getDashboardData, 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'
import HiddenDangerPanel from './components/HiddenDangerPanel.vue'
// 类型定义
interface AlertItem {
describe: string
status: number
}
interface RegionItem {
name: string
code: string
}
type TabType = '危险作业' | '安全考核' | '安全、保密培训'
// 响应式数据
const currentDateTime = ref<string>('')
const currentDate = ref<string>('')
const currentWeek = ref<string>('')
const currentTime = ref<string>('')
const regionSelectorVisible = ref<boolean>(false)
const selectedRegion = ref<string>('')
const sourceIndex = ref<number>(1)
// 大屏数据
const dashboardData = ref<any>(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 dangerDetail = 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 router = useRouter()
const route = useRoute()
const regionOption = ref<RegionItem[]>([])
const selectedPark = ref<string>('')
const parkValue = ref<string>('')
// 定时器ID
const dashboardTimerId = ref<ReturnType<typeof setInterval> | null>(null)
const timeUpdateTimerId = ref<ReturnType<typeof setInterval> | null>(null)
const sourceIndexTimerId = ref<ReturnType<typeof setInterval> | null>(null)
// 返回上一级
const returnToHeadquarters = () => {
router.back()
}
const query = reactive({
pageNo: 1,
pageSize: 10000,
campus_id: "",
startDate: currentDateTime.value + " 00:00:00",
endDate: currentDateTime.value + " 23:59:59",
regionCode: ""
})
// 生命周期
onMounted(async () => {
updateTime()
timeUpdateTimerId.value = setInterval(updateTime, 1000)
//
if (typeof route.query.region === 'string') {
selectedRegion.value = route.query.region
query.regionCode = route.query.regionCode as string
parkValue.value = route.query.regionCode as string
console.log("区域>>>>>", selectedRegion.value, query.regionCode);
}
try {
let { records } = await getTableList(
'park_info_list'
)
// records = [
// {
// "region_id": "130601",
// "park_code": "1825468527486140416",
// "region": "北京",
// "park_name": "雄安新区总部"
// },
// {
// "region_id": "130601",
// "park_code": "1825468527486140417",
// "region": "北京",
// "park_name": "雄安地面站"
// },
// {
// "region_id": "130603",
// "park_code": "1825468527486140426",
// "region": "武汉",
// "park_name": "花山新区总部"
// }
// ]
if (records && records.length > 0) {
// 去重region字段使用Map来确保唯一性
const regionMap = new Map()
records.filter((el) => el.region_id == query.regionCode)
.map(el => { return el })
.forEach(el => {
if (!regionMap.has(el.park_name)) {
regionMap.set(el.park_name, {
name: el.park_name,
code: el.park_code // 使用region_code作为code
})
}
})
// // 转换为数组
regionOption.value = Array.from(regionMap.values())
console.log('regionOption.value>>>>', regionOption.value);
query.campus_id = regionOption.value.map(el => el.code).join()
}
} catch (error) {
console.error('初始化园区数据失败:', error)
}
// 初始化数据
await loadDashboardData()
// 启动定时器
timeOut1()
// 添加周期性动画演示
dashboardTimerId.value = setInterval(async () => {
await loadDashboardData()
}, 2 * 60 * 1000) // 每2分钟更新一次
})
// 组件卸载时清理定时器
onUnmounted(() => {
if (dashboardTimerId.value) {
clearInterval(dashboardTimerId.value)
dashboardTimerId.value = null
}
if (timeUpdateTimerId.value) {
clearInterval(timeUpdateTimerId.value)
timeUpdateTimerId.value = null
}
if (sourceIndexTimerId.value) {
clearInterval(sourceIndexTimerId.value)
sourceIndexTimerId.value = null
}
})
let isFirstLoad = ref<boolean>(true)
// 数据初始化方法
const loadDashboardData = async (): Promise<void> => {
const data = await getDashboardData()
if (isFirstLoad.value) {
console.log('第一次加载');
dashboardData.value = data
isFirstLoad.value = false
}
console.log('dashboardData.value>>>>>>>>>>', dashboardData.value);
try {
// 获取总体概览数据
getTableList('generalTotal', query).then(generalTotal => {
if (generalTotal.records && generalTotal.records.length > 0) {
dashboardData.value.totalCount = generalTotal.records.reduce((sum, item) => sum + Number(item.totalCount || 0), 0)
dashboardData.value.formalEmployeeCount = generalTotal.records.reduce((sum, item) => sum + Number(item.formalEmployeeCount || 0), 0)
dashboardData.value.externalStaffCount = generalTotal.records.reduce((sum, item) => sum + Number(item.externalStaffCount || 0), 0)
dashboardData.value.visitorCount = generalTotal.records.reduce((sum, item) => sum + Number(item.visitorCount || 0), 0)
}
updateAllCounts({// 使用动画更新数字
total: Number(dashboardData.value.totalCount),
formal: Number(dashboardData.value.formalEmployeeCount),
external: Number(dashboardData.value.externalStaffCount),
visitor: Number(dashboardData.value.visitorCount)
})
})
} catch (error) {
console.error('获取总体概览数据失败:', error)
}
try {
// 获取各园区统计数据
getTableList('parkscreen_user_info', query).then(parkscreen_user_info => {
if (parkscreen_user_info.records && parkscreen_user_info.records.length > 0) {
dashboardData.value.parkStatistics = parkscreen_user_info.records.map(el => {
return {
name: el.campus_name,
formal: el.formalEmployeeCount,
external: el.externalStaffCount,
visitor: el.visitorCount
}
})
}
})
} catch (error) {
console.error('获取各园区统计数据失败:', error)
}
try {
// 获取风险预警数据
getTableList('risk_alert_data', query).then(risk_alert_data => {
if (risk_alert_data.records && risk_alert_data.records.length > 0) {
dashboardData.value.alertData.total = risk_alert_data.records.reduce((sum, item) => sum + Number(item.total || 0), 0)
dashboardData.value.alertData.processed = risk_alert_data.records.reduce((sum, item) => sum + Number(item.processed || 0), 0)
dashboardData.value.alertData.pending = risk_alert_data.records.reduce((sum, item) => sum + Number(item.pending || 0), 0)
dashboardData.value.alertData.processing = risk_alert_data.records.reduce((sum, item) => sum + Number(item.processing || 0), 0)
}
}).catch(error => {
console.error('获取风险预警数据失败:', error)
})
} catch (error) {
console.error('获取风险预警数据失败:', error)
}
try {
// 获取风险预警详情数据
getTableList('risk_alert_detail', query).then(risk_alert_detail => {
if (risk_alert_detail.records && risk_alert_detail.records.length > 0) {
dashboardData.value.alertData.details = risk_alert_detail.records
}
}).catch(error => {
console.error('获取风险预警详情数据失败:', error)
})
} catch (error) {
console.error('获取风险预警详情数据失败:', error)
}
try {
// 获取超期工单数据
getTableList('timeout_work_order', query).then(timeout_work_order => {
if (timeout_work_order.records && timeout_work_order.records.length > 0) {
dashboardData.value.timeoutWorkOrders.total = timeout_work_order.records.length
dashboardData.value.timeoutWorkOrders.details = timeout_work_order.records
}
}).catch(error => {
console.error('获取超期工单数据失败:', error)
})
} catch (error) {
console.error('获取超期工单数据失败:', error)
}
handleRiskTabChange('危险作业')
handleHiddenDangerPannelData(query)
console.log('dashboardData.value>>>>>>>>>>', dashboardData.value);
}
const handleHiddenDangerPannelData = (query) => {
let _data = {
flag: false,
general: 0,
major: 0,
overdue: 0,
processed: 0,
processing: 0,
pending: 0
}
let _data2 = {
flag: false,
general: 0,
major: 0,
overdue: 0,
processed: 0,
processing: 0,
pending: 0
}
try {
dashboardData.value.hiddenDangerData.general = 0
dashboardData.value.hiddenDangerData.major = 0
dashboardData.value.hiddenDangerData.progress.overdue = 0
dashboardData.value.hiddenDangerData.progress.processed = 0
dashboardData.value.hiddenDangerData.progress.processing = 0
// 获取隐患排查治理数据
getTableList('risk_level_count', query).then(res => {
if (res.records && res.records.length > 0) {
_data.general = _data.general + Number(res.records[0].general_count)
_data.major = _data.major + Number(res.records[0].major_count)
// 获取隐患排查治理数据
getTableList('risk_status_count', query).then(res => {
if (res.records && res.records.length > 0) {
_data.overdue = _data.overdue + Number(res.records[0].overdueCnt)
_data.processed = _data.processed + Number(res.records[0].processedCnt)
_data.processing = _data.processing + Number(res.records[0].processingCnt)
_data.pending = _data.pending
_data.flag = true
if (_data2.flag) {
// 合并数据
console.log("请求系统和第三方成功,合并数据", _data, _data2);
let generalCnt = _data.general + _data2.general
let majorCnt = _data.major + _data2.major
dashboardData.value.hiddenDangerData.general = generalCnt
dashboardData.value.hiddenDangerData.major = majorCnt
let totalCnt = generalCnt + majorCnt
let overdueCnt = ((_data.overdue + _data2.overdue) / totalCnt * 100).toFixed(2)
let processedCnt = ((_data.processed + _data2.processed) / totalCnt * 100).toFixed(2)
let processingCnt = ((_data.processing + _data2.processing) / totalCnt * 100).toFixed(2)
let pendingCnt = ((_data.pending + _data2.pending) / totalCnt * 100).toFixed(2)
dashboardData.value.hiddenDangerData.progress = {
overdue: overdueCnt,
processed: processedCnt,
processing: processingCnt,
pending: pendingCnt,
}
} else {
console.log("请求系统成功,展示数据", _data, _data2);
dashboardData.value.hiddenDangerData.general = _data.general
dashboardData.value.hiddenDangerData.major = _data.major
let totalCnt = _data.general + _data.major
// 显示系统数据
dashboardData.value.hiddenDangerData.progress = {
overdue: (_data.overdue / totalCnt * 100).toFixed(2),
processed: (_data.processed / totalCnt * 100).toFixed(2),
processing: (_data.processing / totalCnt * 100).toFixed(2),
pending: (_data.pending / totalCnt * 100).toFixed(2),
}
}
}
})
}
})
// 获取隐患排查治理数据
getTableList('hidden_danger_investigation', query).then(res => {
if (res.records && res.records.length > 0) {
_data2.general = Number(res.records[0].general)
_data2.major = Number(res.records[0].major)
dashboardData.value.hiddenDangerData.safetyIndex = res.records[0].safetyIndex
// 获取隐患排查治理处理进度数据
getTableList('hidden_danger_process_progress', query).then(res => {
if (res.records && res.records.length > 0) {
_data2.flag = true
_data2.overdue = Number(res.records[0].overdue) / 100 * (_data2.general + _data2.major)
_data2.processed = Number(res.records[0].processed) / 100 * (_data2.general + _data2.major)
_data2.processing = Number(res.records[0].processing) / 100 * (_data2.general + _data2.major)
_data2.pending = Number(res.records[0].pending) / 100 * (_data2.general + _data2.major)
if (_data.flag) {
console.log("请求第三方和系统成功,合并数据", _data, _data2);
// 合并数据
let generalCnt = _data.general + _data2.general
let majorCnt = _data.major + _data2.major
dashboardData.value.hiddenDangerData.general = generalCnt
dashboardData.value.hiddenDangerData.major = majorCnt
let totalCnt = generalCnt + majorCnt
let overdueCnt = ((_data.overdue + _data2.overdue) / totalCnt * 100).toFixed(2)
let processedCnt = ((_data.processed + _data2.processed) / totalCnt * 100).toFixed(2)
let processingCnt = ((_data.processing + _data2.processing) / totalCnt * 100).toFixed(2)
let pendingCnt = ((_data.pending + _data2.pending) / totalCnt * 100).toFixed(2)
dashboardData.value.hiddenDangerData.progress = {
overdue: overdueCnt,
processed: processedCnt,
processing: processingCnt,
pending: pendingCnt,
}
} else {
//显示三方数据
console.log("请求第三方成功,展示数据", _data, _data2);
dashboardData.value.hiddenDangerData.general = _data2.general
dashboardData.value.hiddenDangerData.major = _data2.major
dashboardData.value.hiddenDangerData.progress = {
overdue: res.records[0].overdue,
processed: res.records[0].processed,
processing: res.records[0].processing,
pending: res.records[0].pending,
}
}
}
}).catch(error => {
console.error('获取隐患排查治理处理进度数据失败:', error)
})
}
}).catch(error => {
console.error('获取隐患排查治理数据失败:', error)
})
} catch (error) {
console.error('获取隐患排查治理数据失败:', error)
}
try {
// 获取隐患排查治理TOP3类型数据
getTableList('hidden_danger_top', query).then(res => {
if (res.records && res.records.length > 0) {
dashboardData.value.hiddenDangerData.top3Types = res.records
}
}).catch(error => {
console.error('获取隐患排查治理TOP3类型数据失败:', error)
})
} catch (error) {
console.error('获取隐患排查治理TOP3类型数据失败:', 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'
}
if (code == 'hazardous_operations') {
// 根据不同的tab请求不同的接口
getTableList(
code,
{ ...query, activeTab: tab }
).then(response => {
// 更新风险统计数据 - 传递完整的数组数据用于饼图显示
if (response.records && response.records.length > 0) {
dangerDetail.value = response.records
}
}).catch(error => {
console.error('获取风险统计数据失败:', error)
if (isFirstLoad.value) {
// dangerDetail.value = [
// {
// description: '2025-08-10 上午8:00 武汉园区隐患内容管理 状态 处理',
// alarm_level_code: 'general',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283937'
// },
// {
// description: '2025-08-10 上午8:00 武汉园区隐患内容管理 状态 处理',
// alarm_level_code: 'general',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283937'
// }, {
// description: '2025-08-10 上午8:00 武汉园区隐患内容管理 状态 处理',
// alarm_level_code: 'general',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283937'
// }, {
// description: '2025-08-10 上午8:00 武汉园区隐患内容管理 状态 处理',
// alarm_level_code: 'general',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283937'
// }, {
// description: '2025-08-10 上午8:00 武汉园区隐患内容管理 状态 处理',
// alarm_level_code: 'general',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283937'
// }, {
// description: '2025-08-10 上午8:00 武汉园区隐患内容管理 状态 处理',
// alarm_level_code: 'general',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283937'
// }
// ]
}
})
} else {
// 根据不同的tab请求不同的接口
getTableList(
code,
{ ...query, activeTab: tab }
).then(response => {
// 更新风险统计数据 - 传递完整的数组数据用于饼图显示
if (response.records && response.records.length > 0) {
riskStatistics.value = response.records
} else {
// 如果没有数据,设置默认值
riskStatistics.value = [
{
csmpus_name: "雄安新区总部",
finishCount: "0",
participateCount: "0"
}
]
}
}).catch(error => {
console.error('获取风险统计数据失败:', error)
})
}
} catch (error) {
console.error('获取风险统计数据失败:', error)
}
}
// 方法定义
const onRegionChange = (item: RegionItem): void => {
selectedPark.value = item.name
parkValue.value = item.code
router.push({
path: '/screen/company',
query: { parkName: item.name, parkCode: 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')
currentDate.value = `${year}${month}${day}`
currentDateTime.value = `${year}-${month}-${day}`
currentWeek.value = `${weekday}`
currentTime.value = `${hours}:${minutes}:${seconds}`
query.startDate = currentDateTime.value + " 00:00:00"
query.endDate = currentDateTime.value + " 23:59:59"
}
// 定时器
const timeOut1 = (): void => {
sourceIndexTimerId.value = setInterval(() => {
if (sourceIndex.value === 2) {
sourceIndex.value--
} else {
sourceIndex.value++
}
}, 3000)
}
</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;
}
}
.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;
}
}
.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;
}
}
.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%);
}
}
.date-wrapper {
display: flex;
margin-right: 20px;
flex: 1;
justify-content: end;
column-gap: 2vw;
}
.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>