feat: 移动式检修车间系统前端完成
- 完成系统日志页面,优化表格滚动和样式 - 完成报警记录页面,优化表格滚动和报警级别显示 - 完成环境参数页面,优化参数显示和监控画面 - 完成参数记录页面,优化图表样式和简洁设计 - 集成MQTT配置,支持实时数据对接 - 统一UI设计风格,采用现代化卡片式布局 - 添加响应式设计,适配不同屏幕尺寸 - 预留MQTT数据接口,支持AC空调和WSD温湿度设备
This commit is contained in:
841
src/components/AlarmRecord.vue
Normal file
841
src/components/AlarmRecord.vue
Normal file
@ -0,0 +1,841 @@
|
||||
<template>
|
||||
<view class="page-container alarm-record-container">
|
||||
<!-- 页面头部 -->
|
||||
<view class="page-header">
|
||||
<view class="header-left">
|
||||
<text class="page-title">报警记录</text>
|
||||
</view>
|
||||
<view class="header-right">
|
||||
<view class="system-title">
|
||||
<view class="system-title-icon">
|
||||
<text class="icon">📋</text>
|
||||
</view>
|
||||
<text class="system-title-text">移动式检修车间系统</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 报警表格 -->
|
||||
<view class="page-content">
|
||||
<view class="alarm-content">
|
||||
<view class="alarm-table">
|
||||
<!-- 表格头部 -->
|
||||
<view class="table-header">
|
||||
<view class="table-cell header-cell content-column">内容</view>
|
||||
<view class="table-cell header-cell type-column">种类</view>
|
||||
<view class="table-cell header-cell time-column">时间</view>
|
||||
<view class="table-cell header-cell level-column">级别</view>
|
||||
<view class="table-cell header-cell action-column">处置</view>
|
||||
<view class="table-cell header-cell action-time-column">时间</view>
|
||||
</view>
|
||||
|
||||
<!-- 表格内容 -->
|
||||
<scroll-view
|
||||
class="table-body"
|
||||
scroll-y="true"
|
||||
:scroll-with-animation="true"
|
||||
:scroll-top="scrollTop"
|
||||
@scrolltolower="onScrollToLower"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<!-- 加载状态 -->
|
||||
<view class="table-loading-container" v-if="isLoading">
|
||||
<view class="table-loading-spinner"></view>
|
||||
<text class="table-loading-text">正在加载报警记录...</text>
|
||||
</view>
|
||||
|
||||
<!-- 表格数据 -->
|
||||
<template v-else>
|
||||
<view
|
||||
v-for="(alarm, index) in alarmList"
|
||||
:key="index"
|
||||
class="table-row"
|
||||
:class="{ 'even-row': index % 2 === 0 }"
|
||||
>
|
||||
<view class="table-cell content-column">{{ alarm.content }}</view>
|
||||
<view class="table-cell type-column">{{ alarm.type }}</view>
|
||||
<view class="table-cell time-column">{{ alarm.time }}</view>
|
||||
<view class="table-cell level-column" :class="getLevelClass(alarm.level)">
|
||||
{{ alarm.level }}
|
||||
</view>
|
||||
<view class="table-cell action-column">{{ alarm.action }}</view>
|
||||
<view class="table-cell action-time-column">{{ alarm.actionTime }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 空数据提示 -->
|
||||
<view class="table-empty-container" v-if="!isLoading && alarmList.length === 0 && hasInitialized">
|
||||
<text class="table-empty-text">暂无报警记录</text>
|
||||
</view>
|
||||
|
||||
<!-- 初始状态提示 -->
|
||||
<view class="table-empty-container" v-if="!isLoading && alarmList.length === 0 && !hasInitialized">
|
||||
<text class="table-empty-text">暂无数据</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部间距,确保最后一条记录完全显示 -->
|
||||
<view class="table-bottom-spacing"></view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
|
||||
// 报警数据
|
||||
const alarmList = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const isConnected = ref(false);
|
||||
const hasInitialized = ref(false);
|
||||
|
||||
// 滚动相关
|
||||
const scrollTop = ref(0);
|
||||
const isScrolling = ref(false);
|
||||
|
||||
// 移除空行占位逻辑,没有数据时只显示"暂无数据"
|
||||
|
||||
// MQTT报警服务接口(预留)
|
||||
const mqttAlarmService = {
|
||||
// 连接MQTT服务器
|
||||
connect: async () => {
|
||||
console.log('MQTT报警服务连接中...');
|
||||
try {
|
||||
// 模拟连接延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
isConnected.value = true;
|
||||
console.log('MQTT报警服务连接成功');
|
||||
return Promise.resolve();
|
||||
} catch (error) {
|
||||
console.error('MQTT报警连接失败:', error);
|
||||
isConnected.value = false;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
|
||||
// 订阅报警数据
|
||||
subscribeAlarmData: () => {
|
||||
console.log('订阅系统报警数据');
|
||||
// 这里后期会实现真实的MQTT报警订阅
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
// 获取历史报警记录
|
||||
getHistoryAlarms: async (limit = 50) => {
|
||||
console.log(`获取历史报警记录,限制${limit}条`);
|
||||
try {
|
||||
isLoading.value = true;
|
||||
hasInitialized.value = true;
|
||||
// 模拟请求延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
|
||||
// 模拟报警数据
|
||||
const mockAlarms = [
|
||||
{
|
||||
content: '湿度45%超标',
|
||||
type: '参数超标',
|
||||
time: '2025-9-3-12:01',
|
||||
level: 'A',
|
||||
action: '恢复',
|
||||
actionTime: '2025-9-3-13:11'
|
||||
},
|
||||
{
|
||||
content: '温度28℃过高',
|
||||
type: '参数超标',
|
||||
time: '2025-9-3-11:45',
|
||||
level: 'B',
|
||||
action: '调整',
|
||||
actionTime: '2025-9-3-12:30'
|
||||
},
|
||||
{
|
||||
content: '洁净度异常',
|
||||
type: '环境异常',
|
||||
time: '2025-9-3-11:20',
|
||||
level: 'A',
|
||||
action: '清理',
|
||||
actionTime: '2025-9-3-11:50'
|
||||
},
|
||||
{
|
||||
content: '设备通讯中断',
|
||||
type: '设备故障',
|
||||
time: '2025-9-3-10:55',
|
||||
level: 'C',
|
||||
action: '重启',
|
||||
actionTime: '2025-9-3-11:05'
|
||||
},
|
||||
{
|
||||
content: '压力值偏低',
|
||||
type: '参数异常',
|
||||
time: '2025-9-3-10:30',
|
||||
level: 'B',
|
||||
action: '检查',
|
||||
actionTime: '2025-9-3-10:45'
|
||||
},
|
||||
{
|
||||
content: '电源电压不稳',
|
||||
type: '电气故障',
|
||||
time: '2025-9-3-10:10',
|
||||
level: 'A',
|
||||
action: '更换',
|
||||
actionTime: '2025-9-3-10:25'
|
||||
},
|
||||
{
|
||||
content: '传感器数据异常',
|
||||
type: '设备异常',
|
||||
time: '2025-9-3-09:50',
|
||||
level: 'B',
|
||||
action: '校准',
|
||||
actionTime: '2025-9-3-10:00'
|
||||
},
|
||||
{
|
||||
content: '网络连接超时',
|
||||
type: '通讯故障',
|
||||
time: '2025-9-3-09:30',
|
||||
level: 'C',
|
||||
action: '重连',
|
||||
actionTime: '2025-9-3-09:35'
|
||||
},
|
||||
{
|
||||
content: '内存使用率过高',
|
||||
type: '系统异常',
|
||||
time: '2025-9-3-09:15',
|
||||
level: 'B',
|
||||
action: '清理',
|
||||
actionTime: '2025-9-3-09:20'
|
||||
},
|
||||
{
|
||||
content: '磁盘空间不足',
|
||||
type: '存储异常',
|
||||
time: '2025-9-3-09:00',
|
||||
level: 'A',
|
||||
action: '扩容',
|
||||
actionTime: '2025-9-3-09:10'
|
||||
},
|
||||
{
|
||||
content: 'CPU温度过高',
|
||||
type: '硬件故障',
|
||||
time: '2025-9-3-08:45',
|
||||
level: 'A',
|
||||
action: '散热',
|
||||
actionTime: '2025-9-3-08:50'
|
||||
},
|
||||
{
|
||||
content: '数据库连接失败',
|
||||
type: '数据异常',
|
||||
time: '2025-9-3-08:30',
|
||||
level: 'B',
|
||||
action: '修复',
|
||||
actionTime: '2025-9-3-08:35'
|
||||
},
|
||||
{
|
||||
content: '配置文件损坏',
|
||||
type: '配置异常',
|
||||
time: '2025-9-3-08:15',
|
||||
level: 'C',
|
||||
action: '恢复',
|
||||
actionTime: '2025-9-3-08:20'
|
||||
},
|
||||
{
|
||||
content: '服务进程异常',
|
||||
type: '进程故障',
|
||||
time: '2025-9-3-08:00',
|
||||
level: 'B',
|
||||
action: '重启',
|
||||
actionTime: '2025-9-3-08:05'
|
||||
},
|
||||
{
|
||||
content: '日志文件过大',
|
||||
type: '存储异常',
|
||||
time: '2025-9-3-07:45',
|
||||
level: 'C',
|
||||
action: '压缩',
|
||||
actionTime: '2025-9-3-07:50'
|
||||
}
|
||||
];
|
||||
|
||||
alarmList.value = mockAlarms;
|
||||
isLoading.value = false;
|
||||
return Promise.resolve(mockAlarms);
|
||||
} catch (error) {
|
||||
console.error('获取历史报警记录失败:', error);
|
||||
isLoading.value = false;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
|
||||
// 获取实时报警
|
||||
getRealtimeAlarms: async () => {
|
||||
console.log('获取实时报警');
|
||||
try {
|
||||
// 模拟实时报警数据
|
||||
const contents = ['温度超标', '湿度异常', '压力偏高', '洁净度超标', '设备故障', '通讯中断'];
|
||||
const types = ['参数超标', '环境异常', '设备故障', '电气故障', '通讯故障'];
|
||||
const levels = ['A', 'B', 'C'];
|
||||
const actions = ['处理中', '已恢复', '待处理', '检查中'];
|
||||
|
||||
const newAlarm = {
|
||||
content: contents[Math.floor(Math.random() * contents.length)],
|
||||
type: types[Math.floor(Math.random() * types.length)],
|
||||
time: new Date().toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
}).replace(/\//g, '-').replace(', ', '-'),
|
||||
level: levels[Math.floor(Math.random() * levels.length)],
|
||||
action: actions[Math.floor(Math.random() * actions.length)],
|
||||
actionTime: new Date().toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'numeric',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
}).replace(/\//g, '-').replace(', ', '-')
|
||||
};
|
||||
|
||||
// 添加到报警列表顶部
|
||||
alarmList.value.unshift(newAlarm);
|
||||
|
||||
// 限制报警数量,保持最新的50条
|
||||
if (alarmList.value.length > 50) {
|
||||
alarmList.value = alarmList.value.slice(0, 50);
|
||||
}
|
||||
|
||||
// 自动滚动到顶部显示最新报警
|
||||
setTimeout(() => {
|
||||
scrollToTop();
|
||||
}, 100);
|
||||
|
||||
return Promise.resolve(newAlarm);
|
||||
} catch (error) {
|
||||
console.error('获取实时报警失败:', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
|
||||
// 确认报警
|
||||
confirmAlarm: async (alarmId) => {
|
||||
console.log('确认报警:', alarmId);
|
||||
try {
|
||||
// 模拟确认操作
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
uni.showToast({
|
||||
title: '报警已确认',
|
||||
icon: 'success'
|
||||
});
|
||||
return Promise.resolve();
|
||||
} catch (error) {
|
||||
console.error('确认报警失败:', error);
|
||||
uni.showToast({
|
||||
title: '确认失败',
|
||||
icon: 'error'
|
||||
});
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
|
||||
// 清空报警记录
|
||||
clearAlarms: async () => {
|
||||
console.log('清空报警记录');
|
||||
try {
|
||||
// 模拟清空操作
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
alarmList.value = [];
|
||||
uni.showToast({
|
||||
title: '报警记录已清空',
|
||||
icon: 'success'
|
||||
});
|
||||
return Promise.resolve();
|
||||
} catch (error) {
|
||||
console.error('清空报警记录失败:', error);
|
||||
uni.showToast({
|
||||
title: '清空失败',
|
||||
icon: 'error'
|
||||
});
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
|
||||
// 断开连接
|
||||
disconnect: () => {
|
||||
console.log('MQTT报警服务断开连接');
|
||||
isConnected.value = false;
|
||||
// 这里后期会实现真实的MQTT断开
|
||||
}
|
||||
};
|
||||
|
||||
// 获取级别样式类
|
||||
const getLevelClass = (level) => {
|
||||
switch (level) {
|
||||
case 'A':
|
||||
return 'level-a';
|
||||
case 'B':
|
||||
return 'level-b';
|
||||
case 'C':
|
||||
return 'level-c';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// 滚动事件处理
|
||||
let scrollTimer = null;
|
||||
|
||||
const onScroll = (e) => {
|
||||
isScrolling.value = true;
|
||||
|
||||
// 防抖处理,避免频繁触发
|
||||
if (scrollTimer) {
|
||||
clearTimeout(scrollTimer);
|
||||
}
|
||||
|
||||
scrollTimer = setTimeout(() => {
|
||||
isScrolling.value = false;
|
||||
}, 150);
|
||||
|
||||
// 可以在这里添加滚动时的逻辑
|
||||
};
|
||||
|
||||
const onScrollToLower = () => {
|
||||
console.log('滚动到底部');
|
||||
// 可以在这里添加加载更多数据的逻辑
|
||||
};
|
||||
|
||||
// 滚动到顶部
|
||||
const scrollToTop = () => {
|
||||
scrollTop.value = scrollTop.value === 0 ? 1 : 0;
|
||||
};
|
||||
|
||||
// 滚动到底部
|
||||
const scrollToBottom = () => {
|
||||
// 使用nextTick确保DOM更新完成
|
||||
nextTick(() => {
|
||||
// 计算滚动到底部的位置
|
||||
const scrollHeight = alarmList.value.length * 80; // 假设每行80rpx
|
||||
scrollTop.value = scrollHeight + 100; // 额外100rpx确保完全滚动到底部
|
||||
});
|
||||
};
|
||||
|
||||
// 定时获取实时报警
|
||||
let realtimeTimer = null;
|
||||
|
||||
const startRealtimeAlarm = () => {
|
||||
if (realtimeTimer) return;
|
||||
|
||||
realtimeTimer = setInterval(() => {
|
||||
if (isConnected.value && !isLoading.value) {
|
||||
// 20%概率生成新报警
|
||||
if (Math.random() < 0.2) {
|
||||
mqttAlarmService.getRealtimeAlarms();
|
||||
}
|
||||
}
|
||||
}, 8000); // 每8秒检查一次
|
||||
};
|
||||
|
||||
const stopRealtimeAlarm = () => {
|
||||
if (realtimeTimer) {
|
||||
clearInterval(realtimeTimer);
|
||||
realtimeTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 组件生命周期
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 连接MQTT并初始化
|
||||
await mqttAlarmService.connect();
|
||||
await mqttAlarmService.subscribeAlarmData();
|
||||
await mqttAlarmService.getHistoryAlarms();
|
||||
|
||||
// 开始实时报警获取
|
||||
startRealtimeAlarm();
|
||||
} catch (error) {
|
||||
console.error('报警系统初始化失败:', error);
|
||||
uni.showToast({
|
||||
title: '连接失败',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopRealtimeAlarm();
|
||||
mqttAlarmService.disconnect();
|
||||
|
||||
// 清理滚动定时器
|
||||
if (scrollTimer) {
|
||||
clearTimeout(scrollTimer);
|
||||
scrollTimer = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/styles/common.scss';
|
||||
|
||||
.alarm-record-container {
|
||||
// 继承通用页面容器样式
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.alarm-content {
|
||||
// 继承通用内容样式
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.alarm-table {
|
||||
// 继承通用表格样式
|
||||
@extend .common-table;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
background: #f8f9fa;
|
||||
color: #3c4043;
|
||||
border-bottom: 1rpx solid #e8eaed;
|
||||
}
|
||||
|
||||
.header-cell {
|
||||
font-weight: 500;
|
||||
font-size: 26rpx;
|
||||
padding: 20rpx 16rpx;
|
||||
text-align: center;
|
||||
letter-spacing: 0.2rpx;
|
||||
}
|
||||
|
||||
.table-body {
|
||||
flex: 1;
|
||||
height: 0; /* 重要:配合flex: 1使用,确保正确计算高度 */
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
/* 滚动条样式 */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #dadce0 #f1f3f4;
|
||||
/* 平滑滚动 */
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Webkit浏览器滚动条样式 */
|
||||
.table-body::-webkit-scrollbar {
|
||||
width: 8rpx;
|
||||
}
|
||||
|
||||
.table-body::-webkit-scrollbar-track {
|
||||
background: rgba(241, 243, 244, 0.5);
|
||||
border-radius: 4rpx;
|
||||
margin: 4rpx 0;
|
||||
}
|
||||
|
||||
.table-body::-webkit-scrollbar-thumb {
|
||||
background: linear-gradient(180deg, #dadce0 0%, #bdc1c6 100%);
|
||||
border-radius: 4rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.table-body::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(180deg, #bdc1c6 0%, #9aa0a6 100%);
|
||||
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.table-body::-webkit-scrollbar-thumb:active {
|
||||
background: linear-gradient(180deg, #9aa0a6 0%, #5f6368 100%);
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
border-bottom: 1rpx solid #e8eaed;
|
||||
transition: all 0.2s ease;
|
||||
min-height: 80rpx;
|
||||
}
|
||||
|
||||
.table-row.even-row {
|
||||
background-color: rgba(248, 249, 250, 0.5);
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background-color: rgba(241, 243, 244, 0.8);
|
||||
transform: translateX(2rpx);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.table-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
padding: 16rpx 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #3c4043;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
min-height: 80rpx;
|
||||
line-height: 1.4;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.content-column {
|
||||
flex: 3;
|
||||
justify-content: flex-start;
|
||||
text-align: left;
|
||||
padding-left: 30rpx;
|
||||
}
|
||||
|
||||
.type-column {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.time-column {
|
||||
flex: 2.5;
|
||||
}
|
||||
|
||||
.level-column {
|
||||
flex: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.action-column {
|
||||
flex: 1.5;
|
||||
}
|
||||
|
||||
.action-time-column {
|
||||
flex: 2.5;
|
||||
}
|
||||
|
||||
.level-a {
|
||||
color: #ea4335;
|
||||
font-weight: 700;
|
||||
position: relative;
|
||||
background: linear-gradient(135deg, rgba(234, 67, 53, 0.1) 0%, rgba(234, 67, 53, 0.05) 100%);
|
||||
border-radius: 8rpx;
|
||||
padding: 8rpx 12rpx;
|
||||
border: 1rpx solid rgba(234, 67, 53, 0.2);
|
||||
}
|
||||
|
||||
.level-a::before {
|
||||
content: '🔴';
|
||||
margin-right: 6rpx;
|
||||
font-size: 18rpx;
|
||||
}
|
||||
|
||||
.level-b {
|
||||
color: #fbbc04;
|
||||
font-weight: 700;
|
||||
position: relative;
|
||||
background: linear-gradient(135deg, rgba(251, 188, 4, 0.1) 0%, rgba(251, 188, 4, 0.05) 100%);
|
||||
border-radius: 8rpx;
|
||||
padding: 8rpx 12rpx;
|
||||
border: 1rpx solid rgba(251, 188, 4, 0.2);
|
||||
}
|
||||
|
||||
.level-b::before {
|
||||
content: '🟡';
|
||||
margin-right: 6rpx;
|
||||
font-size: 18rpx;
|
||||
}
|
||||
|
||||
.level-c {
|
||||
color: #34a853;
|
||||
font-weight: 700;
|
||||
position: relative;
|
||||
background: linear-gradient(135deg, rgba(52, 168, 83, 0.1) 0%, rgba(52, 168, 83, 0.05) 100%);
|
||||
border-radius: 8rpx;
|
||||
padding: 8rpx 12rpx;
|
||||
border: 1rpx solid rgba(52, 168, 83, 0.2);
|
||||
}
|
||||
|
||||
.level-c::before {
|
||||
content: '🟢';
|
||||
margin-right: 6rpx;
|
||||
font-size: 18rpx;
|
||||
}
|
||||
|
||||
/* 移除空行样式,不再需要 */
|
||||
|
||||
.table-bottom-spacing {
|
||||
height: 40rpx;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.table-loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60rpx 20rpx;
|
||||
gap: 20rpx;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.table-loading-spinner {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border: 3rpx solid #e8eaed;
|
||||
border-top: 3rpx solid #5f6368;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.table-loading-text {
|
||||
font-size: 24rpx;
|
||||
color: #5f6368;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.table-empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 80rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.table-empty-text {
|
||||
font-size: 26rpx;
|
||||
color: #9aa0a6;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.table-loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid #e9ecef;
|
||||
border-top: 4rpx solid #6c757d;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.table-loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.table-empty-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 100rpx 20rpx;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.table-empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60rpx;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid rgba(255, 152, 0, 0.3);
|
||||
border-top: 4rpx solid #ff9800;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 100rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #adb5bd;
|
||||
}
|
||||
|
||||
// 响应式设计 - 平板设备适配
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.header-cell {
|
||||
font-size: 28rpx;
|
||||
padding: 24rpx 20rpx;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
font-size: 26rpx;
|
||||
padding: 20rpx 16rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计 - 手机设备适配
|
||||
@media (max-width: 750rpx) {
|
||||
.header-cell {
|
||||
font-size: 22rpx;
|
||||
padding: 16rpx 12rpx;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
font-size: 20rpx;
|
||||
padding: 12rpx 8rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计 - 大屏设备适配
|
||||
@media (min-width: 1200px) {
|
||||
.header-cell {
|
||||
font-size: 30rpx;
|
||||
padding: 28rpx 24rpx;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
font-size: 28rpx;
|
||||
padding: 24rpx 20rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user