<template>
	<view class="notice-board">
		<view class="board-header">
			<view class="card_logo" @click="linkSet">
				<image class="logo_image" src="/static/logo.png" mode=""></image>
				<view class="logo_title">观山湖区疾控中心</view>
			</view>
			<view class="card_yellow">
				<view class="card_board">
					<text class="board-title">接种留观等待</text>
				</view>
			</view>
			<view class="card_time">
				<view class="time_title">{{hhmmss}}</view>
				<view class="current-time">{{ currentTime }} {{whatDay}}</view>
			</view>
		</view>
		<!-- 表格容器 -->
		<view class="table-container">
			<!-- 主要表头 -->
			<view class="table-header">
				<view class="table-row header-row">
					<view class="cell name title_color">姓名</view>
					<view class="cell time title_color">留观时间</view>
					<view class="cell time title_color">离开时间</view>
					<view class="cell status title_color">状态</view>
				</view>
			</view>
			<!-- 特殊状态数据展示区域（固定） -->
			<view class="special-status-container" v-if="specialStatusData.length > 0">
				<view class="table-row" v-for="(item, index) in specialStatusData" :key="item.id"
					:class="`item-${getClass(item.status)}`">
					<view class="table-cell name" :class="`title-${getClass(item.status)}`">{{ item.patientName }}
					</view>
					<view class="table-cell time">{{ formatTime(item.createTime) }}</view>
					<view class="table-cell time">{{ formatTime(item.outTime) }}</view>
					<view class="table-cell status">
						<view class="status-tag" :class="`status-${getClass(item.status)}`">
							{{ getStatusText(item) }}
						</view>
					</view>
				</view>
				<!-- 分隔线 -->
				<view class="divider"></view>
			</view>
			<!-- 普通数据内容区域 -->
			<view class="table-body" @touchstart="handleTouchStart" @touchmove="handleTouchMove"
				@touchend="handleTouchEnd">
				<view class="table-row" v-for="(item, index) in displayNormalData" :key="item.id"
					:class="`item-${getClass(item.status)}`">
					<view class="table-cell name" :class="`title-${getClass(item.status)}`">{{ item.patientName }}
					</view>
					<view class="table-cell time">{{ formatTime(item.createTime) }}</view>
					<view class="table-cell time">{{ formatTime(item.outTime) }}</view>
					<view class="table-cell status">
						<view class="status-tag" :class="`status-${getClass(item.status)}`">
							{{ getStatusText(item) }}
						</view>
					</view>
				</view>
				<!-- 空数据提示 -->
				<view v-if="displayNormalData.length === 0 && specialStatusData.length === 0" class="empty-tip">
					暂无留观人员信息
				</view>
			</view>
		</view>
		<view class="card_foot">
			<view class="card_tips_box">
				<image class="tips_imageil" src="/static/horn.png" mode=""></image>
				<view class="title_tips">温馨提示：</view>
			</view>
			<view class="title_tips_foot">请注意留观30分钟后无不良反应后再离开，谢谢。</view>
		</view>
		<!-- 连接状态与IP编辑栏 -->
		<view class="box_link_set" v-if="linkShow">
			<view class="box_popup" :style="{bottom: keyboardHeight + 'px'}">
				<view class="head_popup_title">
					<view class="title_head_popup">连接设置</view>
					<view class="close_title" @click="getClose">×</view>
				</view>
				<view class="status-bar-bottom" :class="`status-${connectionStatus}`">
					<!-- IP 编辑区域 -->
					<view class="ip-input-group">
						<text class="ip-label">IP:</text>
						<input type="text" :value="serverIp" @input="onIpInput" placeholder="192.168.0.41"
							class="ip-input" @keyboardheightchange="keyboardheightchange" />
						<text class="colon">:</text>
						<input type="number" :value="serverPort" @input="onPortInput" placeholder="8811"
							class="port-input" @keyboardheightchange="keyboardheightchange" />
						<button class="btn-reconnect" size="mini" @click="handleReconnect">
							更新并重连
						</button>
					</view>
					<view class="status-text-box">
						<text class="dot" :class="`dot-${connectionStatus}`">●</text>
						<text class="status-text">{{ connectionText }}</text>
					</view>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				currentTime: '', // 当前时间
				hhmmss: '', // 当前时间
				whatDay: '', //星期几
				timeTimer: null, // 时间更新定时器
				// WebSocket 实例
				ws: null,
				reconnectTimer: null,
				heartbeatTimer: null, // 心跳定时器
				heartbeatTimeout: null, // 心跳响应超时计时器
				heartbeatInterval: 30000, // 30秒发一次心跳
				heartbeatTimeoutTime: 10000, // 10秒内未响应视为超时
				isPongReceived: true, // 标记是否收到 pong
				// 连接状态
				connectionStatus: 'connecting', // connecting, connected, disconnected
				statusText: {
					observing: '留观中',
					completed: '留观完成，可离开',
					warning: '提前离开',
					hasleft: '已离开',
				},
				// 可编辑的服务器地址
				serverIp: '192.168.0.41',
				serverPort: '8811',
				// 所有数据留观数据列表
				allData: [],
				// 当前页码
				currentPage: 1,
				// 每页显示条数（自适应计算）
				pageSize: 10,
				// 是否自动滚动
				autoScroll: true,
				// 自动滚动定时器
				autoScrollTimer: null,
				// 滚动间隔时间（毫秒）
				scrollInterval: 6000,
				// 窗口高度
				windowHeight: 0,
				// 触摸相关
				touchStartX: 0,
				touchStartTime: 0,
				isTouching: false,
				touchStartY: 0,
				// 垂直滑动相关
				lastSwipeTime: 0,
				linkShow: false,
				ipArray: [],
				keyboardHeight: 0,
				now: new Date(),
				timer: null,
				heartbeatRate: 0,
				isSend: true,
				reconnectionNum: 0,
			}
		},
		computed: {
			connectionText() {
				const {
					connectionStatus,
					serverIp,
					serverPort
				} = this;
				return {
					connecting: `正在连接 ${serverIp}:${serverPort}...`,
					connected: `已连接到 ${serverIp}:${serverPort}`,
					disconnected: `连接已断开`
				} [connectionStatus];
			},
			// 特殊状态数据（状态1和2）
			specialStatusData() {
				return this.allData.filter(item => item.status === 3 || item.status === 4 || item.IsOut == 1)
			},
			// 普通状态数据（状态0）
			normalData() {
				return this.allData.filter(item => item.status === 0 || item.status === 1)
			},
			// 实际显示的条数（减去缓冲行）
			actualPageSize() {
				return Math.max(1, this.pageSize - 1)
			},
			// 总页数（基于普通数据计算）
			totalPages() {
				if (this.actualPageSize <= 0 || this.normalData.length === 0) return 1
				return Math.ceil(this.normalData.length / this.actualPageSize)
			},
			// 显示的普通数据（包含缓冲行）
			displayNormalData() {
				const start = (this.currentPage - 1) * this.actualPageSize
				const end = Math.min(start + this.pageSize, this.normalData.length)
				return this.normalData.slice(start, end).map(item => {
					const expectedTime = new Date(item.outTime)
					const timeDiff = expectedTime - this.now // 毫秒差
					if (timeDiff > 0) {
						// 未到离开时间
						const days = Math.floor(timeDiff / (24 * 3600 * 1000))
						const hours = Math.floor((timeDiff % (24 * 3600 * 1000)) / (3600 * 1000))
						const minutes = Math.floor((timeDiff % (3600 * 1000)) / (60 * 1000))
						const seconds = Math.floor((timeDiff % (60 * 1000)) / 1000)
						var fzArr = ''
						if (minutes) {
							fzArr = `剩余${minutes}分钟`
						} else {
							fzArr = `剩余${seconds}秒`
						}
						return {
							...item,
							remainingTime: fzArr,
						}
					} else {
						return {
							...item,
							remainingTime: '留观完成,可离开',
						}
					}
				})
			}
		},
		mounted() {
			this.reconnectionNum = 0
			// 每秒更新当前时间
			this.timer = setInterval(() => {
				this.now = new Date()
			}, 1000)
			this.getIpAddress()
			this.initPage()
			const lastIp = uni.getStorageSync('serverIp');
			const lastPort = uni.getStorageSync('serverPort');
			if (lastIp && lastPort) {
				this.serverIp = lastIp
				this.serverPort = lastPort
			}
			// 在H5平台添加鼠标滚轮支持
			//#ifdef H5
			this.startCheck()
			this.addMouseWheelSupport()
			//#endif
			this.updateCurrentTime(); // 立即更新一次时间
			this.timeTimer = setInterval(() => {
				this.updateCurrentTime();
			}, 1000); // 每秒更新一次时间
		},
		beforeDestroy() {
			// 组件销毁时清理定时器
			if (this.timer) {
				this.timer = null
				clearInterval(this.timer)
			}
			this.stopAutoScroll()
			uni.offWindowResize(this.handleWindowResize)
			//#ifdef H5
			this.removeMouseWheelSupport()
			//#endif
			if (this.ws) {
				this.ws.close();
			}
			if (this.reconnectTimer) {
				clearTimeout(this.reconnectTimer);
			}
			this.stopHeartbeat();
			// 清理时间更新定时器
			if (this.timeTimer) {
				clearInterval(this.timeTimer);
			}
		},
		methods: {
			keyboardheightchange() {
				uni.onKeyboardHeightChange(res => {
					this.keyboardHeight = res.height
				})
			},
			findFirstWebSocketIp(ipList, index = 0, onSuccess, onAllFailed) {
				// 终止条件：全部失败
				if (index >= ipList.length) {
					if (typeof onAllFailed === 'function') {
						onAllFailed();
					}
					return;
				}
				// console.log(ipList, 344)
				const ip = ipList[index].trim();
				const port = this.serverPort; // 根据你的服务修改端口
				const url = `ws://${ip}:${port}/`; // 可改为 /ws, /device 等路径
				// console.log(`🔍 正在尝试连接 WebSocket: ${url}`);
				// 先确保没有遗留连接
				if (this.ws) {
					this.ws.close();
					this.ws = null;
				}
				// 创建 SocketTask
				this.ws = uni.connectSocket({
					url: url,
					success: (res) => {
						console.log('connectSocket success', res);
					},
					fail: (err) => {
						console.error('connectSocket 失败', err);
						this.connectionStatus = 'disconnected';
						if (this.serverIp == ip) {
							this.heartbeatRate = 0
							this.reconnectionNum++
							//#ifdef H5
							this.isSend = true
							this.findFirstWebSocketIp([this.serverIp], index, onSuccess, onAllFailed);
							//#endif
						}
						//#ifdef APP
						this.reconnectionNum++
						this.isSend = true
						this.heartbeatRate = 0
						this.findFirstWebSocketIp(ipList, index + 1, onSuccess, onAllFailed);
						//#endif
					}
				});
				// --- WebSocket 事件监听 ---
				this.ws.onOpen((res) => {
					this.serverIp = ip
					uni.setStorageSync('serverIp', this.serverIp);
					uni.setStorageSync('serverPort', this.serverPort);
					// console.log('WebSocket 连接成功', res);
					this.connectionStatus = 'connected';
					this.ws.send({
						data: 'link',
					});
					onSuccess(ip);
					// ✅ 连接成功后启动心跳
					this.startHeartbeat();
					uni.hideLoading();
				});
				this.ws.onMessage((res) => {
					// ✅ 处理心跳响应 pong
					if (res.data === 'PONG' || res.data === '{"type":"PONG"}') {
						uni.hideToast();
						this.isSend = true
						this.reconnectionNum = 0
						this.heartbeatRate = 0
						this.isPongReceived = true;
						// console.log('收到 pong，心跳正常');
						// ✅ 清除等待 pong 的超时计时器
						if (this.heartbeatTimeout) {
							clearTimeout(this.heartbeatTimeout);
							this.heartbeatTimeout = null;
						}
						return;
					}
					try {
						const data = JSON.parse(res.data);
						// console.log('收到消息:', data);
						this.handleMessage(data);
					} catch (e) {
						console.warn('非 JSON 消息，已忽略', res.data);
					}
				});
				this.ws.onClose((res) => {
					console.log('WebSocket 连接关闭', res);
					this.connectionStatus = 'disconnected';
					this.stopHeartbeat(); // 停止心跳
					var reconnectionTime = setTimeout(() => {
						clearTimeout(reconnectionTime)
						if (this.serverIp == ip) {
							//#ifdef H5
							this.isSend = true
							this.heartbeatRate = 0
							this.reconnectionNum++
							this.findFirstWebSocketIp([this.serverIp], index, onSuccess, onAllFailed);
							//#endif
						}
						//#ifdef APP
						this.isSend = true
						this.heartbeatRate = 0
						this.reconnectionNum++
						this.findFirstWebSocketIp(ipList, index + 1, onSuccess, onAllFailed);
						//#endif
					}, 2000)
				});
				this.ws.onError((err) => {
					console.error('WebSocket 错误', err);
					this.connectionStatus = 'disconnected';
					//#ifdef APP
					this.isSend = true
					this.heartbeatRate = 0
					this.reconnectionNum++
					this.findFirstWebSocketIp(ipList, index + 1, onSuccess, onAllFailed);
					//#endif
				});
			},
			// 连接设置
			linkSet() {
				this.linkShow = true
			},
			// 关闭弹窗
			getClose() {
				this.linkShow = false
			},
			// 获取ip地址
			getIpAddress() {
				//获取插件示例
				//#ifdef APP
				var deviceFinder = uni.requireNativePlugin("Alikes-NetTools-DeviceFinder")
				deviceFinder.scan({}, (res) => {
					this.ipArray = res
					this.ipArray.unshift(this.serverIp)
					this.startCheck()
				})
				//#endif
			},
			// 启动检查
			startCheck() {
				var ipArray = []
				//#ifdef H5
				ipArray = [this.serverIp]
				//#endif
				//#ifdef APP
				ipArray = this.ipArray
				//#endif
				this.findFirstWebSocketIp(
					ipArray,
					0,
					(ip) => {
						// ✅ 找到可用设备
						this.onDeviceFound(ip);
						this.linkShow = false
					},
					() => {
						// ❌ 全部失败
						this.onNoDevice();
					}
				);
			},
			// 找到设备后的逻辑
			onDeviceFound(ip) {
				uni.showToast({
					title: '连接成功',
					icon: 'success'
				});
				// console.log(`🚀 可用设备: ${ip}，开始后续操作...`);
				// 例如：跳转控制页面、订阅消息等
			},
			// 无设备可用
			onNoDevice() {
				uni.showToast({
					title: '无设备响应',
					icon: 'none',
					duration: 3000
				});
				this.getIpAddress()
				// console.log('🚨 所有设备 WebSocket 均无法连接');
			},
			getClass(status) {
				var title = '';
				if (status == 0) {
					title = 'observing'
				} else if (status == 1) {
					title = 'completed'
				} else if (status == 3) {
					title = 'warning'
				} else if (status == 4) {
					title = 'hasleft'
				}
				return title
			},
			// 获取状态文本
			getStatusText(item) {
				if (item.status == 0) {
					return item.remainingTime
					// 每秒更新一次
				} else if (item.status == 1) {
					return this.statusText.completed
				} else if (item.status == 3) {
					return this.statusText.warning
				} else if (item.status == 4) {
					return this.statusText.hasleft
				}
			},
			// 初始化页面
			initPage() {
				this.calculatePageSize()
				this.handleWindowResize = this.debounce(this.calculatePageSize, 300)
				uni.onWindowResize(this.handleWindowResize)
			},
			// 计算自适应的页面大小
			calculatePageSize() {
				try {
					// 获取系统信息
					const systemInfo = uni.getSystemInfoSync()
					this.windowHeight = systemInfo.windowHeight

					// 计算可用高度
					const headerHeight = uni.upx2px(130) // 头部高度
					const paginationHeight = uni.upx2px(100) // 分页区域高度
					const tableHeaderHeight = uni.upx2px(120) // 表格头部高度
					const specialDataHeight = this.specialStatusData.length > 0 ?
						uni.upx2px(80 * this.specialStatusData.length) : 0 // 特殊数据区域高度 + 分隔线
					const padding = uni.upx2px(0) // 上下padding

					const availableHeight = this.windowHeight - headerHeight - paginationHeight -
						tableHeaderHeight - specialDataHeight - padding

					// 计算行高（px）- 固定60rpx
					const rowHeightPx = uni.upx2px(140)

					// 计算可显示的行数
					const calculatedPageSize = Math.floor(availableHeight / rowHeightPx)

					// 设置页面大小（最少显示4行，最多显示30行）
					this.pageSize = Math.max(4, Math.min(30, calculatedPageSize))

					// 验证当前页
					this.validateCurrentPage()

				} catch (error) {
					console.error('计算页面大小失败:', error)
					this.pageSize = 10 // 默认值
				}
			},
			// 防抖函数
			debounce(func, wait) {
				let timeout
				return function executedFunction(...args) {
					const later = () => {
						clearTimeout(timeout)
						func(...args)
					}
					clearTimeout(timeout)
					timeout = setTimeout(later, wait)
				}
			},
			// 验证当前页是否有效
			validateCurrentPage() {
				if (this.currentPage > this.totalPages && this.totalPages > 0) {
					this.currentPage = this.totalPages
				}
				if (this.currentPage < 1) {
					this.currentPage = 1
				}
			},
			// 构建 WebSocket URL
			getWebSocketUrl() {
				return `ws://${this.serverIp}:${this.serverPort}`;
			},
			onIpInput(value) {
				this.serverIp = value.detail.value
			},
			// 输入
			onPortInput(value) {
				this.serverPort = value.detail.value
			},
			// 处理重连
			handleReconnect() {
				uni.showLoading({
					title: '正在重连...'
				});
				this.linkShow = false
				this.connectionStatus = 'connecting';
				// 关闭旧连接
				if (this.ws) {
					this.ws.close({
						success: () => {
							// console.log('旧连接已关闭');
							//#ifdef H5
							this.startCheck()
							//#endif
							//#ifdef APP
							this.getIpAddress()
							//#endif
						},
						fail: () => {
							//#ifdef H5
							this.startCheck()
							//#endif
							//#ifdef APP
							this.getIpAddress()
							//#endif
						}
					});
				} else {
					//#ifdef H5
					this.startCheck()
					//#endif
					//#ifdef APP
					this.getIpAddress()
					//#endif
				}
				setTimeout(() => {
					uni.hideLoading();
				}, 2000);
			},
			// 处理收到的消息
			handleMessage(data) {
				// 1. 全量数据：数组（不是对象，或没有 action 字段）
				if (data.action == 'link') {
					this.updateListWithArray(data.data);
					return;
				}
				// 2. 增量操作：对象
				if (typeof data === 'object' && data !== null) {
					const action = data.action;

					if (!action) {
						console.warn('消息缺少 action 字段', data);
						return;
					}
					switch (action) {
						case 'add':
						case 'create':
							this.batchAdd(data.data);
							break;
						case 'update':
							this.batchUpdate(data.data);
							break;
						case 'remove':
						case 'delete':
							this.batchRemove(data.data);
							break;
						default:
							console.warn('未知操作类型:', action);
					}
				} else {
					console.warn('收到未知格式消息:', data);
				}
			},
			// 批量新增
			batchAdd(data) {
				if (!data) return;
				const items = Array.isArray(data) ? data : [data]; // 兼容单条
				const validItems = items.filter(item => item && item.id !== undefined);
				if (validItems.length === 0) {
					console.warn('没有有效数据用于新增', data);
					return;
				}
				// 避免重复添加
				const updated = [...this.allData];
				validItems.forEach(item => {
					const index = updated.findIndex(i => i.id == item.id);
					if (index > -1) {
						// 已存在 → 更新
						updated[index] = {
							...updated[index],
							...item
						};
					} else {
						updated.unshift(item);
					}
				});
				this.allData = updated;
			},
			// 批量修改
			batchUpdate(data) {
				if (!data) return;
				const items = Array.isArray(data) ? data : [data]; // 支持单条或数组
				const updated = [...this.allData];
				items.forEach(item => {
					if (!item || item.id === undefined) return;
					const index = updated.findIndex(i => i.id == item.id);
					if (index > -1) {
						updated[index] = {
							...updated[index],
							...item
						};
					}
					// else { this.allData.push(item); } // 可选：当作新增
				});
				this.allData = updated;
			},
			// 批量删除
			batchRemove(data) {
				let idsToRemove = [];
				if (Array.isArray(data.id)) {
					// remove: { id: [1,2,3] }
					idsToRemove = data.id;
				} else if (data.id !== undefined) {
					// remove: { id: 1 }
					idsToRemove = [data.id];
				} else if (Array.isArray(data.data)) {
					// remove: { data: [ {id:1}, {id:2} ] }
					idsToRemove = data.data.map(item => item.id).filter(id => id !== undefined);
				} else {
					idsToRemove = [data.id];
					console.warn('无法解析删除指令', data);
					return;
				}
				if (idsToRemove.length === 0) return;
				const updated = this.allData.filter(item => !idsToRemove.includes(item.id));
				this.allData = updated;
			},
			updateListWithArray(newList) {
				this.allData = []
				if (!Array.isArray(newList)) return;
				const map = new Map();
				newList.forEach(item => {
					if (item.id !== undefined) {
						map.set(item.id, item);
					}
				});
				const updated = [...this.allData];
				// 更新或新增
				for (const [id, item] of map.entries()) {
					const index = updated.findIndex(i => i.id == id);
					if (index > -1) {
						updated[index] = item;
					} else {
						updated.push(item);
					}
				}
				const final = updated.filter(item => map.has(item.id));
				this.allData = final;
			},
			// 断线重连（指数退避）
			reconnect() {
				if (this.reconnectTimer) return; // 防止重复定时器
				this.reconnectTimer = setTimeout(() => {
					// console.log('正在尝试重新连接...');
					this.connectWebSocket();
					this.reconnectTimer = null;
				}, 3000);
			},
			// 启动心跳
			startHeartbeat() {
				// 清除旧心跳
				var that = this
				that.stopHeartbeat();
				// 发送 ping 的定时器
				that.heartbeatTimer = setInterval(() => {
					if (that.ws && that.connectionStatus === 'connected') {
						// 检查上一次的 pong 是否已收到，未收到则判定为断线
						if (!that.isSend && !that.isPongReceived) {
							that.heartbeatRate++
							console.warn(that.heartbeatRate, '上一次心跳未收到 pong，可能已断线新');
							if (that.heartbeatRate >= 3) {
								that.ws.close();
								return;
							}
						}
						// 发送心跳
						that.isPongReceived = false; // 等待 pong
						// 设置超时检测：10秒内没收到 pong 就断开
						that.heartbeatTimeout = setTimeout(() => {
							if (that.isSend && !that.isPongReceived) {
								console.warn('⚠️ 心跳超时：未在规定时间内收到 pong，即将重连');
								uni.showToast({
									title: '连接异常，正在重连...',
									icon: 'none',
									duration: 2000
								});
								that.ws.close(); // 触发 onClose → 重连
							}
						}, that.heartbeatTimeoutTime); // 使用配置的超时时间（如 10000ms）
						if (that.isSend) {
							that.ws.send({
								data: 'PING',
								success: () => {
									// console.log('ping 已发送');
									that.isSend = false
								},
								fail: (err) => {
									console.error('ping 发送失败', err);
									that.ws.close();
								}
							});
						}
					}
				}, that.heartbeatInterval);
				// 可选：启动响应超时检测（更严格）
				// 通常靠"发 ping 后未收到 pong"即可判断
			},
			// 停止心跳（断开连接时调用）
			stopHeartbeat() {
				if (this.heartbeatTimer) {
					clearInterval(this.heartbeatTimer);
					this.heartbeatTimer = null;
				}
				if (this.heartbeatTimeout) {
					clearTimeout(this.heartbeatTimeout);
					this.heartbeatTimeout = null;
				}
			},
			// 格式化时间
			formatTime(dateTimeStr) {
				// 创建 Date 对象
				const date = new Date(dateTimeStr);
				// 提取小时和分钟，并格式化为两位数
				const hours = String(date.getHours()).padStart(2, '0');
				const minutes = String(date.getMinutes()).padStart(2, '0');
				const time = `${hours}:${minutes}`;
				return time
			},
			// 上一页
			prevPage() {
				if (this.currentPage > 1) {
					this.currentPage--
					this.resetAutoScroll()
				}
			},
			// 下一页
			nextPage() {
				if (this.currentPage < this.totalPages) {
					this.currentPage++
					this.resetAutoScroll()
				}
			},
			// 触摸开始
			handleTouchStart(e) {
				this.touchStartX = e.touches[0].clientX
				this.touchStartY = e.touches[0].clientY
				this.touchStartTime = Date.now()
				this.isTouching = true
				// 停止自动滚动
				if (this.autoScroll) {
					this.stopAutoScroll()
				}
			},
			// 触摸移动
			handleTouchMove(e) {
				if (!this.isTouching) return
			},
			// 触摸结束
			handleTouchEnd(e) {
				if (!this.isTouching) return
				const touchEndX = e.changedTouches[0].clientX
				const touchEndY = e.changedTouches[0].clientY
				const touchEndTime = Date.now()
				const deltaX = touchEndX - this.touchStartX
				const deltaY = touchEndY - this.touchStartY
				const deltaTime = touchEndTime - this.touchStartTime
				// 防抖：限制滑动切换频率
				const now = Date.now()
				if (now - this.lastSwipeTime < 300) {
					this.isTouching = false
					return
				}
				// 判断滑动方向
				const absDeltaX = Math.abs(deltaX)
				const absDeltaY = Math.abs(deltaY)
				// 水平滑动优先
				if (absDeltaX > 50 && absDeltaX > absDeltaY && deltaTime < 500) {
					this.lastSwipeTime = now
					if (deltaX > 0) {
						// 向右滑动 - 上一页
						this.prevPage()
					} else {
						// 向左滑动 - 下一页
						this.nextPage()
					}
				}
				// 垂直滑动
				else if (absDeltaY > 50 && absDeltaY > absDeltaX && deltaTime < 500) {
					this.lastSwipeTime = now
					if (deltaY > 0) {
						// 向下滑动 - 上一页
						this.prevPage()
					} else {
						// 向上滑动 - 下一页
						this.nextPage()
					}
				}
				this.isTouching = false
				// 如果开启了自动滚动，重新启动
				if (this.autoScroll) {
					this.resetAutoScroll()
				}
			},
			// 添加鼠标滚轮支持（仅H5平台）
			//#ifdef H5
			addMouseWheelSupport() {
				const tableBody = document.querySelector('.table-body')
				if (tableBody) {
					this.wheelHandler = this.debounce((e) => {
						e.preventDefault()
						const delta = e.wheelDelta || -e.detail
						if (delta < 0) {
							// 向下滚动 - 下一页
							this.nextPage()
						} else {
							// 向上滚动 - 上一页
							this.prevPage()
						}
					}, 150)

					tableBody.addEventListener('mousewheel', this.wheelHandler, {
						passive: false
					})
					tableBody.addEventListener('DOMMouseScroll', this.wheelHandler, {
						passive: false
					})
				}
			},
			// 移除鼠标滚轮支持
			removeMouseWheelSupport() {
				const tableBody = document.querySelector('.table-body')
				if (tableBody && this.wheelHandler) {
					tableBody.removeEventListener('mousewheel', this.wheelHandler)
					tableBody.removeEventListener('DOMMouseScroll', this.wheelHandler)
				}
			},
			//#endif
			// 切换自动滚动
			toggleAutoScroll(e) {
				this.autoScroll = e.detail.value
				if (this.autoScroll) {
					this.startAutoScroll()
				} else {
					this.stopAutoScroll()
				}
			},
			// 开始自动滚动
			startAutoScroll() {
				this.stopAutoScroll()
				if (this.autoScroll && this.normalData.length > 0 && this.totalPages > 1) {
					this.autoScrollTimer = setInterval(() => {
						if (this.currentPage < this.totalPages) {
							this.currentPage++
						} else {
							this.currentPage = 1 // 回到第一页
						}
					}, this.scrollInterval)
				}
			},
			// 停止自动滚动
			stopAutoScroll() {
				if (this.autoScrollTimer) {
					clearInterval(this.autoScrollTimer)
					this.autoScrollTimer = null
				}
			},
			// 重置自动滚动（手动操作后）
			resetAutoScroll() {
				if (this.autoScroll) {
					this.stopAutoScroll()
					setTimeout(() => {
						this.startAutoScroll()
					}, this.scrollInterval)
				}
			},
			// 当前时间
			updateCurrentTime() {
				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 hours = String(now.getHours()).padStart(2, '0');
				const minutes = String(now.getMinutes()).padStart(2, '0');
				const seconds = String(now.getSeconds()).padStart(2, '0');
				this.currentTime = `${year}-${month}-${day}`;
				this.hhmmss = `${hours}:${minutes}:${seconds}`;
				const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
				const weekday = weekdays[now.getDay()];
				this.whatDay = weekday
			},
		},
		watch: {
			// 监听数据变化
			allData: {
				handler() {
					this.$nextTick(() => {
						this.validateCurrentPage()
						if (this.autoScroll) {
							this.startAutoScroll()
						}
					})
				},
				immediate: true
			},
			// 监听页面大小变化
			pageSize() {
				this.validateCurrentPage()
			},
			// 监听特殊状态数据变化
			specialStatusData() {
				// 特殊状态数据变化时重新计算页面大小
				this.$nextTick(() => {
					this.calculatePageSize()
				})
			},
			// 重连次数
			reconnectionNum: {
				handler(newVal, oldVal) {
					if (newVal >= 50) {
						//#ifdef APP
						plus.runtime.restart();
						setTimeout(() => {
							plus.navigator.closeSplashscreen();
						}, 3000);
						this.reconnectionNum = 0
						//#endif
					}
					// console.log('对象属性变化', newVal, oldVal);
				},
				deep: true,
				immediate: true
			}
		}
	}
