Files
lc_frontend/src/views/screen/companyScreen.vue

1874 lines
58 KiB
Vue
Raw Normal View History

2025-10-17 10:31:13 +08:00
<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"> {{ selectedPark }} </div>
</div>
<h1 class="header-title">{{ selectedPark }}综合监控大屏</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">
<!-- 左上区域 -->
<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">
<div class="bottom-card-title">
<!-- 隐患排查治理 标题需要隐藏 2025-10-31 -->
<span></span>
2025-10-17 10:31:13 +08:00
<img width="50%" style="margin: 8px 0" src="@/assets/images/line_1.png" />
</div>
<div class="type-wrapper">
<div class="type-item">
<span class="type-btn yellow">重大</span>
<span class="type-num">{{ mockData.hiddenDangerData.severityCount }}</span>
</div>
<div class="type-item">
<span class="type-btn green">一般</span>
<span class="type-num">{{ mockData.hiddenDangerData.generalCount }}</span>
</div>
<!-- <div class="type-item">
<span class="type-btn blue">较大</span>
<span class="type-num">{{ mockData.hiddenDangerData.majorCount }}</span>
</div> -->
</div>
<div class="hazard-wrapper">
<div ref="progressChartRef" class="progress-chart"></div>
<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>
</div>
<!-- 左下区域 -->
<div class="left-bottom">
<div class="panel-title">
<div class="tabs">
<span class="tab" :class="{ active: activeTab === '高危作业' }" @click="handleTabClick('高危作业')">高危作业</span>
<span class="divider">|</span>
<span
class="tab" :class="{ active: activeTab === '安全培训考试' }"
@click="handleTabClick('安全培训考试')">安全培训考试</span>
<span class="divider">|</span>
<span class="tab" :class="{ active: activeTab === '应急预案及演练' }" @click="handleTabClick('应急预案及演练')">应急预案及演练</span>
</div>
</div>
<img style="margin: 8px 0" src="@/assets/images/title_border_line.png" />
<div style="padding-top: 10px;display: flex; flex-direction: column; justify-content: start; height: 91%;">
<template v-if="activeTab === '高危作业'">
<div class="bottom-card">
<div class="bottom-card-title" style="margin-top: 0;">
<span>各园区统计</span>
<img width="50%" style="margin: 8px 0" src="@/assets/images/line_1.png" />
</div>
</div>
<div class="row-wrapper">
<div class="row-item">
<img src="@/assets/images/screen/unfinished.png" width="20" />
<span class="row-item-title">未开始数量</span>
<span class="row-item-number">{{unfinishedCount}}</span>
</div>
<div class="row-item">
<img src="@/assets/images/screen/inProcess.png" width="20" />
<span class="row-item-title">进行中数量</span>
<span class="row-item-number">{{inProgressCount}}</span>
</div>
<div class="row-item">
<img src="@/assets/images/screen/finished.png" width="20" />
<span class="row-item-title">已完成数量</span>
<span class="row-item-number">{{finishedCount}}</span>
</div>
</div>
<div style="display: flex; width: 100%;">
<AlertList maxHeight="25vh" style="margin-left: 1vw;" :list-data="dangerList" />
</div>
</template>
<template v-if="activeTab === '安全培训考试'">
<div class="bottom-card" style="margin-top: 10px;">
<div class="bottom-card-title" style="margin-top: 0;">
<span>安全培训考试</span>
<img width="50%" style="margin: 8px 0" src="@/assets/images/line_1.png" />
</div>
</div>
<AlertList maxHeight="40vh" style="margin-left: 1vw;" :table-title="tableTitle" :list-data="examList" />
</template>
<template v-if="activeTab === '应急预案及演练'">
<div class="bottom-card">
<div class="bottom-card-title" style="margin-top: 0;">
<span>应急预案及演练</span>
<img width="50%" style="margin: 8px 0" src="@/assets/images/line_1.png" />
</div>
</div>
<AlertList maxHeight="40vh" style="margin-left: 1vw;" :table-title="trainingTableTitle" :list-data="drillList" />
</template>
</div>
</div>
</div>
<div class="right-wrapper">
<!-- 右上区域 -->
<div class="right-top" style="text-align: right">
<div class="panel-title">高风险告警</div>
<div>
<img style="margin: 8px 0" src="@/assets/images/title_border_line_1.png" />
</div>
<div class="tip-container">
<div class="tip-image">
<img src="@/assets/images/screen/circle_image.png" width="80" height="80" />
<span class="number">{{ mockData.alertData.total }}</span>
</div>
<img src="@/assets/images/screen/tip_bg_image.png" width="100%" height="70" />
<div class="tip-content">
<div class="col-item">
<img src="@/assets/images/screen/warning_img.png" width="23" />
<span>告警总数</span>
<span style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">{{ mockData.alertData.total }}</span>
</div>
<div class="col-item">
<span>已处理</span>
<span style="font-size: 1.2rem; marker-start: 2vw; color: greenyellow;">{{ mockData.alertData.processed
}}</span>
</div>
<div class="col-item" style=" display: flex;margin-left: 2vw; align-items: center;">
<span>待处理</span>
<span style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">{{ mockData.alertData.pending }}</span>
</div>
<div class="col-item" style=" display: flex;margin-left: 2vw; align-items: center;">
<span>处理中</span>
<span style="font-size: 1.2rem; marker-start: 2vw; color: yellow;">{{ mockData.alertData.processing }}</span>
</div>
</div>
</div>
<div style=" display: flex; width: 100%;margin-top: 1vw; flex: 1; justify-content: flex-end;">
<AlertList style="margin-right: 1vw;" title="告警详情" :list-data="mockData.alertData.details" />
</div>
</div>
<!-- 右下区域 -->
<div class="right-bottom" style="text-align: right">
<div class="panel-title">超时工单</div>
<img style="margin: 8px 0" src="@/assets/images/title_border_line_1.png" />
<div class="tip-container">
<div class="tip-image">
<img src="@/assets/images/screen/circle_image.png" width="80" height="80" />
<span class="number">{{ mockData.timeoutWorkOrders.total }}</span>
</div>
<img src="@/assets/images/screen/tip_bg_image.png" width="100%" height="70" />
<div class="tip-content">
<div class="col-item">
<img src="@/assets/images/screen/warning_img.png" width="23" />
<span>超时工单数</span>
<span style="font-size: 1.2rem; marker-start: 2vw; color: red;">{{ mockData.timeoutWorkOrders.total }}</span>
</div>
</div>
</div>
<div style=" display: flex; width: 100%;margin-top: 1vw; flex: 1; justify-content: flex-end;">
<AlertList style="margin-right: 1vw;" title="工单详情" :list-data="mockData.timeoutWorkOrders.details" />
</div>
</div>
</div>
<div class="center-container">
<!-- 中部区域 -->
<ParkCenter
:parkName="selectedPark" :backgroundImage="backgroundImage" @point-hover="handlePointHover"
@point-leave="handlePointLeave" />
</div>
<!-- 点位信息弹窗 -->
<PointInfoPopup
v-if="showPointPopup" :visible="showPointPopup" :point-info="currentPoint"
:park-name="selectedPark || '雄安园区'" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import * as echarts from 'echarts'
import ParkCenter from './components/ParkCenter.vue'
import PointInfoPopup from './components/PointInfoPopup.vue'
import WeatherWarning from './components/WeatherWarning.vue'
import AlertList from './components/AlertList.vue'
import { getTableList, getTableData, getDangerDetail, getDangerCount, getExamDetail, getDrillDetail } from './report'
interface PointPosition {
label: string
x: number
y: number
relativeX: number
relativeY: number
}
const totalCountDigits = computed(() => String(mockData.totalCount).split('').map(Number))
const formalEmployeeDigits = computed(() => String(mockData.formalEmployeeCount).split('').map(Number))
const externalStaffDigits = computed(() => String(mockData.externalStaffCount).split('').map(Number))
const visitorDigits = computed(() => String(mockData.visitorCount).split('').map(Number))
const route = useRoute()
const router = useRouter()
const selectedPark = ref('雄安园区')
const backgroundImage = ref('')
const currentDateTime = ref<string>('')
const currentDate = ref<string>('')
const currentWeek = ref<string>('')
const currentTime = ref<string>('')
const activeTab = ref<string>('高危作业')
const showPointPopup = ref(false)
const currentPoint = ref<PointPosition | null>(null)
const progressChart = ref<echarts.ECharts | null>(null)
const progressChartRef = ref<HTMLElement | null>(null)
const timeUpdateTimerId = ref<ReturnType<typeof setInterval> | null>(null)
const dangerList = ref<any[]>([])
const unfinishedCount = ref<number>(0)
const inProgressCount = ref<number>(0)
const finishedCount = ref<number>(0)
const tableTitle = [
{
name: '培训(考试)名称',
key: 'examname'
},
{
name: '参与人次',
key: 'exampeoplenum'
},
{
name: '培训时长(小时)',
key: 'examduration',
},
{
name: '考试通过率(%)',
key: 'exampassrate',
}
]
const trainingTableTitle = [
{
name: '演练名称',
key: 'drill_plan_name'
},
{
name: '完成时间',
key: 'drill_time'
},
{
name: '参与人数',
key: 'drill_count'
}
]
const examList = ref<any[]>([])
const drillList = ref<any[]>([])
const query = reactive({
pageNo: 1,
pageSize: 10000,
startDate: currentDateTime.value + " 00:00:00",
endDate: currentDateTime.value + " 23:59:59",
campus_id: "1825468527486140416" //园区id如果是多个用逗号拼接
})
// 模拟数据
const mockData = reactive({
totalCount: 0,
formalEmployeeCount: 0,
externalStaffCount: 0,
visitorCount: 0,
buildInfoList: [
{
highRiskCount: 2,
buildName: "A楼栋",
alarmsCount: 23
},
{
highRiskCount: 1,
buildName: "B楼栋",
alarmsCount: 3
},
{
highRiskCount: 11,
buildName: "C楼栋",
alarmsCount: 33
},
{
highRiskCount: 11,
buildName: "D楼栋",
alarmsCount: 33
}
],
alertData: {
total: 0,
processed: 0,
pending: 0,
processing: 0,
details: [
// {
// description: '2024-07-10 上午8:00 雄安园区隐患内容管理 状态 处理',
// alarm_level_code: 'major',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283935'
// },
// {
// description: '2025-07-09 上海XXX区域A1门禁告警 处理中 紧急',
// alarm_level_code: 'major',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283936'
// },
// {
// description: '2025-07-09 上海XXX区域A1门禁告警 处理中 紧急',
// alarm_level_code: 'major',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283937'
// }, {
// description: '2024-07-10 上午8:00 雄安园区隐患内容管理 状态 处理',
// alarm_level_code: 'major',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283935'
// },
// {
// description: '2025-07-09 上海XXX区域A1门禁告警 处理中 紧急',
// alarm_level_code: 'major',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283936'
// },
// {
// description: '2025-07-09 上海XXX区域A1门禁告警 处理中 紧急',
// alarm_level_code: 'major',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283937'
// }
]
},
timeoutWorkOrders: {
total: 0,
details: [
// {
// 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'
// },
]
},
hiddenDangerData: {
generalCount: 0,
majorCount: 0,
severityCount: 0,
progress: {
participateCount: 0,
toParticipateCount: 0,
finishCount: 0
},
progressDetail: [
// {
// description: '2025-08-10 上午8:00 武汉园区参与内容管理 状态 处理',
// alarm_level_code: 'severity',
// 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: 'major',
// 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: 'severity',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283937'
// }, {
// description: '2025-08-10 上午8:00 武汉园区参与内容管理 状态 处理',
// alarm_level_code: 'general',
// alarm_status: '已关闭',
// alarm_biz_id: '20250827164305283937'
// },
]
}
})
// 返回上一级
const returnToHeadquarters = () => {
router.back()
}
const handlePointHover = (position: PointPosition) => {
currentPoint.value = position
showPointPopup.value = true
}
const handlePointLeave = () => {
showPointPopup.value = false
currentPoint.value = null
}
// 初始化进度图表
const initProgressChart = () => {
const chartElement = progressChartRef.value
if (chartElement) {
progressChart.value = echarts.init(chartElement)
const width = progressChart.value.getWidth()
const progressOption = {
series: [
{
type: 'pie',
radius: '60%',
center: ['50%', '50%'],
left: 0,
top: 0,
bottom: 0,
data: [
{ value: 20, name: '已逾期', itemStyle: { color: '#ef4444' } },
{ value: 35, name: '已处理', itemStyle: { color: '#10b981' } },
// { value: 25, name: '待排查', itemStyle: { color: '#eab308' } },
{ value: 20, name: '处理中', itemStyle: { color: '#3b82f6' } },
],
label: {
alignTo: 'edge',
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 < width / 2;
const points = params.labelLinePoints;
points[2][0] = isLeft
? params.labelRect.x
: params.labelRect.x + params.labelRect.width;
return {
labelLinePoints: points
};
},
}
]
}
progressChart.value.setOption(progressOption)
}
}
const isFirstLoad = ref<boolean>(true)
// 动画相关的状态
const isAnimating = ref<boolean>(false)
const animationDuration = 2000 // 动画持续时间(毫秒)
const dashboardTimerId = ref<ReturnType<typeof setInterval> | null>(null)
interface RegionItem {
name: string
code: string
pic_url: string
}
const regionOption = ref<RegionItem[]>([])
onMounted(async () => {
updateTime()
timeUpdateTimerId.value = setInterval(updateTime, 1000)
if (typeof route.query.parkName === 'string') {
selectedPark.value = route.query.parkName
query.campus_id = route.query.parkCode as string
}
try {
let { records } = await getTableList(
'park_info_list'
)
// records = [
// {
// "region_id": "130601",
// "park_code": "1825468527486140416",
// "region": "北京",
// "park_name": "雄安新区总部",
// "pic_url": "1"
// },
// {
// "region_id": "130601",
// "park_code": "1825468527486140417",
// "region": "北京",
// "park_name": "雄安地面站",
// "pic_url": "2"
// },
// {
// "region_id": "130603",
// "park_code": "1825468527486140426",
// "region": "武汉",
// "park_name": "花山新区总部",
// "pic_url": "3"
// }
// ]
if (records && records.length > 0) {
// 更新为新的数据格式
regionOption.value = records.map(el => ({
name: el.park_name,
code: el.park_code,
pic_url: el.pic_url
}))
for (let i = 0; i < regionOption.value.length; i++) {
const el = regionOption.value[i];
if (el.code == query.campus_id && el.pic_url) {
backgroundImage.value = el.pic_url
}
}
}
} catch (error) {
console.error('初始化园区数据失败:', error)
}
// 初始化数据
await loadDashboardData()
// 添加周期性动画演示
dashboardTimerId.value = setInterval(async () => {
await loadDashboardData()
}, 2 * 60 * 1000) // 每2分钟更新一次
})
const loadDashboardData = () => {
if (isFirstLoad.value) {
isFirstLoad.value = false
}
// 获取总体概览数据
getTableList('generalTotal', query).then(generalTotal => {
if (generalTotal.records && generalTotal.records.length > 0) {
mockData.totalCount = Number(generalTotal.records[0].totalCount)
mockData.formalEmployeeCount = Number(generalTotal.records[0].formalEmployeeCount)
mockData.externalStaffCount = Number(generalTotal.records[0].externalStaffCount)
mockData.visitorCount = Number(generalTotal.records[0].visitorCount)
}
}).catch(error => {
console.log("获取总体概览数据失败>>>>>>>", error);
})
// 获取总体概览-隐患排查治理数量
// getTableList('hidden_danger_investigation', query).then(res => {
// if (res.records && res.records.length > 0) {
// mockData.hiddenDangerData.generalCount = res.records[0].general
// mockData.hiddenDangerData.severityCount = res.records[0].major
// // 获取隐患排查治理数据
// getTableList('risk_level_count', { ...query }).then(res => {
// mockData.hiddenDangerData.generalCount = Number(mockData.hiddenDangerData.generalCount) + Number(res.records[0].general_count)
// mockData.hiddenDangerData.severityCount = Number(mockData.hiddenDangerData.severityCount) + Number(res.records[0].major_count)
// })
// }
// }).catch(error => {
// console.log("获取隐患排查治理数量失败>>>>>>>", error);
// })
// 获取总体概览-隐患排查治理统计
// getTableList('hidden_danger_process_progress', query).then(res => {
// if (res.records && res.records.length > 0) {
// const process = res.records[0]
// progressChart.value = echarts.init(progressChartRef.value)
// const width = progressChart.value.getWidth()
// const progressOption = {
// series: [
// {
// type: 'pie',
// radius: '60%',
// center: ['50%', '50%'],
// left: 0,
// top: 0,
// bottom: 0,
// data: [
// { value: process.overdue || 0, name: '已逾期', itemStyle: { color: '#ef4444' } },
// { value: process.processed || 0, name: '已处理', itemStyle: { color: '#10b981' } },
// { value: process.pending || 0, name: '待排查', itemStyle: { color: '#eab308' } },
// { value: process.processing || 0, name: '处理中', itemStyle: { color: '#3b82f6' } }
// ],
// label: {
// alignTo: 'edge',
// 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 < width / 2;
// const points = params.labelLinePoints;
// points[2][0] = isLeft
// ? params.labelRect.x
// : params.labelRect.x + params.labelRect.width;
// return {
// labelLinePoints: points
// };
// },
// }
// ]
// }
// progressChart.value.setOption(progressOption)
// getTableData('1963446160885366786', { ...query, park_name: query.campus_id }).then(res => {
// let overdueCnt = 0
// let processedCnt = 0
// let processingCnt = 0
// res.records.forEach(el => {
// // 整改状态
// if (el.corrective_status == 2) { //已逾期
// overdueCnt++
// }
// if (el.corrective_status == 1) { //已处理
// processedCnt++
// }
// if (el.corrective_status == 0) { //处理中
// processingCnt++
// }
// });
// progressOption.series[0].data[0].value = Number(progressOption.series[0].data[0].value) + overdueCnt
// progressOption.series[0].data[1].value = Number(progressOption.series[0].data[1].value) + processedCnt
// progressOption.series[0].data[3].value = Number(progressOption.series[0].data[3].value) + processingCnt
// if (progressChart.value) {
// progressChart.value.setOption(progressOption)
// }
// })
// }
// }).catch(error => {
// console.log("获取隐患排查治理统计数据失败>>>>>>>", error);
// })
handleTabClick("高危作业")
// 获取风险预警数据
getTableList('risk_alert_data', query).then(risk_alert_data => {
if (risk_alert_data.records && risk_alert_data.records.length > 0) {
mockData.alertData.total = risk_alert_data.records[0].total
mockData.alertData.processed = risk_alert_data.records[0].processed
mockData.alertData.pending = risk_alert_data.records[0].pending
mockData.alertData.processing = risk_alert_data.records[0].processing
}
}).catch(error => {
console.error('获取风险预警数据失败:', error)
})
// 获取风险预警详情数据
getTableList('risk_alert_detail', query).then(risk_alert_detail => {
if (risk_alert_detail.records && risk_alert_detail.records.length > 0) {
mockData.alertData.details = risk_alert_detail.records
}
}).catch(error => {
console.error('获取风险预警详情数据失败:', error)
})
// 获取超期工单数据
getTableList('timeout_work_order', query).then(timeout_work_order => {
if (timeout_work_order.records && timeout_work_order.records.length > 0) {
mockData.timeoutWorkOrders.total = timeout_work_order.records.length
mockData.timeoutWorkOrders.details = timeout_work_order.records
}
}).catch(error => {
console.error('获取超期工单数据失败:', error)
})
// 获取超期工单数据
getTableList('park_build_info', query).then(res => {
if (res.records && res.records.length > 0) {
}
}).catch(error => {
console.error('获取超期工单数据失败:', error)
})
handleHiddenDangerPannelData(query)
}
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
}
progressChart.value = echarts.init(progressChartRef.value)
const width = progressChart.value.getWidth()
const progressOption = {
series: [
{
type: 'pie',
radius: '60%',
center: ['50%', '50%'],
left: 0,
top: 0,
bottom: 0,
data: [
// { value: overdueCnt || 0, name: '已逾期', itemStyle: { color: '#ef4444' } },
// { value: processedCnt || 0, name: '已处理', itemStyle: { color: '#10b981' } },
// { value: pendingCnt || 0, name: '待排查', itemStyle: { color: '#eab308' } },
// { value: processingCnt || 0, name: '处理中', itemStyle: { color: '#3b82f6' } }
] as any,
label: {
alignTo: 'edge',
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 < width / 2;
const points = params.labelLinePoints;
points[2][0] = isLeft
? params.labelRect.x
: params.labelRect.x + params.labelRect.width;
return {
labelLinePoints: points
};
},
}
]
}
try {
// 获取隐患排查治理数据
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
mockData.hiddenDangerData.generalCount = generalCnt
mockData.hiddenDangerData.severityCount = 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)
progressOption.series[0].data = [
{ value: overdueCnt || 0, name: '已逾期', itemStyle: { color: '#ef4444' } },
{ value: processedCnt || 0, name: '已处理', itemStyle: { color: '#10b981' } },
{ value: pendingCnt || 0, name: '待排查', itemStyle: { color: '#eab308' } },
{ value: processingCnt || 0, name: '处理中', itemStyle: { color: '#3b82f6' } }
]
if (progressChart.value != null) {
progressChart.value.setOption(progressOption)
}
} else {
console.log("请求系统成功,展示数据", _data, _data2);
mockData.hiddenDangerData.generalCount = _data.general
mockData.hiddenDangerData.severityCount = _data.major
let totalCnt = _data.general + _data.major
let overdueCnt = (_data.overdue / totalCnt * 100).toFixed(2)
let processedCnt = (_data.processed / totalCnt * 100).toFixed(2)
let processingCnt = (_data.processing / totalCnt * 100).toFixed(2)
let pendingCnt = (_data.pending / totalCnt * 100).toFixed(2)
progressOption.series[0].data = [
{ value: overdueCnt || 0, name: '已逾期', itemStyle: { color: '#ef4444' } },
{ value: processedCnt || 0, name: '已处理', itemStyle: { color: '#10b981' } },
{ value: pendingCnt || 0, name: '待排查', itemStyle: { color: '#eab308' } },
{ value: processingCnt || 0, name: '处理中', itemStyle: { color: '#3b82f6' } }
]
if (progressChart.value != null) {
progressChart.value.setOption(progressOption)
}
}
}
})
}
})
// 获取隐患排查治理数据
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)
// 获取隐患排查治理处理进度数据
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
mockData.hiddenDangerData.generalCount = generalCnt
mockData.hiddenDangerData.severityCount = 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)
progressOption.series[0].data = [
{ value: overdueCnt || 0, name: '已逾期', itemStyle: { color: '#ef4444' } },
{ value: processedCnt || 0, name: '已处理', itemStyle: { color: '#10b981' } },
{ value: pendingCnt || 0, name: '待排查', itemStyle: { color: '#eab308' } },
{ value: processingCnt || 0, name: '处理中', itemStyle: { color: '#3b82f6' } }
]
if (progressChart.value != null) {
progressChart.value.setOption(progressOption)
}
} else {
//显示三方数据
console.log("请求第三方成功,展示数据", _data, _data2);
mockData.hiddenDangerData.generalCount = _data2.general
mockData.hiddenDangerData.severityCount = _data2.major
let overdueCnt = res.records[0].overdue
let processedCnt = res.records[0].processed
let processingCnt = res.records[0].processing
let pendingCnt = res.records[0].pending
progressOption.series[0].data = [
{ value: overdueCnt || 0, name: '已逾期', itemStyle: { color: '#ef4444' } },
{ value: processedCnt || 0, name: '已处理', itemStyle: { color: '#10b981' } },
{ value: pendingCnt || 0, name: '待排查', itemStyle: { color: '#eab308' } },
{ value: processingCnt || 0, name: '处理中', itemStyle: { color: '#3b82f6' } }
]
if (progressChart.value != null) {
progressChart.value.setOption(progressOption)
}
}
}
}).catch(error => {
console.error('获取隐患排查治理处理进度数据失败:', error)
})
}
}).catch(error => {
console.error('获取隐患排查治理数据失败:', error)
})
} catch (error) {
console.error('获取隐患排查治理数据失败:', error)
}
}
const trainingStatistics = ref<any>({})
const trainingList = ref<any>({})
const handleTabClick = async (tab: string) => {
activeTab.value = tab
let code = ''
let code1 = ''
switch (tab) {
case '高危作业':
code = "fire_drill"
code1 = "fire_drill_detail"
getDangerDetail(query.campus_id).then(res => {
const list = res.records.map((item: any) => ({description: item.contenttext}))
dangerList.value = list
})
getDangerCount(query.campus_id).then(res => {
unfinishedCount.value = res.records[0].wks
inProgressCount.value = res.records[0].jxz
finishedCount.value = res.records[0].ywc
})
break
case '安全培训考试':
code = 'security_training_count'
code1 = 'security_training_detail'
const res = await getExamDetail(query.campus_id)
examList.value = res.records
break
case '应急预案及演练':
code = 'security_training_count'
code1 = 'security_training_detail'
const res1 = await getDrillDetail(query.campus_id)
drillList.value = res1.records
break
}
// 根据不同的tab请求不同的接口
getTableList(
code,
{ ...query, activeTab: tab }
).then(response => {
// 更新风险统计数据 - 传递完整的数组数据用于饼图显示
if (response.records && response.records.length > 0) {
trainingStatistics.value = response.records[0]
mockData.hiddenDangerData.progress = response.records[0]
}
}).catch(error => {
console.error('获取风险统计数据失败:', error)
})
getTableList(
code1,
{ ...query, activeTab: tab }
).then(response => {
// 更新风险统计数据 - 传递完整的数组数据用于饼图显示
if (response.records && response.records.length > 0) {
mockData.hiddenDangerData.progressDetail = response.records
}
}).catch(error => {
console.error('获取风险统计数据失败:', error)
})
}
// 批量更新所有数字的方法
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 = mockData.totalCount
animateNumber(startValue, counts.total || 0, animationDuration, (value) => {
mockData.totalCount = value
})
setTimeout(resolve, animationDuration)
}))
}
if (counts.formal !== undefined) {
promises.push(new Promise<void>((resolve) => {
const startValue = mockData.formalEmployeeCount
animateNumber(startValue, counts.formal || 0, animationDuration, (value) => {
mockData.formalEmployeeCount = value
})
setTimeout(resolve, animationDuration)
}))
}
if (counts.external !== undefined) {
promises.push(new Promise<void>((resolve) => {
const startValue = mockData.externalStaffCount
animateNumber(startValue, counts.external || 0, animationDuration, (value) => {
mockData.externalStaffCount = value
})
setTimeout(resolve, animationDuration)
}))
}
if (counts.visitor !== undefined) {
promises.push(new Promise<void>((resolve) => {
const startValue = mockData.visitorCount
animateNumber(startValue, counts.visitor || 0, animationDuration, (value) => {
mockData.visitorCount = value
})
setTimeout(resolve, animationDuration)
}))
}
Promise.all(promises).then(() => {
isAnimating.value = false
flashNumbers() // 更新完成后闪烁效果
})
}
// 数字滚动动画方法
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 flashNumbers = () => {
const numberElements = document.querySelectorAll('.total-number, .type-number')
numberElements.forEach((el) => {
el.classList.add('flash')
setTimeout(() => {
el.classList.remove('flash')
}, 300)
})
}
// 更新时间
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"
}
onUnmounted(() => {
if (dashboardTimerId.value) {
clearInterval(dashboardTimerId.value)
dashboardTimerId.value = null
}
if (timeUpdateTimerId.value) {
clearInterval(timeUpdateTimerId.value)
timeUpdateTimerId.value = null
}
})
</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 {
.content-container {
column-gap: 3vw;
padding: 0 8px 15px;
.left-wrapper {
.left-top .hazard-wrapper {
.progress-chart {
min-height: 180px;
}
}
.left-bottom .donut-chart-with-labels {
width: 35vw;
height: 35vh;
}
}
.center-container {
width: 55vh;
height: 55vh;
}
}
}
}
@media (width <=768px) {
.dashboard-container {
.content-container {
column-gap: 2vw;
padding: 0 5px 10px;
.left-wrapper {
.left-top .hazard-wrapper {
.progress-chart {
min-height: 160px;
}
}
.left-bottom .donut-chart-with-labels {
width: 40vw;
height: 40vh;
margin-left: 1vw;
}
}
.center-container {
top: 60%;
width: 50vh;
height: 50vh;
}
}
}
}
@media (width <=480px) {
.dashboard-container {
.content-container {
column-gap: 1vw;
padding: 0 3px 8px;
.left-wrapper {
.left-top .hazard-wrapper {
.progress-chart {
min-height: 140px;
}
}
.left-bottom .donut-chart-with-labels {
width: 45vw;
height: 45vh;
margin-left: 0.5vw;
}
}
.center-container {
top: 65%;
width: 45vh;
height: 45vh;
}
}
}
}
.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;
}
.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;
.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: flex-end;
column-gap: 2vw;
}
.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;
}
}
.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;
width: 67%;
margin-bottom: 20px;
.bottom-card-title {
display: flex;
margin-top: 5px;
flex-direction: column;
align-items: center;
}
.type-wrapper {
display: flex;
justify-content: center;
column-gap: 2vw;
margin-top: 5px;
.type-item {
display: flex;
flex-direction: column;
align-items: center;
column-gap: 4px;
.type-btn {
display: inline-block;
padding: 2px 30px;
font-size: 0.7rem;
cursor: pointer;
background-color: rgb(59 130 246 / 20%);
border-radius: 12px;
user-select: none;
&.blue {
background-color: rgb(32 148 235);
}
&.green {
background-color: rgb(80 139 79);
}
&.yellow {
background-color: rgb(204 96 26);
}
}
.type-num {
margin-top: 0.3vh;
font-size: 0.9rem;
}
}
}
.hazard-wrapper {
flex: 1;
display: flex;
align-items: center;
column-gap: 1.8vw;
.progress-chart {
width: 55%;
min-height: 140px;
}
.progress-legend {
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.5vw;
.legend-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 0.7rem;
}
}
}
}
.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 {
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;
}
}
.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: #1afb8f;
border-radius: 2px;
}
}
}
}
}
}
.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;
min-height: 200px;
margin-left: 2vw;
}
.row-wrapper {
display: flex;
width: 67%;
column-gap: 1vw;
margin-left: 1vw;
.row-item {
display: flex;
padding: 0.5vh 0;
font-size: 0.7rem;
background-color: rgb(4 35 125 / 50%);
flex-direction: column;
align-items: center;
column-gap: 10px;
flex: 1;
.row-item-title {}
.row-item-number {
font-size: 1rem;
}
}
}
}
}
.right-wrapper {
display: flex;
height: 100%;
flex: 1;
flex-direction: column;
row-gap: 1rem;
.right-top {
/* 设置重复方式 */
display: flex;
background-image:
url('@/assets/images/screen/right_top_img.png'),
url('@/assets/images/screen/right_center_img.png'),
url('@/assets/images/screen/right_bottom_img.png');
background-position:
top center,
right center,
bottom center;
/* 设置大小,注意中间的背景图应该覆盖整个容器 */
background-repeat: no-repeat, no-repeat, no-repeat;
/* 设置位置 */
background-size:
100% 90px,
cover,
100% 68px;
flex: 1;
flex-direction: column;
align-items: flex-end;
.tip-container {
position: relative;
display: flex;
width: 70%;
height: 80px;
padding-right: 20px;
align-items: center;
justify-content: flex-end;
.tip-image {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%) translateX(-50%);
.number {
position: absolute;
top: 50%;
left: 50%;
font-size: 1rem;
color: #fff;
transform: translate(-80%, -50%);
}
}
.tip-content {
position: absolute;
inset: 0% 0 0 6%;
display: flex;
padding-left: 20px;
align-items: center;
.col-item {
display: flex;
margin-left: 1vw;
align-items: center;
span {
margin-right: 10px;
}
}
}
}
}
.right-bottom {
/* 设置重复方式 */
display: flex;
background-image:
url('@/assets/images/screen/right_top_img.png'),
url('@/assets/images/screen/right_center_img.png'),
url('@/assets/images/screen/right_bottom_img.png');
background-position:
top center,
right center,
bottom center;
/* 设置大小,注意中间的背景图应该覆盖整个容器 */
background-repeat: no-repeat, no-repeat, no-repeat;
/* 设置位置 */
background-size:
100% 90px,
cover,
100% 68px;
flex: 1;
flex-direction: column;
align-items: flex-end;
.tip-container {
position: relative;
display: flex;
width: 50%;
height: 80px;
padding-right: 20px;
align-items: center;
justify-content: flex-end;
.tip-image {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%) translateX(-50%);
.number {
position: absolute;
top: 50%;
left: 50%;
font-size: 1rem;
color: #fff;
transform: translate(-80%, -50%);
}
}
.tip-content {
position: absolute;
inset: 0% 0 0 6%;
display: flex;
padding-left: 20px;
align-items: center;
.col-item {
display: flex;
margin-left: 1vw;
align-items: center;
span {
margin-right: 10px;
}
}
}
}
}
}
.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;
.test-btn {
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
padding: 8px 16px;
font-size: 12px;
color: white;
cursor: pointer;
background: rgb(59 130 246 / 80%);
border: 1px solid rgb(59 130 246 / 40%);
border-radius: 4px;
&:hover {
background: rgb(59 130 246 / 60%);
}
}
}
}
}
</style>