|
|
<template> <div id="index-container"> <div class="loading-overlay" v-if="isLoading"> <div class="loading-content"> <div class="loading-spinner"></div> <span class="loading-text">{{ shutdownMessage }}</span> </div> </div> <el-header class="nav-bar"> <div class="logo"> <img src="@/assets/Index/logo.svg" alt="" width="150px" /> </div> <div class="nav-tabs" @click="handleTabClick"> <!-- 使用 router-link 进行路由跳转 --> <router-link to="/index/regular" class="nav-tab" :class="{ active: selectedTab === '常规' }" data-tab="常规" >常规</router-link > <router-link to="/index/history" class="nav-tab" :class="{ active: selectedTab === '历史' }" data-tab="历史" >历史</router-link > <router-link to="/index/setting" class="nav-tab" :class="{ active: selectedTab === '设置' }" data-tab="设置" >设置</router-link > </div> <div v-if="deviceStore.deviceState.workState === 'IDLE'" class="test-control" > <!-- 显示开始测试按钮 --> <el-button type="primary" class="start-test" @click="startTest" >开始测试</el-button > </div> <div v-else class="test-control"> <!-- 显示暂停和停止按钮 --> <el-button type="warning" class="pause-test" @click="pauseTest" v-if="deviceStore.deviceState.workState !== 'PAUSE'" >暂停</el-button > <el-button type="success" class="continue-test" @click="continueTest" v-else >继续</el-button > <el-button type="danger" class="stop-test" @click="stopTest" >停止</el-button > </div> </el-header>
<!-- 间隔线 --> <div class="interval"> <div class="blue-line" :style="{ width: `${lineWidth}px`, left: `${lineLeft}px` }" ></div> </div>
<!-- 主内容区域 --> <main class="main-content"> <router-view v-slot="{ Component }"> <keep-alive :exclude="['TubeUserId', 'EmergencyForm']"> <component :is="Component" /> </keep-alive> </router-view> </main>
<!-- 底部操作信息 --> <el-footer class="footer-info">
<el-dropdown placement="top-start" style="margin-left:-10px"> <div class="user-card"> <img class="user-logo" src="@/assets/Index/user.svg" /> <div class="user-name"> {{ deviceStore.contextState.loginFlag ? deviceStore.contextState.loginUser.account : '未登录' }} </div> </div> <template #dropdown> <el-dropdown-menu style="width:100px"> <el-dropdown-item > <div @click="onInitDevice" style="display: flex;"> <img :src="InitSvg" alt="Icon" width="15" /> <div class="pd-5">初始化</div> </div> </el-dropdown-item> <el-dropdown-item > <div @click="onCloseDevice" style="display: flex;"> <img :src="CloseSvg" alt="Icon" width="15" /> <div class="pd-5">关机</div> </div> </el-dropdown-item> <el-dropdown-item> <div @click="onLogout" style="display: flex;"> <img :src="LogoutSvg" alt="Icon" width="15" /> <div class="pd-5">退出</div> </div> </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown>
<div class="equipment-status" @click="showRecentMsgDialog = true"> <div class="status-text" :class="deviceStore.messageState.topMessage.messageLevel" > {{ deviceStore.messageState.topMessage.message }} </div> </div> <div class="time-card"> <img class="time-logo" src="@/assets/Index/clock.svg" /> <div class="time-text"> <Time></Time> </div> </div> </el-footer> <el-dialog class="recent-msg-dialog" v-model="showRecentMsgDialog" title="最近消息" width="700" > <div class="msg-container"> <div class="msg-item" v-for="item in deviceStore.messageState.messageBoxList" :key="item.time" :class="[item.messageLevel]" > <span>{{ formatDate(item.time) }}</span> <span>{{ item.message }}</span> </div> </div> </el-dialog>
<el-dialog class="event-report-dialog" v-model="showEventReportDlg" width="600" :show-close="false" > <template #header> <div class="report-title"> {{ currEventReport ? currEventReport.prompt.title : '' }} </div> <div class="report-desc"> {{ currEventReport ? currEventReport.prompt.info : '' }} </div> </template> <div class="report-detail"> <div v-if=" currEventReport && currEventReport.prompt.detailInfoType === 'Forms' " > <div class="report-form-item" v-for="(item, index) in currEventReport.prompt.detailInfo" :key="index" > <span>{{ item.name + ' :' }}</span> <span>{{ item.description }}</span> </div> </div> <div v-else-if=" currEventReport && currEventReport.prompt.detailInfoType === 'Table' " > <div class="report-table-container"> <div class="report-table-header"> <div v-for="(item, index) in currEventReport.prompt.detailInfo .header" :key="index" > <span>{{ item }}</span> </div> </div> <div class="report-table-rows"> <div class="report-table-row" v-for="(row, index) in currEventReport.prompt.detailInfo.vars" :key="index" > <div v-for="(col, idx) in row" :key="idx"> {{ col.val }} </div> </div> </div> </div> </div> <span v-else> {{ currEventReport ? currEventReport.prompt.detailInfo : '' }} </span> </div> <template #footer> <div class="dialog-footer"> <el-button type="primary" @click="onConfirmReport"> 确定 </el-button> </div> </template> </el-dialog>
<InitWarn v-if="showModal" :visible="showModal" title="确认操作" message="确认试管架中的试管槽是否取下,避免自检/自动复位时对设备产生损坏!" icon="/src/assets/Warn.svg" cancelText="返回" confirmText="确认取下/开始复位" @close="showModal = false" @confirm="handleConfirm" /> <!-- 通用加载弹窗组件 --> <LoadingModal v-if="showDeviceResettingModal" :visible="showDeviceResettingModal" title="正在自检/自动复位中" message="请不要有任何手动操作!" cancelText="返回" confirmText="确认取下/开始复位" disableButtons @close="showDeviceResettingModal = false" /> <LoadingModal v-if="showDeviceWaitingModal" :visible="showDeviceWaitingModal" :title="deviceWaitingModelInfo.title" :message="deviceWaitingModelInfo.message" :showBtns="false" /> <!-- 自动自检已完成 --> <InitWarn v-if="showAlreadyModal" :visible="showAlreadyModal" title="确认操作" message="自检/自动复位已完成!" icon="/src/assets/OK.svg" cancelText="返回" confirmText="确认" @close="showAlreadyModal = false" @confirm="handleAlreadyConfirm" /> <!-- 自动自检失败 --> <InitWarn v-if="showFailModal" :visible="showFailModal" :title="failMessage.title" :message="failMessage.message" icon="/src/assets/Warn.svg" cancelText="返回" confirmText="重试" @close="showFailModal = false" @confirm="confirmFail" /> <InitWarn v-if="idCardInserted" :visible="idCardInserted" title="检测到id卡插入,是否保存?" :message="idCardMessage" cancelText="返回" icon="/src/assets/update-pin-icon.svg" confirmText="确认保存" @close="idCardInserted = false" @confirm="saveIdInfo" /> <InitWarn v-if="showErrorModal" :visible="showErrorModal" title="错误提示" :message="ErrorMessage" icon="/src/assets/Warn.svg" cancelText="返回" confirmText="确认" @close="confirmError" @confirm="confirmError" /> <InitWarn v-if="showWarnModal" :visible="showWarnModal" title="注意" :message="WarnMessage" cancelText="返回" icon="/src/assets/update-pin-icon.svg" confirmText="确认" @close="confirmWarn" @confirm="confirmWarn" /> <Confirm :isLoading="confirmVisible" :confirmInfo="confirmInfo"></Confirm> </div> </template> <script setup lang="ts"> import { useRouter } from 'vue-router' import { ref, onMounted, computed, watch } from 'vue' import { ElDialog } from 'element-plus' import { Time, InitWarn, LoadingModal } from './components/Consumables' import { startWork, pauseWork, continueWork, stopWork, getDeviceWorkState, getInitState, initDevice, saveMountedCardInfo, closeBuzzer, getProjectInfo, getBloodTypes, confirmPromptInfo, } from '../../services/index'
import { useConsumablesStore, useDeviceStore, useEmergencyStore, useSettingTestTubeStore, useTestTubeStore, } from '../../store' import { createWebSocket } from '../../websocket/socket' import type { AppEventMessage, ConsumablesStateMessage, DeviceContextStateMessage, DeviceWorkStateMessage, EmergencyPosStateMessage, EventReport, FooterMessageState, IncubationPlateStateMessage, OptScanModuleStateMessage, SensorStateMessage, TubeHolderSettingMessage, TubeHolderStateMessage, } from '../../websocket/socket' import { getServerInfo } from '../../utils/getServerInfo' import { eventBus } from '../../eventBus' import { logout, shutdown } from '@/services/Login/login' import { useRunningStore } from '@/store/modules/running' import { formatDate } from '@/utils/formDate' import { eMessage } from './utils'
import InitSvg from '@/assets/init.svg' import CloseSvg from '@/assets/close.svg' import LogoutSvg from '@/assets/logout.svg'
const selectedTab = ref(sessionStorage.getItem('selectedTab') || '常规') const lineWidth = ref(0) const lineLeft = ref(0) const showModal = ref(false) const showDeviceResettingModal = ref(false) const showDeviceWaitingModal = ref(false) const deviceWaitingModelInfo = ref({ title: '设备正在响应中', message: '请不要有任何手动操作!', }) const showAlreadyModal = ref(false) const showFailModal = ref(false) // const checkData = ref<CheckItem[]>([]);
// const failItems = ref<CheckItem[]>([]);
const consumableStore = useConsumablesStore() const deviceStore = useDeviceStore() const runningStore = useRunningStore() const settingTubeStore = useSettingTestTubeStore() const tubeRackStore = useTestTubeStore() const emergencyStore = useEmergencyStore() //关机中message
const shutdownMessage = ref('正在关机中…') const confirmInfo = ref({}) const confirmVisible = ref(false) const idCardMessage = ref('是否保存id卡信息')
const failMessage = ref({ title: '检测失败', message: '', }) // 存储动态生成的错误信息
const idCardInserted = ref(false) // id卡插入状态
//事件状态
const EventText = ref<string>('闲置...') const showWarnModal = ref(false) const ErrorMessage = ref<string>('') const showErrorModal = ref(false) const WarnMessage = ref<string>('')
const showRecentMsgDialog = ref(false)
// WebSocket 实例
const eventUrl = getServerInfo('/api/v1/app/ws/event') const wsEvent = createWebSocket(eventUrl.wsUrl) const stateUrl = getServerInfo('/api/v1/app/ws/state') const wsState = createWebSocket(stateUrl.wsUrl)
const eventReports = ref<EventReport[]>([]) const showEventReportDlg = ref(false) const currEventReport = computed(() => { return eventReports.value.length > 0 ? eventReports.value[0] : undefined }) const onConfirmReport = async () => { if (eventReports.value.length > 0) { const report = eventReports.value.shift() const res = await confirmPromptInfo(report!.eventId) if (res && res.success) { // do nothing
} else { res && res.data && res.data.info && console.error(res.data.info) } } if (eventReports.value.length === 0) { showEventReportDlg.value = false } }
const handleSocketClose = (num: number) => { if (num === 0) { // socket连接断开
deviceWaitingModelInfo.value = { title: '系统产生错误,请重启系统', message: '请尽快重启系统', } showDeviceWaitingModal.value = true } else if (num === -1) { // 从断开 到 恢复连接, 关闭弹框
showDeviceWaitingModal.value = false } // else if (num === 5) {
// // 重试达到最大次数,重试,取消。
// failMessage.value = {
// title: '恢复连接失败',
// message: '未能恢复连接,建议重启设备',
// }
// showFailModal.value = true
// }
}
// 处理应用事件消息
// const formatDate = (date: any) => new Date(date).toLocaleDateString()
const handleAppEvent = (data: AppEventMessage['data']) => { console.log('🚀 ~ handleAppEvent ~ data:', data) if (data.typeName === 'AppPromptEvent') { eventReports.value.push(data) showEventReportDlg.value = true } else if (data.typeName === 'AppIDCardMountEvent') { consumableStore.setIdCardInserted(true) let projectInfo = data.projectInfo consumableStore.setIdCardInfo(projectInfo) //显示部分id卡信息。 项目名称,过期时间,批次号
idCardInserted.value = true //接收端是用v-html处理
idCardMessage.value = `
<div style='line-height:0.5;margin-top: -90px;'> <div style='margin-left:25%'> <div style='display:flex'><div>项目名称:</div><div>${projectInfo.projName}</div></div> <div style='display:flex'><div>批次号:</div><div>${projectInfo.lotId}</div></div> <div style='display:flex'><div>过期时间:</div><div>${formatDate(projectInfo.expiryDate)}</div></div> </div> </div>`
} else if (data.typeName === 'AppIDCardUnmountEvent') { consumableStore.setIdCardInserted(false) consumableStore.setIdCardInfo(null) idCardInserted.value = false } }
//注销用户
const router = useRouter() const onLogout = () => { logout().then(() => { router.push({ path: '/login', }) sessionStorage.setItem('token', '') }) }
const confirmFail = async () => { showFailModal.value = false await startInit() } //确认错误事件
const confirmError = async () => { showErrorModal.value = false //关闭蜂鸣器
await closeBuzzer() EventText.value = '闲置...' } //确认警告事件
const confirmWarn = async () => { showWarnModal.value = false } //保存id卡信息
const saveIdInfo = async () => { const res = await saveMountedCardInfo() if (res.success) { console.log('保存id卡信息成功') idCardInserted.value = false } }
//初始化设备
const onInitDevice = ()=> { eventBus.emit('initDevice') }
//关机
let isLoading = ref(false) const onCloseDevice = ()=> { // ElMessageBox.alert()
//检查设备状态 设备运行时不可关机
const deviceState = deviceStore.deviceState.workState; if(deviceState == 'WORKING'){ showWarnModal.value = true; WarnMessage.value = '设备正在运行中,不能进行关机操作' return }
confirmVisible.value = true confirmInfo.value = { title: '提示', message: '请确认是否进行关机操作?', cancelBtn: '取消', OkBtn: '确认', confirmCallback:()=>{ //关机时的遮罩层
isLoading.value = true; confirmVisible.value = false; //有时接口返回的太快,看不到loading状态,延迟1s,看一到loading状态。
setTimeout(async ()=>{ await shutdown() shutdownMessage.value = '设备已关机,请拔掉电源' },1000) }, cancelCallback: ()=>{ confirmVisible.value = false; } } }
const showInitDeviceAlert = () => { showModal.value = true }
const handleDeviceState = (data: DeviceWorkStateMessage['data']) => { deviceStore.setDeviceState(data) } const handleDeviceContextState = (data: DeviceContextStateMessage['data']) => { deviceStore.setContextState(data) } const handleSensorState = (data: SensorStateMessage['data']) => { deviceStore.setSensorState(data) } const handleFooterState = (data: FooterMessageState['data']) => { deviceStore.setMessageState(data) } const handleTubeHolderStateMessage = (data: TubeHolderStateMessage['data']) => { runningStore.setTubeHolderState(data) } const handleTubeHolderSettingMessage = ( data: TubeHolderSettingMessage['data'], ) => { tubeRackStore.setTubeRacks(data) }
const handleOptScanModuleStateMessage = ( data: OptScanModuleStateMessage['data'], ) => { runningStore.setOptScanModuleState(data) }
const handleIncubationPlateStateMessage = ( data: IncubationPlateStateMessage['data'], ) => { runningStore.setSubTanks(data.subtanks) }
const handleConsumablesState = (data: ConsumablesStateMessage['data']) => { consumableStore.setConsumablesData(data) }
const handleEmergencyPosState = (data: EmergencyPosStateMessage['data']) => { emergencyStore.setInfo(data.tube) }
const getProjectList = async () => { const res = await getProjectInfo() if (res.success) { settingTubeStore.setSupportedProjects(res.data) } } const getBloodTypeList = async () => { const res = await getBloodTypes() if (res.success) { settingTubeStore.setBloodTypes(res.data) } } onMounted(() => { eventBus.on('initDevice', showInitDeviceAlert) eventBus.on('socketClosed', handleSocketClose)
wsEvent.subscribe<AppEventMessage>('AppEvent', handleAppEvent) wsEvent.connect() wsState.subscribe<ConsumablesStateMessage>( 'ConsumablesState', handleConsumablesState, ) wsState.subscribe<DeviceWorkStateMessage>( 'DeviceWorkState', handleDeviceState, ) wsState.subscribe<DeviceContextStateMessage>( 'DeviceContext', handleDeviceContextState, ) wsState.subscribe<SensorStateMessage>('SensorState', handleSensorState) wsState.subscribe<FooterMessageState>('MessageBoxState', handleFooterState) wsState.subscribe<TubeHolderStateMessage>( 'TubeHolderState', handleTubeHolderStateMessage, ) wsState.subscribe<TubeHolderSettingMessage>( 'TubeHolderSetting', handleTubeHolderSettingMessage, ) wsState.subscribe<IncubationPlateStateMessage>( 'IncubationPlateState', handleIncubationPlateStateMessage, ) wsState.subscribe<OptScanModuleStateMessage>( 'OptScanModuleState', handleOptScanModuleStateMessage, ) wsState.subscribe<EmergencyPosStateMessage>( 'EmergencyPosState', handleEmergencyPosState, ) wsState.connect()
getProjectList() getBloodTypeList() })
// onBeforeUnmount(() => {
// eventBus.off('initDevice', showInitDeviceAlert)
// ws.unsubscribe<AppEventMessage>('AppEvent', handleAppEvent)
// ws.disconnect()
// })
// 动态生成错误信息
// const generateErrorMessages = (data: CheckItem[]): string[] => {
// return data
// .filter(item => !item.pass)
// .map(item => `错误:${item.typechinfo} 检测未通过,请检查设备状态。`);
// };
const untilDeviceReady = async () => { deviceWaitingModelInfo.value = { title: '设备正在响应中', message: '请不要有任何手动操作!', } showDeviceWaitingModal.value = true const res = await getDeviceWorkState() if (res.ecode === 'SUC') { if (res.data.pending) { setTimeout(async () => await untilDeviceReady(), 250) } else { showDeviceWaitingModal.value = false } } else { showDeviceWaitingModal.value = false } }
// 开始测试
const startTest = async () => { const res = await getInitState() if (res.ecode === 'SUC' && !res.data.deviceInited) { eventBus.emit('initDevice') return } try { deviceWaitingModelInfo.value = { title: '设备正在响应中', message: '请不要有任何手动操作!', } showDeviceWaitingModal.value = true const res = await startWork() showDeviceWaitingModal.value = false if (res && res.success) { await untilDeviceReady() } else { res && res.data && res.data.info && eMessage.error(res.data.info) } } catch (error) { console.error('开始测试失败:', error) } }
// 暂停测试
const pauseTest = async () => { deviceWaitingModelInfo.value = { title: '设备正在响应中', message: '请不要有任何手动操作!', } showDeviceWaitingModal.value = true const res = await pauseWork() showDeviceWaitingModal.value = false if (res && res.success) { await untilDeviceReady() } else { res && res.data && res.data.info && eMessage.error(res.data.info) } }
// 停止测试时清除标记
const stopTest = async () => { deviceWaitingModelInfo.value = { title: '设备正在响应中', message: '请不要有任何手动操作!', } showDeviceWaitingModal.value = true const res = await stopWork() showDeviceWaitingModal.value = false if (res && res.success) { await untilDeviceReady() } else { res && res.data && res.data.info && eMessage.error(res.data.info) } } //继续测试
const continueTest = async () => { deviceWaitingModelInfo.value = { title: '设备正在响应中', message: '请不要有任何手动操作!', } showDeviceWaitingModal.value = true const res = await continueWork() showDeviceWaitingModal.value = false if (res && res.success) { await untilDeviceReady() } else { res && res.data && res.data.info && eMessage.error(res.data.info) } } const handleConfirm = async () => { showModal.value = false // 关闭初始弹窗
await startInit() }
const startInit = async () => { await initDevice() await pollingInitState() }
const pollingInitState = async () => { showDeviceResettingModal.value = true // 显示 LoadingModal
const res = await getInitState() if (res.ecode === 'SUC') { if (res.data.isBusy) { setTimeout(async () => await pollingInitState(), 500) } else { showDeviceResettingModal.value = false if (res.data.passed) { console.log('初始化成功') sessionStorage.setItem('deviceResetFinished', 'true') showAlreadyModal.value = true } else { const infos = res.data.promopt.detailInfos failMessage.value = { title: '检测失败', message: infos && infos.length > 0 ? infos.map((d: any) => d.name).join('\n') : res.data.promopt.info, } showFailModal.value = true // 显示失败弹窗
} } } else { showDeviceResettingModal.value = false } }
const handleAlreadyConfirm = () => { console.log('用户确认操作') showAlreadyModal.value = false showFailModal.value = false }
onMounted(() => { checkInit() })
// 开始检测
const checkInit = () => { const hasExecutedReset = sessionStorage.getItem('deviceResetFinished') if (!hasExecutedReset || hasExecutedReset === 'false') { showModal.value = true } }
// 更新蓝线的宽度和左偏移量
const updateLinePosition = (element: any) => { lineWidth.value = element.offsetWidth // 当前选中标签的宽度
lineLeft.value = element.offsetLeft // 当前选中标签的左偏移量
}
// 处理标签点击事件
const handleTabClick = (event: any) => { const tabElement = event.target.closest('.nav-tab') if (tabElement) { const tab = tabElement.dataset.tab selectedTab.value = tab sessionStorage.setItem('selectedTab', tab) // 将选中标签存储到 localStorage
updateLinePosition(tabElement) // 更新蓝线的位置和宽度
} }
// 页面加载时默认选中"常规"标签,并设置蓝线的位置
onMounted(() => { const defaultTab = document.querySelector( `.nav-tab[data-tab="${selectedTab.value}"]`, ) if (defaultTab) { updateLinePosition(defaultTab) } })
watch( () => deviceStore.contextState, (newVal) => { if (!newVal.loginFlag) { router.push({ path: '/login', }) sessionStorage.setItem('token', '') } }, ) </script>
<style scoped lang="less"> #index-container { margin: 0; padding: 0; width: 100vw; height: 100vh; background-color: #fff; position: relative; box-sizing: border-box; display: flex; flex-direction: column;
.fade-slide-enter-active, .fade-slide-leave-active { transition: opacity 0.5s, transform 0.5s; /* 控制透明度和滑动的过渡时间 */ }
.fade-slide-enter-from { opacity: 0; transform: translateX(20px); /* 从右侧滑入 */ }
.fade-slide-leave-to { opacity: 0; transform: translateX(-20px); /* 向左滑出 */ }
.nav-bar { width: 100%; height: 60px; margin-bottom: 2px; display: flex; justify-content: space-between; align-items: center;
.logo { width: 100px; height: 75px; padding-left: 10px; }
.nav-tabs { width: 60%; font-size: 24px; display: flex; justify-content: space-around; font-size: 40px;
.nav-tab { cursor: pointer; padding-bottom: 10px; color: black; transition: color 0.3s; position: relative; }
.nav-tab.active { color: #478ffe; } }
.test-control { display: flex; justify-content: space-evenly; padding-right: 10px;
.start-test { width: 200px; height: 48px; background-color: #73bc54; font-size: 20px; }
.pause-test, .stop-test, .continue-test { width: 100px; height: 48px; font-size: 20px; } } }
.interval { width: 100%; height: 5px; background-color: #f5f5f5; position: relative;
.blue-line { position: absolute; bottom: 0; height: 5px; background-color: #478ffe; transition: all 0.3s ease; } }
.footer-info { width: 100%; height: 50px; display: flex; justify-content: end; align-items: center; position: relative; bottom: 0; margin-top: auto;
> *:first-child { margin-right: auto; }
.user-logo, .time-logo { width: 36px; height: 36px; margin-right: 8px; }
.user-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .user-card, .time-card { display: flex; align-items: center; font-size: 24px; }
.equipment-status { width: 440px; height: 44px; line-height: $height; text-align: center; border-radius: 10px; margin-right: 20px; .status-text { border-radius: 8px; font-size: 22px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; &.Info { color: #333; background-color: #f5f5f5; } &.Warn { color: #fa9d3b; background-color: rgb(239, 230, 220); } &.Error { color: #d81212; background-color: rgb(236, 216, 216); } } } .logout { width: 70px; text-align: center; font-size: 20px; margin: auto; } }
.main-content { flex: 1; min-height: 0; // 添加:防止溢出
} } .msg-container { max-height: 850px; padding: 4px; overflow-y: auto; .msg-item { padding: 0 10px; height: 45px; line-height: $height; font-size: 22px; display: flex; gap: 10px; &:nth-child(even) { background-color: rgb(250, 250, 250); } &:nth-child(odd) { background-color: #f2f2f2; } &.Warn { color: #fa9d3b; background-color: rgb(239, 230, 220); } &.Error { color: #d81212; background-color: rgb(236, 216, 216); } } } .event-report-dialog { font-size: 1.5rem; .report-title { font-size: 30px; margin: 18px 0; } .report-desc { font-size: 26px; } .report-detail { font-size: 22px; margin-bottom: 14px; .report-form-item { display: flex; gap: 20px; margin: 0 20px; padding: 20px; border-bottom: 1px solid lightgray; } .report-table-container { } .report-table-header { // background-color: lightgray;
display: flex; text-align: left; font-size: 22px; height: 45px; line-height: $height; padding: 0 20px; > * { flex: 1 1 33%; font-weight: 600; } } .report-table-row { display: flex; text-align: left; height: 45px; line-height: $height; padding: 0 20px; > * { flex: 1 1 33%; } &:nth-child(even) { background-color: rgb(250, 250, 250); } &:nth-child(odd) { background-color: #f2f2f2; } } } .dialog-footer .el-button { height: 50px; width: 100px; font-size: 1.5rem; } }
.pd-5{ padding: 0 5px; font-size: 28px; }
.loading-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(4px); display: flex; justify-content: center; align-items: center; z-index: 9998; }
.loading-content { display: flex; flex-direction: column; align-items: center; gap: 20px; }
.loading-spinner { width: 60px; height: 60px; border: 4px solid #f3f3f3; border-top: 4px solid #4caf50; border-radius: 50%; animation: spin 1s linear infinite; }
.loading-text { color: white; font-size: 24px; font-weight: 500; }
.loading-progress { width: 300px; height: 6px; background: rgba(255, 255, 255, 0.2); border-radius: 3px; overflow: hidden; } </style>
|