|
|
@@ -0,0 +1,1755 @@
|
|
|
+if (typeof Promise !== "undefined" && !Promise.prototype.finally) {
|
|
|
+ Promise.prototype.finally = function(callback) {
|
|
|
+ const promise = this.constructor;
|
|
|
+ return this.then(
|
|
|
+ (value) => promise.resolve(callback()).then(() => value),
|
|
|
+ (reason) => promise.resolve(callback()).then(() => {
|
|
|
+ throw reason;
|
|
|
+ })
|
|
|
+ );
|
|
|
+ };
|
|
|
+}
|
|
|
+;
|
|
|
+if (typeof uni !== "undefined" && uni && uni.requireGlobal) {
|
|
|
+ const global = uni.requireGlobal();
|
|
|
+ ArrayBuffer = global.ArrayBuffer;
|
|
|
+ Int8Array = global.Int8Array;
|
|
|
+ Uint8Array = global.Uint8Array;
|
|
|
+ Uint8ClampedArray = global.Uint8ClampedArray;
|
|
|
+ Int16Array = global.Int16Array;
|
|
|
+ Uint16Array = global.Uint16Array;
|
|
|
+ Int32Array = global.Int32Array;
|
|
|
+ Uint32Array = global.Uint32Array;
|
|
|
+ Float32Array = global.Float32Array;
|
|
|
+ Float64Array = global.Float64Array;
|
|
|
+ BigInt64Array = global.BigInt64Array;
|
|
|
+ BigUint64Array = global.BigUint64Array;
|
|
|
+}
|
|
|
+;
|
|
|
+if (uni.restoreGlobal) {
|
|
|
+ uni.restoreGlobal(Vue, weex, plus, setTimeout, clearTimeout, setInterval, clearInterval);
|
|
|
+}
|
|
|
+(function(vue) {
|
|
|
+ "use strict";
|
|
|
+ function requireNativePlugin(name) {
|
|
|
+ return weex.requireModule(name);
|
|
|
+ }
|
|
|
+ function formatAppLog(type, filename, ...args) {
|
|
|
+ if (uni.__log__) {
|
|
|
+ uni.__log__(type, filename, ...args);
|
|
|
+ } else {
|
|
|
+ console[type].apply(console, [...args, filename]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const _imports_0 = "/static/logo.png";
|
|
|
+ const _imports_1 = "/static/horn.png";
|
|
|
+ const _export_sfc = (sfc, props) => {
|
|
|
+ const target = sfc.__vccOpts || sfc;
|
|
|
+ for (const [key, val] of props) {
|
|
|
+ target[key] = val;
|
|
|
+ }
|
|
|
+ return target;
|
|
|
+ };
|
|
|
+ const _sfc_main$3 = {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ currentTime: "",
|
|
|
+ // 当前时间
|
|
|
+ hhmmss: "",
|
|
|
+ // 当前时间
|
|
|
+ whatDay: "",
|
|
|
+ //星期几
|
|
|
+ timeTimer: null,
|
|
|
+ // 时间更新定时器
|
|
|
+ // WebSocket 实例
|
|
|
+ ws: null,
|
|
|
+ reconnectTimer: null,
|
|
|
+ heartbeatTimer: null,
|
|
|
+ // 心跳定时器
|
|
|
+ heartbeatTimeout: null,
|
|
|
+ // 心跳响应超时计时器
|
|
|
+ heartbeatInterval: 3e4,
|
|
|
+ // 30秒发一次心跳
|
|
|
+ heartbeatTimeoutTime: 5e3,
|
|
|
+ // 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: 6e3,
|
|
|
+ // 窗口高度
|
|
|
+ windowHeight: 0,
|
|
|
+ // 触摸相关
|
|
|
+ touchStartX: 0,
|
|
|
+ touchStartTime: 0,
|
|
|
+ isTouching: false,
|
|
|
+ touchStartY: 0,
|
|
|
+ // 垂直滑动相关
|
|
|
+ lastSwipeTime: 0,
|
|
|
+ linkShow: false,
|
|
|
+ ipArray: [],
|
|
|
+ keyboardHeight: 0,
|
|
|
+ now: /* @__PURE__ */ 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 minutes = Math.floor(timeDiff % (3600 * 1e3) / (60 * 1e3));
|
|
|
+ const seconds = Math.floor(timeDiff % (60 * 1e3) / 1e3);
|
|
|
+ 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 = /* @__PURE__ */ new Date();
|
|
|
+ }, 1e3);
|
|
|
+ this.getIpAddress();
|
|
|
+ this.initPage();
|
|
|
+ const lastIp = uni.getStorageSync("serverIp");
|
|
|
+ const lastPort = uni.getStorageSync("serverPort");
|
|
|
+ if (lastIp && lastPort) {
|
|
|
+ this.serverIp = lastIp;
|
|
|
+ this.serverPort = lastPort;
|
|
|
+ }
|
|
|
+ this.updateCurrentTime();
|
|
|
+ this.timeTimer = setInterval(() => {
|
|
|
+ this.updateCurrentTime();
|
|
|
+ }, 1e3);
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+ if (this.timer) {
|
|
|
+ this.timer = null;
|
|
|
+ clearInterval(this.timer);
|
|
|
+ }
|
|
|
+ this.stopAutoScroll();
|
|
|
+ uni.offWindowResize(this.handleWindowResize);
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ const ip = ipList[index].trim();
|
|
|
+ const port = this.serverPort;
|
|
|
+ const url = `ws://${ip}:${port}/`;
|
|
|
+ if (this.ws) {
|
|
|
+ this.ws.close();
|
|
|
+ this.ws = null;
|
|
|
+ }
|
|
|
+ this.ws = uni.connectSocket({
|
|
|
+ url,
|
|
|
+ success: (res) => {
|
|
|
+ formatAppLog("log", "at pages/index/home.vue:299", "connectSocket success", res);
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ formatAppLog("error", "at pages/index/home.vue:302", "connectSocket 失败", err);
|
|
|
+ this.connectionStatus = "disconnected";
|
|
|
+ if (this.serverIp == ip) {
|
|
|
+ this.heartbeatRate = 0;
|
|
|
+ this.reconnectionNum++;
|
|
|
+ }
|
|
|
+ this.reconnectionNum++;
|
|
|
+ this.isSend = true;
|
|
|
+ this.heartbeatRate = 0;
|
|
|
+ this.findFirstWebSocketIp(ipList, index + 1, onSuccess, onAllFailed);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.ws.onOpen((res) => {
|
|
|
+ this.serverIp = ip;
|
|
|
+ uni.setStorageSync("serverIp", this.serverIp);
|
|
|
+ uni.setStorageSync("serverPort", this.serverPort);
|
|
|
+ this.connectionStatus = "connected";
|
|
|
+ this.ws.send({
|
|
|
+ data: "link"
|
|
|
+ });
|
|
|
+ onSuccess(ip);
|
|
|
+ this.startHeartbeat();
|
|
|
+ uni.hideLoading();
|
|
|
+ });
|
|
|
+ this.ws.onMessage((res) => {
|
|
|
+ if (res.data === "PONG" || res.data === '{"type":"PONG"}') {
|
|
|
+ this.isSend = true;
|
|
|
+ this.heartbeatRate = 0;
|
|
|
+ this.isPongReceived = true;
|
|
|
+ if (this.heartbeatTimeout) {
|
|
|
+ clearTimeout(this.heartbeatTimeout);
|
|
|
+ this.heartbeatTimeout = null;
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const data = JSON.parse(res.data);
|
|
|
+ this.handleMessage(data);
|
|
|
+ } catch (e) {
|
|
|
+ formatAppLog("warn", "at pages/index/home.vue:354", "非 JSON 消息,已忽略", res.data);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.ws.onClose((res) => {
|
|
|
+ formatAppLog("log", "at pages/index/home.vue:358", "WebSocket 连接关闭", res);
|
|
|
+ this.connectionStatus = "disconnected";
|
|
|
+ this.stopHeartbeat();
|
|
|
+ var reconnectionTime = setTimeout(() => {
|
|
|
+ clearTimeout(reconnectionTime);
|
|
|
+ if (this.serverIp == ip)
|
|
|
+ ;
|
|
|
+ this.isSend = true;
|
|
|
+ this.heartbeatRate = 0;
|
|
|
+ this.reconnectionNum++;
|
|
|
+ this.findFirstWebSocketIp(ipList, index + 1, onSuccess, onAllFailed);
|
|
|
+ }, 2e3);
|
|
|
+ });
|
|
|
+ this.ws.onError((err) => {
|
|
|
+ formatAppLog("error", "at pages/index/home.vue:380", "WebSocket 错误", err);
|
|
|
+ this.connectionStatus = "disconnected";
|
|
|
+ this.isSend = true;
|
|
|
+ this.heartbeatRate = 0;
|
|
|
+ this.reconnectionNum++;
|
|
|
+ this.findFirstWebSocketIp(ipList, index + 1, onSuccess, onAllFailed);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 连接设置
|
|
|
+ linkSet() {
|
|
|
+ this.linkShow = true;
|
|
|
+ },
|
|
|
+ // 关闭弹窗
|
|
|
+ getClose() {
|
|
|
+ this.linkShow = false;
|
|
|
+ },
|
|
|
+ // 获取ip地址
|
|
|
+ getIpAddress() {
|
|
|
+ var deviceFinder = requireNativePlugin("Alikes-NetTools-DeviceFinder");
|
|
|
+ deviceFinder.scan({}, (res) => {
|
|
|
+ this.ipArray = res;
|
|
|
+ this.ipArray.unshift(this.serverIp);
|
|
|
+ this.startCheck();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 启动检查
|
|
|
+ startCheck() {
|
|
|
+ var ipArray = [];
|
|
|
+ ipArray = this.ipArray;
|
|
|
+ this.findFirstWebSocketIp(
|
|
|
+ ipArray,
|
|
|
+ 0,
|
|
|
+ (ip) => {
|
|
|
+ this.onDeviceFound(ip);
|
|
|
+ this.linkShow = false;
|
|
|
+ },
|
|
|
+ () => {
|
|
|
+ this.onNoDevice();
|
|
|
+ }
|
|
|
+ );
|
|
|
+ },
|
|
|
+ // 找到设备后的逻辑
|
|
|
+ onDeviceFound(ip) {
|
|
|
+ uni.showToast({
|
|
|
+ title: "连接成功",
|
|
|
+ icon: "success"
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 无设备可用
|
|
|
+ onNoDevice() {
|
|
|
+ uni.showToast({
|
|
|
+ title: "无设备响应",
|
|
|
+ icon: "none",
|
|
|
+ duration: 3e3
|
|
|
+ });
|
|
|
+ this.getIpAddress();
|
|
|
+ },
|
|
|
+ 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);
|
|
|
+ const availableHeight = this.windowHeight - headerHeight - paginationHeight - tableHeaderHeight - specialDataHeight - padding;
|
|
|
+ const rowHeightPx = uni.upx2px(140);
|
|
|
+ const calculatedPageSize = Math.floor(availableHeight / rowHeightPx);
|
|
|
+ this.pageSize = Math.max(4, Math.min(30, calculatedPageSize));
|
|
|
+ this.validateCurrentPage();
|
|
|
+ } catch (error) {
|
|
|
+ formatAppLog("error", "at pages/index/home.vue:515", "计算页面大小失败:", 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: () => {
|
|
|
+ this.getIpAddress();
|
|
|
+ },
|
|
|
+ fail: () => {
|
|
|
+ this.getIpAddress();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this.getIpAddress();
|
|
|
+ }
|
|
|
+ setTimeout(() => {
|
|
|
+ uni.hideLoading();
|
|
|
+ }, 2e3);
|
|
|
+ },
|
|
|
+ // 处理收到的消息
|
|
|
+ handleMessage(data) {
|
|
|
+ if (data.action == "link") {
|
|
|
+ this.updateListWithArray(data.data);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (typeof data === "object" && data !== null) {
|
|
|
+ const action = data.action;
|
|
|
+ if (!action) {
|
|
|
+ formatAppLog("warn", "at pages/index/home.vue:603", "消息缺少 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:
|
|
|
+ formatAppLog("warn", "at pages/index/home.vue:619", "未知操作类型:", action);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ formatAppLog("warn", "at pages/index/home.vue:622", "收到未知格式消息:", data);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 批量新增
|
|
|
+ batchAdd(data) {
|
|
|
+ if (!data)
|
|
|
+ return;
|
|
|
+ const items = Array.isArray(data) ? data : [data];
|
|
|
+ const validItems = items.filter((item) => item && item.id !== void 0);
|
|
|
+ if (validItems.length === 0) {
|
|
|
+ formatAppLog("warn", "at pages/index/home.vue:631", "没有有效数据用于新增", 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 === void 0)
|
|
|
+ return;
|
|
|
+ const index = updated.findIndex((i) => i.id == item.id);
|
|
|
+ if (index > -1) {
|
|
|
+ updated[index] = {
|
|
|
+ ...updated[index],
|
|
|
+ ...item
|
|
|
+ };
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.allData = updated;
|
|
|
+ },
|
|
|
+ // 批量删除
|
|
|
+ batchRemove(data) {
|
|
|
+ let idsToRemove = [];
|
|
|
+ if (Array.isArray(data.id)) {
|
|
|
+ idsToRemove = data.id;
|
|
|
+ } else if (data.id !== void 0) {
|
|
|
+ idsToRemove = [data.id];
|
|
|
+ } else if (Array.isArray(data.data)) {
|
|
|
+ idsToRemove = data.data.map((item) => item.id).filter((id) => id !== void 0);
|
|
|
+ } else {
|
|
|
+ idsToRemove = [data.id];
|
|
|
+ formatAppLog("warn", "at pages/index/home.vue:682", "无法解析删除指令", 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 = /* @__PURE__ */ new Map();
|
|
|
+ newList.forEach((item) => {
|
|
|
+ if (item.id !== void 0) {
|
|
|
+ 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(() => {
|
|
|
+ this.connectWebSocket();
|
|
|
+ this.reconnectTimer = null;
|
|
|
+ }, 3e3);
|
|
|
+ },
|
|
|
+ // 启动心跳
|
|
|
+ startHeartbeat() {
|
|
|
+ var that = this;
|
|
|
+ that.stopHeartbeat();
|
|
|
+ that.heartbeatTimer = setInterval(() => {
|
|
|
+ if (that.ws && that.connectionStatus === "connected") {
|
|
|
+ if (!that.isSend && !that.isPongReceived) {
|
|
|
+ that.heartbeatRate++;
|
|
|
+ formatAppLog("warn", "at pages/index/home.vue:731", that.heartbeatRate, "上一次心跳未收到 pong,可能已断线新");
|
|
|
+ if (that.heartbeatRate >= 3) {
|
|
|
+ that.ws.close();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ that.isPongReceived = false;
|
|
|
+ that.heartbeatTimeout = setTimeout(() => {
|
|
|
+ if (that.isSend && !that.isPongReceived) {
|
|
|
+ formatAppLog("warn", "at pages/index/home.vue:742", "⚠️ 心跳超时:未在规定时间内收到 pong,即将重连");
|
|
|
+ uni.showToast({
|
|
|
+ title: "连接异常,正在重连...",
|
|
|
+ icon: "none",
|
|
|
+ duration: 2e3
|
|
|
+ });
|
|
|
+ that.ws.close();
|
|
|
+ }
|
|
|
+ }, that.heartbeatTimeoutTime);
|
|
|
+ if (that.isSend) {
|
|
|
+ that.ws.send({
|
|
|
+ data: "PING",
|
|
|
+ success: () => {
|
|
|
+ that.isSend = false;
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ formatAppLog("error", "at pages/index/home.vue:759", "ping 发送失败", err);
|
|
|
+ that.ws.close();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, that.heartbeatInterval);
|
|
|
+ },
|
|
|
+ // 停止心跳(断开连接时调用)
|
|
|
+ stopHeartbeat() {
|
|
|
+ if (this.heartbeatTimer) {
|
|
|
+ clearInterval(this.heartbeatTimer);
|
|
|
+ this.heartbeatTimer = null;
|
|
|
+ }
|
|
|
+ if (this.heartbeatTimeout) {
|
|
|
+ clearTimeout(this.heartbeatTimeout);
|
|
|
+ this.heartbeatTimeout = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 格式化时间
|
|
|
+ formatTime(dateTimeStr) {
|
|
|
+ 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平台)
|
|
|
+ // 切换自动滚动
|
|
|
+ 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 = /* @__PURE__ */ 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 >= 3) {
|
|
|
+ plus.runtime.restart();
|
|
|
+ setTimeout(() => {
|
|
|
+ plus.navigator.closeSplashscreen();
|
|
|
+ }, 3e3);
|
|
|
+ this.reconnectionNum = 0;
|
|
|
+ }
|
|
|
+ formatAppLog("log", "at pages/index/home.vue:988", "对象属性变化", newVal, oldVal);
|
|
|
+ },
|
|
|
+ deep: true,
|
|
|
+ immediate: true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
+ return vue.openBlock(), vue.createElementBlock("view", { class: "notice-board" }, [
|
|
|
+ vue.createElementVNode("view", { class: "board-header" }, [
|
|
|
+ vue.createElementVNode("view", {
|
|
|
+ class: "card_logo",
|
|
|
+ onClick: _cache[0] || (_cache[0] = (...args) => $options.linkSet && $options.linkSet(...args))
|
|
|
+ }, [
|
|
|
+ vue.createElementVNode("image", {
|
|
|
+ class: "logo_image",
|
|
|
+ src: _imports_0,
|
|
|
+ mode: ""
|
|
|
+ }),
|
|
|
+ vue.createElementVNode("view", { class: "logo_title" }, "观山湖区疾控中心")
|
|
|
+ ]),
|
|
|
+ vue.createElementVNode("view", { class: "card_yellow" }, [
|
|
|
+ vue.createElementVNode("view", { class: "card_board" }, [
|
|
|
+ vue.createElementVNode("text", { class: "board-title" }, "接种留观等待")
|
|
|
+ ])
|
|
|
+ ]),
|
|
|
+ vue.createElementVNode("view", { class: "card_time" }, [
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ { class: "time_title" },
|
|
|
+ vue.toDisplayString($data.hhmmss),
|
|
|
+ 1
|
|
|
+ /* TEXT */
|
|
|
+ ),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ { class: "current-time" },
|
|
|
+ vue.toDisplayString($data.currentTime) + " " + vue.toDisplayString($data.whatDay),
|
|
|
+ 1
|
|
|
+ /* TEXT */
|
|
|
+ )
|
|
|
+ ])
|
|
|
+ ]),
|
|
|
+ vue.createCommentVNode(" 表格容器 "),
|
|
|
+ vue.createElementVNode("view", { class: "table-container" }, [
|
|
|
+ vue.createCommentVNode(" 主要表头 "),
|
|
|
+ vue.createElementVNode("view", { class: "table-header" }, [
|
|
|
+ vue.createElementVNode("view", { class: "table-row header-row" }, [
|
|
|
+ vue.createElementVNode("view", { class: "cell name title_color" }, "姓名"),
|
|
|
+ vue.createElementVNode("view", { class: "cell time title_color" }, "留观时间"),
|
|
|
+ vue.createElementVNode("view", { class: "cell time title_color" }, "离开时间"),
|
|
|
+ vue.createElementVNode("view", { class: "cell status title_color" }, "状态")
|
|
|
+ ])
|
|
|
+ ]),
|
|
|
+ vue.createCommentVNode(" 特殊状态数据展示区域(固定) "),
|
|
|
+ $options.specialStatusData.length > 0 ? (vue.openBlock(), vue.createElementBlock("view", {
|
|
|
+ key: 0,
|
|
|
+ class: "special-status-container"
|
|
|
+ }, [
|
|
|
+ (vue.openBlock(true), vue.createElementBlock(
|
|
|
+ vue.Fragment,
|
|
|
+ null,
|
|
|
+ vue.renderList($options.specialStatusData, (item, index) => {
|
|
|
+ return vue.openBlock(), vue.createElementBlock(
|
|
|
+ "view",
|
|
|
+ {
|
|
|
+ class: vue.normalizeClass(["table-row", `item-${$options.getClass(item.status)}`]),
|
|
|
+ key: item.id
|
|
|
+ },
|
|
|
+ [
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ {
|
|
|
+ class: vue.normalizeClass(["table-cell name", `title-${$options.getClass(item.status)}`])
|
|
|
+ },
|
|
|
+ vue.toDisplayString(item.patientName),
|
|
|
+ 3
|
|
|
+ /* TEXT, CLASS */
|
|
|
+ ),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ { class: "table-cell time" },
|
|
|
+ vue.toDisplayString($options.formatTime(item.createTime)),
|
|
|
+ 1
|
|
|
+ /* TEXT */
|
|
|
+ ),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ { class: "table-cell time" },
|
|
|
+ vue.toDisplayString($options.formatTime(item.outTime)),
|
|
|
+ 1
|
|
|
+ /* TEXT */
|
|
|
+ ),
|
|
|
+ vue.createElementVNode("view", { class: "table-cell status" }, [
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ {
|
|
|
+ class: vue.normalizeClass(["status-tag", `status-${$options.getClass(item.status)}`])
|
|
|
+ },
|
|
|
+ vue.toDisplayString($options.getStatusText(item)),
|
|
|
+ 3
|
|
|
+ /* TEXT, CLASS */
|
|
|
+ )
|
|
|
+ ])
|
|
|
+ ],
|
|
|
+ 2
|
|
|
+ /* CLASS */
|
|
|
+ );
|
|
|
+ }),
|
|
|
+ 128
|
|
|
+ /* KEYED_FRAGMENT */
|
|
|
+ )),
|
|
|
+ vue.createCommentVNode(" 分隔线 "),
|
|
|
+ vue.createElementVNode("view", { class: "divider" })
|
|
|
+ ])) : vue.createCommentVNode("v-if", true),
|
|
|
+ vue.createCommentVNode(" 普通数据内容区域 "),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ {
|
|
|
+ class: "table-body",
|
|
|
+ onTouchstart: _cache[1] || (_cache[1] = (...args) => $options.handleTouchStart && $options.handleTouchStart(...args)),
|
|
|
+ onTouchmove: _cache[2] || (_cache[2] = (...args) => $options.handleTouchMove && $options.handleTouchMove(...args)),
|
|
|
+ onTouchend: _cache[3] || (_cache[3] = (...args) => $options.handleTouchEnd && $options.handleTouchEnd(...args))
|
|
|
+ },
|
|
|
+ [
|
|
|
+ (vue.openBlock(true), vue.createElementBlock(
|
|
|
+ vue.Fragment,
|
|
|
+ null,
|
|
|
+ vue.renderList($options.displayNormalData, (item, index) => {
|
|
|
+ return vue.openBlock(), vue.createElementBlock(
|
|
|
+ "view",
|
|
|
+ {
|
|
|
+ class: vue.normalizeClass(["table-row", `item-${$options.getClass(item.status)}`]),
|
|
|
+ key: item.id
|
|
|
+ },
|
|
|
+ [
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ {
|
|
|
+ class: vue.normalizeClass(["table-cell name", `title-${$options.getClass(item.status)}`])
|
|
|
+ },
|
|
|
+ vue.toDisplayString(item.patientName),
|
|
|
+ 3
|
|
|
+ /* TEXT, CLASS */
|
|
|
+ ),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ { class: "table-cell time" },
|
|
|
+ vue.toDisplayString($options.formatTime(item.createTime)),
|
|
|
+ 1
|
|
|
+ /* TEXT */
|
|
|
+ ),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ { class: "table-cell time" },
|
|
|
+ vue.toDisplayString($options.formatTime(item.outTime)),
|
|
|
+ 1
|
|
|
+ /* TEXT */
|
|
|
+ ),
|
|
|
+ vue.createElementVNode("view", { class: "table-cell status" }, [
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ {
|
|
|
+ class: vue.normalizeClass(["status-tag", `status-${$options.getClass(item.status)}`])
|
|
|
+ },
|
|
|
+ vue.toDisplayString($options.getStatusText(item)),
|
|
|
+ 3
|
|
|
+ /* TEXT, CLASS */
|
|
|
+ )
|
|
|
+ ])
|
|
|
+ ],
|
|
|
+ 2
|
|
|
+ /* CLASS */
|
|
|
+ );
|
|
|
+ }),
|
|
|
+ 128
|
|
|
+ /* KEYED_FRAGMENT */
|
|
|
+ )),
|
|
|
+ vue.createCommentVNode(" 空数据提示 "),
|
|
|
+ $options.displayNormalData.length === 0 && $options.specialStatusData.length === 0 ? (vue.openBlock(), vue.createElementBlock("view", {
|
|
|
+ key: 0,
|
|
|
+ class: "empty-tip"
|
|
|
+ }, " 暂无留观人员信息 ")) : vue.createCommentVNode("v-if", true)
|
|
|
+ ],
|
|
|
+ 32
|
|
|
+ /* NEED_HYDRATION */
|
|
|
+ )
|
|
|
+ ]),
|
|
|
+ vue.createElementVNode("view", { class: "card_foot" }, [
|
|
|
+ vue.createElementVNode("view", { class: "card_tips_box" }, [
|
|
|
+ vue.createElementVNode("image", {
|
|
|
+ class: "tips_imageil",
|
|
|
+ src: _imports_1,
|
|
|
+ mode: ""
|
|
|
+ }),
|
|
|
+ vue.createElementVNode("view", { class: "title_tips" }, "温馨提示:")
|
|
|
+ ]),
|
|
|
+ vue.createElementVNode("view", { class: "title_tips_foot" }, "请注意留观30分钟后无不良反应后再离开,谢谢。")
|
|
|
+ ]),
|
|
|
+ vue.createCommentVNode(" 连接状态与IP编辑栏 "),
|
|
|
+ $data.linkShow ? (vue.openBlock(), vue.createElementBlock("view", {
|
|
|
+ key: 0,
|
|
|
+ class: "box_link_set"
|
|
|
+ }, [
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ {
|
|
|
+ class: "box_popup",
|
|
|
+ style: vue.normalizeStyle({ bottom: $data.keyboardHeight + "px" })
|
|
|
+ },
|
|
|
+ [
|
|
|
+ vue.createElementVNode("view", { class: "head_popup_title" }, [
|
|
|
+ vue.createElementVNode("view", { class: "title_head_popup" }, "连接设置"),
|
|
|
+ vue.createElementVNode("view", {
|
|
|
+ class: "close_title",
|
|
|
+ onClick: _cache[4] || (_cache[4] = (...args) => $options.getClose && $options.getClose(...args))
|
|
|
+ }, "×")
|
|
|
+ ]),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ {
|
|
|
+ class: vue.normalizeClass(["status-bar-bottom", `status-${$data.connectionStatus}`])
|
|
|
+ },
|
|
|
+ [
|
|
|
+ vue.createCommentVNode(" IP 编辑区域 "),
|
|
|
+ vue.createElementVNode("view", { class: "ip-input-group" }, [
|
|
|
+ vue.createElementVNode("text", { class: "ip-label" }, "IP:"),
|
|
|
+ vue.createElementVNode("input", {
|
|
|
+ type: "text",
|
|
|
+ value: $data.serverIp,
|
|
|
+ onInput: _cache[5] || (_cache[5] = (...args) => $options.onIpInput && $options.onIpInput(...args)),
|
|
|
+ placeholder: "192.168.0.41",
|
|
|
+ class: "ip-input",
|
|
|
+ onKeyboardheightchange: _cache[6] || (_cache[6] = (...args) => $options.keyboardheightchange && $options.keyboardheightchange(...args))
|
|
|
+ }, null, 40, ["value"]),
|
|
|
+ vue.createElementVNode("text", { class: "colon" }, ":"),
|
|
|
+ vue.createElementVNode("input", {
|
|
|
+ type: "number",
|
|
|
+ value: $data.serverPort,
|
|
|
+ onInput: _cache[7] || (_cache[7] = (...args) => $options.onPortInput && $options.onPortInput(...args)),
|
|
|
+ placeholder: "8811",
|
|
|
+ class: "port-input",
|
|
|
+ onKeyboardheightchange: _cache[8] || (_cache[8] = (...args) => $options.keyboardheightchange && $options.keyboardheightchange(...args))
|
|
|
+ }, null, 40, ["value"]),
|
|
|
+ vue.createElementVNode("button", {
|
|
|
+ class: "btn-reconnect",
|
|
|
+ size: "mini",
|
|
|
+ onClick: _cache[9] || (_cache[9] = (...args) => $options.handleReconnect && $options.handleReconnect(...args))
|
|
|
+ }, " 更新并重连 ")
|
|
|
+ ]),
|
|
|
+ vue.createElementVNode("view", { class: "status-text-box" }, [
|
|
|
+ vue.createElementVNode(
|
|
|
+ "text",
|
|
|
+ {
|
|
|
+ class: vue.normalizeClass(["dot", `dot-${$data.connectionStatus}`])
|
|
|
+ },
|
|
|
+ "●",
|
|
|
+ 2
|
|
|
+ /* CLASS */
|
|
|
+ ),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "text",
|
|
|
+ { class: "status-text" },
|
|
|
+ vue.toDisplayString($options.connectionText),
|
|
|
+ 1
|
|
|
+ /* TEXT */
|
|
|
+ )
|
|
|
+ ])
|
|
|
+ ],
|
|
|
+ 2
|
|
|
+ /* CLASS */
|
|
|
+ )
|
|
|
+ ],
|
|
|
+ 4
|
|
|
+ /* STYLE */
|
|
|
+ )
|
|
|
+ ])) : vue.createCommentVNode("v-if", true)
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ const PagesIndexHome = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["render", _sfc_render$2], ["__scopeId", "data-v-760d994e"], ["__file", "D:/code/baozhida-observation-system/pages/index/home.vue"]]);
|
|
|
+ const _sfc_main$2 = {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ currentTime: "",
|
|
|
+ // 当前时间
|
|
|
+ timeTimer: null,
|
|
|
+ // 时间更新定时器
|
|
|
+ // WebSocket 实例
|
|
|
+ ws: null,
|
|
|
+ reconnectTimer: null,
|
|
|
+ heartbeatTimer: null,
|
|
|
+ // 心跳定时器
|
|
|
+ heartbeatTimeout: null,
|
|
|
+ // 心跳响应超时计时器
|
|
|
+ heartbeatInterval: 3e4,
|
|
|
+ // 30秒发一次心跳
|
|
|
+ heartbeatTimeoutTime: 1e4,
|
|
|
+ // 10秒内未响应视为超时
|
|
|
+ isPongReceived: true,
|
|
|
+ // 标记是否收到 pong
|
|
|
+ // 连接状态
|
|
|
+ connectionStatus: "connecting",
|
|
|
+ // connecting, connected, disconnected
|
|
|
+ // 留观数据列表
|
|
|
+ list: [],
|
|
|
+ statusText: {
|
|
|
+ observing: "留观中",
|
|
|
+ completed: "留观完成,可离开",
|
|
|
+ warning: "提前离开",
|
|
|
+ hasleft: "已离开"
|
|
|
+ },
|
|
|
+ // 可编辑的服务器地址
|
|
|
+ serverIp: "192.168.11.132",
|
|
|
+ serverPort: "8811"
|
|
|
+ };
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ connectionText() {
|
|
|
+ const {
|
|
|
+ connectionStatus,
|
|
|
+ serverIp,
|
|
|
+ serverPort
|
|
|
+ } = this;
|
|
|
+ return {
|
|
|
+ connecting: `正在连接 ${serverIp}:${serverPort}...`,
|
|
|
+ connected: `已连接到 ${serverIp}:${serverPort}`,
|
|
|
+ disconnected: `连接已断开`
|
|
|
+ }[connectionStatus];
|
|
|
+ },
|
|
|
+ // 轮播列表数据
|
|
|
+ carouselList() {
|
|
|
+ if (this.list.length === 0)
|
|
|
+ return [];
|
|
|
+ if (this.list.length <= 5)
|
|
|
+ return this.list;
|
|
|
+ return [...this.list, ...this.list];
|
|
|
+ },
|
|
|
+ // 是否需要启动轮播动画
|
|
|
+ shouldAnimate() {
|
|
|
+ return this.list.length > 5;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ 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 = "completed";
|
|
|
+ }
|
|
|
+ return title;
|
|
|
+ },
|
|
|
+ getStatusText(status) {
|
|
|
+ var title = "";
|
|
|
+ if (status == 0) {
|
|
|
+ title = this.statusText.observing;
|
|
|
+ } else if (status == 1) {
|
|
|
+ title = this.statusText.completed;
|
|
|
+ } else if (status == 3) {
|
|
|
+ title = this.statusText.warning;
|
|
|
+ } else if (status == 4) {
|
|
|
+ title = this.statusText.hasleft;
|
|
|
+ }
|
|
|
+ return title;
|
|
|
+ },
|
|
|
+ // 格式时分
|
|
|
+ getTime(dateTimeStr) {
|
|
|
+ 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;
|
|
|
+ },
|
|
|
+ // 格式化时间范围:09:00 - 09:30
|
|
|
+ formatTimeRange(start, end) {
|
|
|
+ return start && end ? `${start} - ${end}` : "--";
|
|
|
+ },
|
|
|
+ // 输入框事件
|
|
|
+ onIpInput(e) {
|
|
|
+ this.serverIp = e.detail.value;
|
|
|
+ },
|
|
|
+ onPortInput(e) {
|
|
|
+ this.serverPort = e.detail.value;
|
|
|
+ },
|
|
|
+ // 构建 WebSocket URL
|
|
|
+ getWebSocketUrl() {
|
|
|
+ return `ws://${this.serverIp}:${this.serverPort}`;
|
|
|
+ },
|
|
|
+ // 处理重连
|
|
|
+ handleReconnect() {
|
|
|
+ uni.showLoading({
|
|
|
+ title: "正在重连..."
|
|
|
+ });
|
|
|
+ this.connectionStatus = "connecting";
|
|
|
+ if (this.ws) {
|
|
|
+ this.ws.close({
|
|
|
+ success: () => {
|
|
|
+ formatAppLog("log", "at pages/index/index.vue:180", "旧连接已关闭");
|
|
|
+ this.connectWebSocket("link");
|
|
|
+ },
|
|
|
+ fail: () => {
|
|
|
+ this.connectWebSocket("link");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ this.connectWebSocket("link");
|
|
|
+ }
|
|
|
+ setTimeout(() => {
|
|
|
+ uni.hideLoading();
|
|
|
+ }, 2e3);
|
|
|
+ },
|
|
|
+ // 建立 WebSocket 连接
|
|
|
+ connectWebSocket(type) {
|
|
|
+ const url = this.getWebSocketUrl();
|
|
|
+ this.ws = uni.connectSocket({
|
|
|
+ url,
|
|
|
+ success: (res) => {
|
|
|
+ formatAppLog("log", "at pages/index/index.vue:201", "connectSocket success", res);
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ formatAppLog("error", "at pages/index/index.vue:204", "connectSocket 失败", err);
|
|
|
+ this.connectionStatus = "disconnected";
|
|
|
+ this.reconnect();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.ws.onOpen((res) => {
|
|
|
+ this.connectionStatus = "connected";
|
|
|
+ this.ws.send({
|
|
|
+ data: type
|
|
|
+ });
|
|
|
+ this.startHeartbeat();
|
|
|
+ uni.hideLoading();
|
|
|
+ });
|
|
|
+ this.ws.onMessage((res) => {
|
|
|
+ if (res.data === "PONG" || res.data === '{"type":"PONG"}') {
|
|
|
+ this.isPongReceived = true;
|
|
|
+ if (this.heartbeatTimeout) {
|
|
|
+ clearTimeout(this.heartbeatTimeout);
|
|
|
+ this.heartbeatTimeout = null;
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const data = JSON.parse(res.data);
|
|
|
+ this.handleMessage(data);
|
|
|
+ } catch (e) {
|
|
|
+ formatAppLog("warn", "at pages/index/index.vue:237", "非 JSON 消息,已忽略", res.data);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.ws.onClose((res) => {
|
|
|
+ formatAppLog("log", "at pages/index/index.vue:241", "WebSocket 连接关闭", res);
|
|
|
+ this.connectionStatus = "disconnected";
|
|
|
+ this.stopHeartbeat();
|
|
|
+ this.reconnect();
|
|
|
+ });
|
|
|
+ this.ws.onError((err) => {
|
|
|
+ formatAppLog("error", "at pages/index/index.vue:247", "WebSocket 错误", err);
|
|
|
+ this.connectionStatus = "disconnected";
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 处理收到的消息
|
|
|
+ handleMessage(data) {
|
|
|
+ if (data.action == "link") {
|
|
|
+ this.updateListWithArray(data.data);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (typeof data === "object" && data !== null) {
|
|
|
+ const action = data.action;
|
|
|
+ if (!action) {
|
|
|
+ formatAppLog("warn", "at pages/index/index.vue:263", "消息缺少 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:
|
|
|
+ formatAppLog("warn", "at pages/index/index.vue:279", "未知操作类型:", action);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ formatAppLog("warn", "at pages/index/index.vue:282", "收到未知格式消息:", data);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 批量新增
|
|
|
+ batchAdd(data) {
|
|
|
+ if (!data)
|
|
|
+ return;
|
|
|
+ const items = Array.isArray(data) ? data : [data];
|
|
|
+ const validItems = items.filter((item) => item && item.id !== void 0);
|
|
|
+ if (validItems.length === 0) {
|
|
|
+ formatAppLog("warn", "at pages/index/index.vue:291", "没有有效数据用于新增", data);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const updated = [...this.list];
|
|
|
+ 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.list = updated;
|
|
|
+ },
|
|
|
+ // 批量修改
|
|
|
+ batchUpdate(data) {
|
|
|
+ if (!data)
|
|
|
+ return;
|
|
|
+ const items = Array.isArray(data) ? data : [data];
|
|
|
+ const updated = [...this.list];
|
|
|
+ items.forEach((item) => {
|
|
|
+ if (!item || item.id === void 0)
|
|
|
+ return;
|
|
|
+ const index = updated.findIndex((i) => i.id == item.id);
|
|
|
+ if (index > -1) {
|
|
|
+ updated[index] = {
|
|
|
+ ...updated[index],
|
|
|
+ ...item
|
|
|
+ };
|
|
|
+ }
|
|
|
+ });
|
|
|
+ this.list = updated;
|
|
|
+ },
|
|
|
+ // 批量删除
|
|
|
+ batchRemove(data) {
|
|
|
+ let idsToRemove = [];
|
|
|
+ if (Array.isArray(data.id)) {
|
|
|
+ idsToRemove = data.id;
|
|
|
+ } else if (data.id !== void 0) {
|
|
|
+ idsToRemove = [data.id];
|
|
|
+ } else if (Array.isArray(data.data)) {
|
|
|
+ idsToRemove = data.data.map((item) => item.id).filter((id) => id !== void 0);
|
|
|
+ } else {
|
|
|
+ idsToRemove = [data.id];
|
|
|
+ formatAppLog("warn", "at pages/index/index.vue:342", "无法解析删除指令", data);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (idsToRemove.length === 0)
|
|
|
+ return;
|
|
|
+ const updated = this.list.filter((item) => !idsToRemove.includes(item.id));
|
|
|
+ this.list = updated;
|
|
|
+ },
|
|
|
+ updateListWithArray(newList) {
|
|
|
+ if (!Array.isArray(newList))
|
|
|
+ return;
|
|
|
+ const map = /* @__PURE__ */ new Map();
|
|
|
+ newList.forEach((item) => {
|
|
|
+ if (item.id !== void 0) {
|
|
|
+ map.set(item.id, item);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ const updated = [...this.list];
|
|
|
+ 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.list = final;
|
|
|
+ },
|
|
|
+ // 断线重连(指数退避)
|
|
|
+ reconnect() {
|
|
|
+ if (this.reconnectTimer)
|
|
|
+ return;
|
|
|
+ this.reconnectTimer = setTimeout(() => {
|
|
|
+ formatAppLog("log", "at pages/index/index.vue:377", "正在尝试重新连接...");
|
|
|
+ this.connectWebSocket("link");
|
|
|
+ this.reconnectTimer = null;
|
|
|
+ }, 3e3);
|
|
|
+ },
|
|
|
+ // 启动心跳
|
|
|
+ startHeartbeat() {
|
|
|
+ formatAppLog("log", "at pages/index/index.vue:384", 2324);
|
|
|
+ this.stopHeartbeat();
|
|
|
+ this.heartbeatTimer = setInterval(() => {
|
|
|
+ if (this.ws && this.connectionStatus === "connected") {
|
|
|
+ if (!this.isPongReceived) {
|
|
|
+ formatAppLog("warn", "at pages/index/index.vue:392", "上一次心跳未收到 pong,可能已断线");
|
|
|
+ this.ws.close();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.isPongReceived = false;
|
|
|
+ this.heartbeatTimeout = setTimeout(() => {
|
|
|
+ if (!this.isPongReceived) {
|
|
|
+ formatAppLog("warn", "at pages/index/index.vue:401", "⚠️ 心跳超时:未在规定时间内收到 pong,即将重连");
|
|
|
+ uni.showToast({
|
|
|
+ title: "连接异常,正在重连...",
|
|
|
+ icon: "none",
|
|
|
+ duration: 2e3
|
|
|
+ });
|
|
|
+ this.ws.close();
|
|
|
+ }
|
|
|
+ }, this.heartbeatTimeoutTime);
|
|
|
+ try {
|
|
|
+ this.ws.send({
|
|
|
+ data: "PING",
|
|
|
+ success: () => {
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ formatAppLog("error", "at pages/index/index.vue:417", "ping 发送失败", err);
|
|
|
+ this.ws.close();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } catch (e) {
|
|
|
+ formatAppLog("error", "at pages/index/index.vue:422", "发送 ping 异常", e);
|
|
|
+ this.ws.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, this.heartbeatInterval);
|
|
|
+ },
|
|
|
+ // 停止心跳(断开连接时调用)
|
|
|
+ stopHeartbeat() {
|
|
|
+ if (this.heartbeatTimer) {
|
|
|
+ clearInterval(this.heartbeatTimer);
|
|
|
+ this.heartbeatTimer = null;
|
|
|
+ }
|
|
|
+ if (this.heartbeatTimeout) {
|
|
|
+ clearTimeout(this.heartbeatTimeout);
|
|
|
+ this.heartbeatTimeout = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ updateCurrentTime() {
|
|
|
+ const now = /* @__PURE__ */ 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} ${hours}:${minutes}:${seconds}`;
|
|
|
+ },
|
|
|
+ goback() {
|
|
|
+ uni.navigateTo({
|
|
|
+ url: "/pages/index/home"
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.connectWebSocket("link");
|
|
|
+ this.updateCurrentTime();
|
|
|
+ this.timeTimer = setInterval(() => {
|
|
|
+ this.updateCurrentTime();
|
|
|
+ }, 1e3);
|
|
|
+ },
|
|
|
+ // 页面卸载时关闭连接
|
|
|
+ beforeDestroy() {
|
|
|
+ if (this.ws) {
|
|
|
+ this.ws.close();
|
|
|
+ }
|
|
|
+ if (this.reconnectTimer) {
|
|
|
+ clearTimeout(this.reconnectTimer);
|
|
|
+ }
|
|
|
+ this.stopHeartbeat();
|
|
|
+ if (this.timeTimer) {
|
|
|
+ clearInterval(this.timeTimer);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
+ return vue.openBlock(), vue.createElementBlock("view", { class: "container" }, [
|
|
|
+ vue.createElementVNode("view", { class: "header" }, [
|
|
|
+ vue.createElementVNode("text", { class: "title" }, "接种留观人员信息表"),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "text",
|
|
|
+ { class: "current-time" },
|
|
|
+ vue.toDisplayString($data.currentTime),
|
|
|
+ 1
|
|
|
+ /* TEXT */
|
|
|
+ )
|
|
|
+ ]),
|
|
|
+ vue.createCommentVNode(" 表格 "),
|
|
|
+ vue.createElementVNode("view", { class: "table-container" }, [
|
|
|
+ vue.createCommentVNode(" 表头 "),
|
|
|
+ vue.createElementVNode("view", { class: "table-header" }, [
|
|
|
+ vue.createElementVNode("view", { class: "table-row header-row" }, [
|
|
|
+ vue.createElementVNode("view", { class: "cell name title_color" }, "姓名"),
|
|
|
+ vue.createElementVNode("view", { class: "cell time title_color" }, "留观时间"),
|
|
|
+ vue.createElementVNode("view", { class: "cell time title_color" }, "离开时间"),
|
|
|
+ vue.createElementVNode("view", { class: "cell status title_color" }, "状态")
|
|
|
+ ])
|
|
|
+ ]),
|
|
|
+ vue.createCommentVNode(" 表格主体 - 支持纵向轮播滚动 "),
|
|
|
+ vue.createElementVNode("view", { class: "table-body-container" }, [
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ {
|
|
|
+ class: vue.normalizeClass(["table-body-wrapper", { "animate": $options.shouldAnimate }])
|
|
|
+ },
|
|
|
+ [
|
|
|
+ (vue.openBlock(true), vue.createElementBlock(
|
|
|
+ vue.Fragment,
|
|
|
+ null,
|
|
|
+ vue.renderList($options.carouselList, (item, index) => {
|
|
|
+ return vue.openBlock(), vue.createElementBlock(
|
|
|
+ "view",
|
|
|
+ {
|
|
|
+ key: index,
|
|
|
+ class: vue.normalizeClass(["table-row body-row", `item-${$options.getClass(item.status)}`])
|
|
|
+ },
|
|
|
+ [
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ { class: "cell name" },
|
|
|
+ vue.toDisplayString(item.patientName),
|
|
|
+ 1
|
|
|
+ /* TEXT */
|
|
|
+ ),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ { class: "cell time" },
|
|
|
+ vue.toDisplayString($options.getTime(item.createTime)),
|
|
|
+ 1
|
|
|
+ /* TEXT */
|
|
|
+ ),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ { class: "cell time" },
|
|
|
+ vue.toDisplayString($options.getTime(item.outTime)),
|
|
|
+ 1
|
|
|
+ /* TEXT */
|
|
|
+ ),
|
|
|
+ vue.createElementVNode("view", { class: "cell status" }, [
|
|
|
+ vue.createElementVNode(
|
|
|
+ "text",
|
|
|
+ {
|
|
|
+ class: vue.normalizeClass(["status-tag", `status-${$options.getClass(item.status)}`])
|
|
|
+ },
|
|
|
+ vue.toDisplayString($options.getStatusText(item.status)),
|
|
|
+ 3
|
|
|
+ /* TEXT, CLASS */
|
|
|
+ )
|
|
|
+ ])
|
|
|
+ ],
|
|
|
+ 2
|
|
|
+ /* CLASS */
|
|
|
+ );
|
|
|
+ }),
|
|
|
+ 128
|
|
|
+ /* KEYED_FRAGMENT */
|
|
|
+ ))
|
|
|
+ ],
|
|
|
+ 2
|
|
|
+ /* CLASS */
|
|
|
+ )
|
|
|
+ ])
|
|
|
+ ]),
|
|
|
+ vue.createCommentVNode(" 连接状态与IP编辑栏 "),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "view",
|
|
|
+ {
|
|
|
+ class: vue.normalizeClass(["status-bar-bottom", `status-${$data.connectionStatus}`])
|
|
|
+ },
|
|
|
+ [
|
|
|
+ vue.createElementVNode("view", { class: "status-text-box" }, [
|
|
|
+ vue.createElementVNode(
|
|
|
+ "text",
|
|
|
+ {
|
|
|
+ class: vue.normalizeClass(["dot", `dot-${$data.connectionStatus}`])
|
|
|
+ },
|
|
|
+ "●",
|
|
|
+ 2
|
|
|
+ /* CLASS */
|
|
|
+ ),
|
|
|
+ vue.createElementVNode(
|
|
|
+ "text",
|
|
|
+ { class: "status-text" },
|
|
|
+ vue.toDisplayString($options.connectionText),
|
|
|
+ 1
|
|
|
+ /* TEXT */
|
|
|
+ )
|
|
|
+ ]),
|
|
|
+ vue.createCommentVNode(" IP 编辑区域 "),
|
|
|
+ vue.createElementVNode("view", { class: "ip-input-group" }, [
|
|
|
+ vue.createElementVNode("text", { class: "ip-label" }, "IP:"),
|
|
|
+ vue.createElementVNode("input", {
|
|
|
+ type: "text",
|
|
|
+ value: $data.serverIp,
|
|
|
+ onInput: _cache[0] || (_cache[0] = (...args) => $options.onIpInput && $options.onIpInput(...args)),
|
|
|
+ placeholder: "192.168.0.41",
|
|
|
+ class: "ip-input"
|
|
|
+ }, null, 40, ["value"]),
|
|
|
+ vue.createElementVNode("text", { class: "colon" }, ":"),
|
|
|
+ vue.createElementVNode("input", {
|
|
|
+ type: "number",
|
|
|
+ value: $data.serverPort,
|
|
|
+ onInput: _cache[1] || (_cache[1] = (...args) => $options.onPortInput && $options.onPortInput(...args)),
|
|
|
+ placeholder: "8811",
|
|
|
+ class: "port-input"
|
|
|
+ }, null, 40, ["value"]),
|
|
|
+ vue.createElementVNode("button", {
|
|
|
+ class: "btn-reconnect",
|
|
|
+ size: "mini",
|
|
|
+ onClick: _cache[2] || (_cache[2] = (...args) => $options.handleReconnect && $options.handleReconnect(...args))
|
|
|
+ }, " 更新并重连 "),
|
|
|
+ vue.createElementVNode("button", {
|
|
|
+ class: "btn-reconnect",
|
|
|
+ size: "mini",
|
|
|
+ onClick: _cache[3] || (_cache[3] = (...args) => $options.goback && $options.goback(...args))
|
|
|
+ }, " 测试 ")
|
|
|
+ ])
|
|
|
+ ],
|
|
|
+ 2
|
|
|
+ /* CLASS */
|
|
|
+ )
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ const PagesIndexIndex = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["render", _sfc_render$1], ["__scopeId", "data-v-1cf27b2a"], ["__file", "D:/code/baozhida-observation-system/pages/index/index.vue"]]);
|
|
|
+ const _sfc_main$1 = {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ serverIp: uni.getStorageSync("serverIp") || "",
|
|
|
+ scanning: false
|
|
|
+ };
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ async scan() {
|
|
|
+ if (this.scanning)
|
|
|
+ return;
|
|
|
+ this.scanning = true;
|
|
|
+ const ip = await this.scanNetwork();
|
|
|
+ if (ip) {
|
|
|
+ this.serverIp = ip;
|
|
|
+ uni.showToast({ title: "发现: " + ip });
|
|
|
+ } else {
|
|
|
+ uni.showToast({ icon: "none", title: "未发现" });
|
|
|
+ }
|
|
|
+ this.scanning = false;
|
|
|
+ },
|
|
|
+ async scanNetwork() {
|
|
|
+ const lastIp = uni.getStorageSync("serverIp");
|
|
|
+ if (lastIp && await this.testIp(lastIp)) {
|
|
|
+ return lastIp;
|
|
|
+ }
|
|
|
+ const prefix = this.getNetworkPrefix();
|
|
|
+ for (let i = 1; i <= 50; i++) {
|
|
|
+ const ip = `${prefix}${i}`;
|
|
|
+ if (await this.testIp(ip)) {
|
|
|
+ return ip;
|
|
|
+ }
|
|
|
+ await this.delay(100);
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ },
|
|
|
+ testIp(ip) {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ uni.request({
|
|
|
+ url: `http://${ip}:8811/ping`,
|
|
|
+ timeout: 3e3,
|
|
|
+ success: (res) => {
|
|
|
+ resolve(res.statusCode === 200 ? ip : null);
|
|
|
+ },
|
|
|
+ fail: () => resolve(null)
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ getNetworkPrefix() {
|
|
|
+ let prefix = "192.168.1.";
|
|
|
+ if (typeof plus !== "undefined" && plus.networkinfo) {
|
|
|
+ const ip = plus.networkinfo.getIPAddress();
|
|
|
+ if (ip)
|
|
|
+ prefix = ip.replace(/\.\d+$/, ".") + ".";
|
|
|
+ }
|
|
|
+ return prefix;
|
|
|
+ },
|
|
|
+ delay(ms) {
|
|
|
+ return new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
+ },
|
|
|
+ connect() {
|
|
|
+ if (!this.serverIp) {
|
|
|
+ uni.showToast({ icon: "none", title: "请先设置IP" });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ uni.setStorageSync("serverIp", this.serverIp);
|
|
|
+ uni.connectSocket({ url: `ws://${this.serverIp}:8811` });
|
|
|
+ uni.onSocketOpen(() => {
|
|
|
+ "link";
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
+ return vue.openBlock(), vue.createElementBlock("view", { class: "container" }, [
|
|
|
+ vue.createElementVNode("text", null, "服务端IP:"),
|
|
|
+ vue.withDirectives(vue.createElementVNode(
|
|
|
+ "input",
|
|
|
+ {
|
|
|
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => $data.serverIp = $event),
|
|
|
+ placeholder: "192.168.1.100"
|
|
|
+ },
|
|
|
+ null,
|
|
|
+ 512
|
|
|
+ /* NEED_PATCH */
|
|
|
+ ), [
|
|
|
+ [vue.vModelText, $data.serverIp]
|
|
|
+ ]),
|
|
|
+ vue.createElementVNode("button", {
|
|
|
+ onClick: _cache[1] || (_cache[1] = (...args) => $options.scan && $options.scan(...args))
|
|
|
+ }, "🔍 自动扫描"),
|
|
|
+ vue.createElementVNode("button", {
|
|
|
+ onClick: _cache[2] || (_cache[2] = (...args) => $options.connect && $options.connect(...args))
|
|
|
+ }, "🚀 连接"),
|
|
|
+ $data.scanning ? (vue.openBlock(), vue.createElementBlock("text", { key: 0 }, "扫描中...")) : vue.createCommentVNode("v-if", true)
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ const PagesIndexMine = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["render", _sfc_render], ["__file", "D:/code/baozhida-observation-system/pages/index/mine.vue"]]);
|
|
|
+ __definePage("pages/index/home", PagesIndexHome);
|
|
|
+ __definePage("pages/index/index", PagesIndexIndex);
|
|
|
+ __definePage("pages/index/mine", PagesIndexMine);
|
|
|
+ const _sfc_main = {
|
|
|
+ mounted() {
|
|
|
+ plus.screen.lockOrientation("landscape-primary");
|
|
|
+ },
|
|
|
+ onLaunch: function() {
|
|
|
+ formatAppLog("log", "at App.vue:11", "App Launch");
|
|
|
+ plus.screen.lockOrientation("landscape-primary");
|
|
|
+ },
|
|
|
+ onShow: function() {
|
|
|
+ formatAppLog("log", "at App.vue:18", "App Show");
|
|
|
+ },
|
|
|
+ onHide: function() {
|
|
|
+ formatAppLog("log", "at App.vue:21", "App Hide");
|
|
|
+ }
|
|
|
+ };
|
|
|
+ const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "D:/code/baozhida-observation-system/App.vue"]]);
|
|
|
+ function createApp() {
|
|
|
+ const app = vue.createVueApp(App);
|
|
|
+ return {
|
|
|
+ app
|
|
|
+ };
|
|
|
+ }
|
|
|
+ const { app: __app__, Vuex: __Vuex__, Pinia: __Pinia__ } = createApp();
|
|
|
+ uni.Vuex = __Vuex__;
|
|
|
+ uni.Pinia = __Pinia__;
|
|
|
+ __app__.provide("__globalStyles", __uniConfig.styles);
|
|
|
+ __app__._component.mpType = "app";
|
|
|
+ __app__._component.render = () => {
|
|
|
+ };
|
|
|
+ __app__.mount("#app");
|
|
|
+})(Vue);
|