</script>

<style scoped>
	.notice-board {
		width: 100%;
		height: 100vh;
		display: flex;
		flex-direction: column;
		background-color: #0d54ec;
		/* padding: 20rpx; */
		box-sizing: border-box;
	}

	.card_logo {
		position: absolute;
		left: 30rpx;
		top: 0;
		bottom: 0;
		display: flex;
		align-items: center;
		cursor: pointer;
	}

	.card_logo:focus {
		border: none !important;
	}

	.board-header {
		/* padding-top: 10rpx; */
		text-align: center;
		/* margin-bottom: 30rpx; */
		position: relative;
		display: flex;
		align-items: center;
		justify-content: center;
		height: 130rpx;
		flex-shrink: 0;
		background-color: #0d54ec;
		box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.2);
	}

	.card_yellow {
		display: flex;
		align-items: center;
		justify-content: center;
		width: fit-content;
		width: -webkit-fit-content;
		width: -moz-fit-content;
		padding: 0rpx 40rpx;
		height: 130rpx;
		background-color: #e5cb8d;
		border-bottom-right-radius: 300rpx;
		border-top-left-radius: 300rpx;
	}

	.card_board {
		display: flex;
		align-items: center;
		justify-content: center;
		width: fit-content;
		width: -webkit-fit-content;
		width: -moz-fit-content;
		padding: 0rpx 140rpx;
		height: 130rpx;
		background-color: #d5f3ff;
		border-bottom-right-radius: 120rpx;
		border-top-left-radius: 120rpx;
	}

	.board-title {
		font-size: 60rpx;
		font-weight: bold;
		color: #0e6699;
	}

	.card_time {
		position: absolute;
		right: 30rpx;
		top: 0;
		bottom: 0;
		display: flex;
		flex-direction: column;
		align-items: flex-end;
		justify-content: center;
	}

	.time_title {
		font-size: 60rpx;
		color: #fff;
		font-weight: bold;
		line-height: 60rpx;
	}

	.current-time {
		display: flex;
		align-items: center;
		display: flex;
		font-size: 40rpx;
		color: #fff;
		font-weight: bold;
		line-height: 45rpx;
	}

	.table-container {
		flex: 1;
		margin: 20rpx 20rpx 0rpx 20rpx;
		border-radius: 24rpx;
		overflow: hidden;
		box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
		background-color: #ffffff;
		display: flex;
		flex-direction: column;
	}

	.table-header {
		background-color: #71a0ee;
		color: white;
		font-weight: bold;
	}

	.table-row {
		display: flex;
		border-bottom: 2rpx solid #dedede;
	}

	.header-row {
		/* background-color: #71a0ee; */
		color: white;
		font-weight: bold;
	}

	.table-cell {
		padding: 24rpx 16rpx;
		font-size: 50rpx;
		text-align: center;
		color: #333;
		display: flex;
		justify-content: center;
		align-items: center;
	}


	.cell {
		padding: 24rpx 16rpx;
		font-size: 50rpx;
		text-align: center;
		color: #333;
	}

	.name {
		width: 25%;
		white-space: nowrap;
		text-overflow: ellipsis;
		overflow: hidden;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.time {
		width: 25%;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.status {
		width: 25%;
	}

	.title_color {
		color: #fff;
	}

	/* 特殊状态数据容器 */
	.special-status-container {
		background-color: #fff8e6;
	}

	.special-row {
		background-color: #fff8e6 !important;
		min-height: 60rpx;
		line-height: 60rpx;
	}

	/* 分隔线 */
	.divider {
		height: 1rpx;
		background-color: #ddd;
	}

	/* 普通数据内容区域 */
	.table-body {
		flex: 1;
		overflow-y: auto;
		/* 添加触摸反馈 */
		-webkit-overflow-scrolling: touch;
		touch-action: pan-y;
	}

	.title-observing {
		color: #007bff;
	}

	.title-completed {
		color: #28a745;
	}

	.title-warning {
		color: #ff0000;
	}

	.title-hasleft {
		color: #fa8c16;
	}

	/* 状态行样式 */
	.status-observing {
		background-color: #9dd6ff;
		color: #007bff;
		border: 2rpx solid #48a0ff;
	}

	.status-completed {
		background-color: #e8f5e8;
		color: #28a745;
		border: 2rpx solid #8bc34a;
	}

	.status-warning {
		background-color: #ffebee;
		color: #ff0000;
		border: 2rpx solid #ef9a9a;
	}

	.status-hasleft {
		background-color: #fff2e8;
		color: #fa8c16;
		border: 2rpx solid #ffbb96;
	}

	.item-observing {
		background-color: #9dd6ff;
	}

	.item-completed {
		background-color: #e8f5e8;
	}

	.item-warning {
		background-color: #ffebee;
	}

	.item-hasleft {
		background-color: #fff2e8;
	}

	/* 状态标签样式 - 控制高度不超过40rpx */
	.status-tag {
		flex: none;
		display: flex;
		align-items: center;
		justify-content: center;
		padding: 8rpx 20rpx;
		border-radius: 32rpx;
		font-size: 48rpx;
		font-weight: 500;
		width: calc(100% - 20rpx);
	}

	.empty-tip {
		text-align: center;
		padding: 80rpx;
		color: #999;
		font-size: 60rpx;
		font-style: italic;
	}

	/* 底部 */
	.card_foot {
		display: flex;
		align-items: center;
		height: 120rpx;
		box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
	}

	.card_tips_box {
		display: flex;
		align-items: center;
		padding-left: 15rpx;
	}

	.title_tips {
		color: #fff;
		font-size: 40rpx;
	}

	.title_tips_foot {
		color: #fff;
		flex: 1;
		font-size: 40rpx;
		border-radius: 20rpx;
		margin-right: 20rpx;
		background-color: #71a0ee;
		padding: 20rpx;
	}

	.box_link_set {
		position: fixed;
		display: flex;
		align-items: center;
		justify-content: center;
		width: 100%;
		height: 100%;
		background-color: rgba(0, 0, 0, 0.3);
	}

	.box_popup {
		position: relative;
		background-color: #ffffff;
		border-radius: 8rpx;
	}

	.head_popup_title {
		position: relative;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.title_head_popup {
		font-size: 40rpx;
		font-weight: 500;
		padding: 30rpx 30rpx 0rpx 30rpx;
	}

	.close_title {
		position: absolute;
		right: 30rpx;
		font-size: 40rpx;
		cursor: pointer;
	}

	/* // 连接状态栏 */
	.status-bar-bottom {
		display: flex;
		flex-direction: column;
		margin: 30rpx;
		padding: 24rpx 30rpx;
		background-color: #ffffff;
		box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
		border-radius: 24rpx;
		display: flex;
		gap: 16rpx;
	}

	.status-text-box {
		display: flex;
		align-items: center;
		font-size: 28rpx;
	}

	.dot {
		color: #d32f2f;
		font-size: 24rpx;
	}

	.dot-connecting {
		color: #f9ae3d;
	}

	.dot-connected {
		color: #28a745;
	}

	.dot-disconnected {
		color: #d32f2f;
	}

	.status-text {
		color: #555;
	}

	.status-text {
		margin-left: 16rpx;
	}

	.title_color {
		color: #fff;
	}

	/* // IP 输入区域 */
	.ip-input-group {
		display: flex;
		align-items: center;
		margin-left: 16rpx;
		flex-wrap: wrap;
		gap: 12rpx;
	}

	.ip-label {
		font-size: 28rpx;
		color: #555;
	}

	.ip-input,
	.port-input {
		border: 2rpx solid #ddd;
		border-radius: 12rpx;
		padding: 12rpx 20rpx;
		font-size: 28rpx;
		width: 200rpx;
	}

	.port-input {
		width: 160rpx;
	}

	.colon {
		font-size: 32rpx;
		color: #666;
		margin: 0 8rpx;
	}

	.btn-reconnect {
		background-color: #007aff;
		color: white;
		font-size: 24rpx;
		padding: 0 16rpx;
		border-radius: 12rpx;
	}

	.logo_image {
		width: 80rpx;
		height: 80rpx;
	}

	.logo_title {
		font-size: 50rpx;
		color: #fff;
		font-weight: bold;
		margin-left: 15rpx;
	}

	.tips_imageil {
		width: 70rpx;
		height: 70rpx;
	}

	/* 响应式优化 */
	@media screen and (max-height: 600px) {
		.card_logo {
			left: 10rpx;
		}

		.logo_title {
			font-size: 18rpx;
			margin-left: 5rpx;
		}

		.logo_image {
			width: 35rpx;
			height: 35rpx;
		}

		.table-row {
			min-height: 35rpx;
			line-height: 35rpx;
			padding: 4rpx 0rpx;
		}

		.table-cell {
			font-size: 22rpx;
			padding: 30rpx 10rpx;
		}

		.status-tag {
			padding: 4rpx 10rpx;
			font-size: 18rpx;
			height: 22rpx;
			min-width: 80rpx;
		}

		.control-btn {
			height: 50rpx;
			line-height: 50rpx;
			font-size: 22rpx;
		}

		.board-header {
			height: 50rpx;
		}

		.card_yellow {
			height: 50rpx;
			padding: 0rpx 20rpx;
		}

		.card_board {
			height: 50rpx;
			padding: 0rpx 30rpx;
		}

		.board-title {
			font-size: 20rpx;
		}

		.card_time {
			right: 10rpx;
		}

		.time_title {
			font-size: 20rpx;
			line-height: 20rpx;
		}

		.current-time {
			font-size: 15rpx;
			line-height: 15rpx;
		}

		.table-container {
			margin: 10rpx 10rpx 0rpx 10rpx;
			border-radius: 8rpx;
		}

		.table-cell {
			padding: 2rpx 5rpx;
			font-size: 18rpx;
		}

		.cell {
			padding: 2rpx 5rpx;
			font-size: 18rpx;
		}

		.card_foot {
			height: 50rpx;
		}

		.title_tips {
			font-size: 15rpx;
		}

		.title_tips_foot {
			font-size: 15rpx;
			padding: 2rpx 2rpx 2rpx 5rpx;
			border-radius: 8rpx;
			margin-right: 10rpx;
		}

		.tips_imageil {
			width: 30rpx;
			height: 30rpx;
		}

		.card_tips_box {
			padding-left: 10rpx;
		}

		.empty-tip {
			padding: 40rpx;
			font-size: 20rpx;
		}
	}


	/* 滚动条样式 */
	.table-body::-webkit-scrollbar {
		width: 4rpx;
	}

	.table-body::-webkit-scrollbar-track {
		background: #f1f1f1;
		border-radius: 6rpx;
	}

	.table-body::-webkit-scrollbar-thumb {
		background: #c1c1c1;
		border-radius: 6rpx;
	}

	.table-body::-webkit-scrollbar-thumb:hover {
		background: #a8a8a8;
	}
</style>