buddhism/pages/memorial/nfcPairing.vue
2025-11-25 10:28:39 +08:00

1164 lines
35 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<base-background />
<custom-navbar title="NFC配对" />
<view class="content">
<view class="status-card">
<view class="status-header">
<view
:class="['status-dot', socketConnected ? 'online' : 'offline']"
/>
<text class="status-title">{{ connectionText }}</text>
</view>
<view class="status-desc">
<text>请保持手机在线等待刷卡设备将NFC卡号传递到本页面</text>
<text v-if="nfcSupported" class="nfc-hint">
<text class="nfc-hint-text">或使用手机 NFC 功能直接读取卡片</text>
</text>
</view>
<view v-if="nfcSupported" class="nfc-status">
<view class="nfc-status-header">
<view :class="['status-dot', nfcEnabled ? 'online' : 'offline']" />
<text class="nfc-status-title">{{ nfcStatusText }}</text>
</view>
<view v-if="nfcError" class="nfc-error-text">
<!-- <text class="error-content">{{ nfcError }}</text>-->
<text class="error-content">请打开NFC</text>
</view>
</view>
<view :class="{ ready: !!cardNo }" class="card-box">
<text class="card-label">NFC卡号</text>
<text class="card-value">{{ cardNo || "等待刷卡..." }}</text>
</view>
<view v-if="connectionError" class="error-text">
<text class="error-content">{{ connectionError }}</text>
</view>
<view v-else-if="lastMessage" class="hint-text">{{ lastMessage }}</view>
<view class="status-actions">
<view class="text-btn" @click="handleRetry">重新连接</view>
<view v-if="cardNo" class="text-btn" @click="resetCard"
>清空卡号
</view>
<view
v-if="connectionError"
class="text-btn"
@click="testServerConnection"
>测试服务器
</view>
</view>
</view>
<view class="form-card">
<!-- <view class="field">-->
<!-- <text class="label">设备 MAC 地址</text>-->
<!-- <input-->
<!-- v-model.trim="deviceMac"-->
<!-- class="input"-->
<!-- maxlength="32"-->
<!-- placeholder="请输入设备 MAC"-->
<!-- placeholder-class="placeholder"-->
<!-- />-->
<!-- </view>-->
<view class="field">
<text class="label">NFC 卡号</text>
<input
v-model="cardNo"
class="input"
disabled
placeholder="等待刷卡"
placeholder-class="placeholder"
/>
</view>
<!-- 牌位信息显示 -->
<view
v-if="
memorialInfo.regionName || memorialInfo.code || memorialInfo.mac
"
class="memorial-info-section"
>
<view class="field readonly">
<text class="label">区域名称</text>
<text class="unit-value">{{ memorialInfo.regionName || "-" }}</text>
</view>
<view class="field readonly">
<text class="label">编码</text>
<text class="unit-value">{{ memorialInfo.code || "-" }}</text>
</view>
<view class="field readonly">
<text class="label">MAC 地址</text>
<text class="unit-value">{{ memorialInfo.mac || "-" }}</text>
</view>
</view>
<!-- <view v-if="unitId" class="field readonly">-->
<!-- <text class="label">绑定单元 ID</text>-->
<!-- <text class="unit-value">{{ unitId }}</text>-->
<!-- </view>-->
<view
:class="['primary-btn', { disabled: !canSubmit || binding }]"
@click="handleBind"
>
{{ binding ? "提交中..." : "提交绑定" }}
</view>
</view>
</view>
</view>
</template>
<script>
import BaseBackground from "@/components/base-background/base-background.vue";
import CustomNavbar from "@/components/custom-navbar/custom-navbar.vue";
import { getRequestConfig, getToken } from "@/utils/request.js";
import {
bindNfcCard,
getMemorialDetail,
swipeNfcCard,
} from "@/api/memorial/index.js";
const WS_PATH = "/ws/ws/device";
const FIXED_MAC = "111111111111";
export default {
components: {
BaseBackground,
CustomNavbar,
},
data() {
return {
unitId: "",
memorialId: "", // 牌位ID
socketTask: null,
socketConnected: false,
deviceMac: "",
cardNo: "",
binding: false,
connectionError: "",
lastMessage: "",
usingGlobalSocketEvents: false,
globalSocketHandlers: null,
connectTimeout: null,
// NFC 相关状态
nfcAdapter: null,
nfcSupported: false,
nfcEnabled: false,
nfcError: "",
reportingSwipe: false,
// 牌位信息
memorialInfo: {
regionName: "",
code: "",
mac: "",
},
};
},
computed: {
canSubmit() {
return !!this.cardNo;
},
connectionText() {
if (this.connectionError) {
return "连接异常";
}
return this.socketConnected ? "已连接,等待刷卡" : "连接中...";
},
nfcStatusText() {
if (!this.nfcSupported) {
return "设备不支持 NFC";
}
if (this.nfcError) {
// return `NFC 错误: ${this.nfcError}`;
return ``;
}
return this.nfcEnabled ? "NFC 已开启,请将卡片贴近手机" : "NFC 未开启";
},
},
async onLoad(options = {}) {
// 优先从 id 参数获取牌位ID
if (options.id) {
this.memorialId = options.id;
await this.loadMemorialInfo(options.id);
} else if (options.unitId) {
this.unitId = options.unitId;
this.memorialId = options.unitId;
await this.loadMemorialInfo(options.unitId);
}
if (options.mac) {
this.deviceMac = options.mac;
}
if (options.label) {
this.label = options.label;
}
this.initSocket();
this.initNFC();
},
onUnload() {
this.cleanupSocket();
this.cleanupNFC();
},
methods: {
buildSocketUrl() {
try {
const { baseUrl } = getRequestConfig();
if (!baseUrl) {
console.error("buildSocketUrl: baseUrl 为空");
return "";
}
// 获取当前登录的 token
const token = getToken();
if (!token) {
console.error("buildSocketUrl: token 为空,请先登录");
this.connectionError = "未登录,请先登录后再试";
return "";
}
// 根据 baseUrl 的协议自动选择 ws 或 wss
const protocol = "wss";
const host = baseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
// 构建查询参数token 和固定的 mac
const query = `?token=${encodeURIComponent(token)}&mac=${FIXED_MAC}`;
const url = `${protocol}://${host}${WS_PATH}${query}`;
console.log("构建 WebSocket URL:", {
baseUrl,
protocol,
host,
path: WS_PATH,
token: token ? `${token.substring(0, 20)}...` : "无",
mac: FIXED_MAC,
finalUrl: url.replace(token, "***"), // 日志中隐藏完整token
});
return url;
} catch (error) {
console.error("构建WebSocket地址失败", error);
return "";
}
},
initSocket() {
this.connectionError = "";
this.lastMessage = "";
const url = this.buildSocketUrl();
if (!url) {
this.connectionError = "缺少WebSocket地址请检查配置";
console.error("initSocket: URL 构建失败");
return;
}
this.cleanupSocket();
console.log("NFC配对页面发起WebSocket连接:", url);
try {
// 添加连接超时处理
this.connectTimeout = setTimeout(() => {
if (!this.socketConnected) {
console.error("WebSocket 连接超时");
this.connectionError = "连接超时,请检查网络和服务器状态";
this.cleanupSocket();
}
}, 10000); // 10秒超时
this.socketTask = uni.connectSocket({
url,
success: (res) => {
console.log("uni.connectSocket success:", res);
},
fail: (err) => {
console.error("uni.connectSocket fail:", err);
clearTimeout(this.connectTimeout);
this.connectionError = `连接失败: ${err.errMsg || "未知错误"}`;
this.socketConnected = false;
},
});
if (!this.socketTask) {
clearTimeout(this.connectTimeout);
this.connectionError = "当前环境不支持WebSocket";
console.error("initSocket: socketTask 为 null");
return;
}
if (typeof this.socketTask.onOpen === "function") {
console.log("使用 Task 级别事件绑定", this.socketTask);
this.bindTaskSocketEvents();
} else {
console.log("使用全局事件绑定", this.socketTask);
this.bindGlobalSocketEvents();
}
} catch (error) {
console.error("initSocket 异常:", error);
clearTimeout(this.connectTimeout);
this.connectionError = `连接异常: ${error.message || "未知错误"}`;
}
},
cleanupSocket() {
// 清除连接超时定时器
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
if (this.socketTask && typeof this.socketTask.close === "function") {
try {
this.socketTask.close();
console.log("WebSocket 连接已关闭 (Task级别)");
} catch (error) {
console.warn("关闭WebSocket失败", error);
}
} else {
try {
uni.closeSocket && uni.closeSocket({});
console.log("WebSocket 连接已关闭 (全局)");
} catch (error) {
console.warn("关闭WebSocket失败", error);
}
}
this.unbindGlobalSocketEvents();
this.socketTask = null;
this.socketConnected = false;
},
bindTaskSocketEvents() {
if (!this.socketTask) return;
this.socketTask.onOpen(() => {
console.log("NFC WebSocket 已连接 (Task级别)");
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
this.socketConnected = true;
this.connectionError = "";
this.lastMessage = "连接成功,等待刷卡...";
});
this.socketTask.onClose((event) => {
console.warn("NFC WebSocket 连接关闭 (Task级别)", event);
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
this.socketConnected = false;
this.socketTask = null;
if (!this.connectionError) {
this.connectionError = "连接已断开";
}
});
this.socketTask.onError((error) => {
console.error("NFC WebSocket 错误 (Task级别)", error);
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
// 解析错误信息
let errorMsg = error.errMsg || error.message || "连接失败";
let userFriendlyMsg = "连接失败";
// 处理 Invalid HTTP status 错误
if (
errorMsg.includes("Invalid HTTP status") ||
error.errCode === 1004
) {
userFriendlyMsg =
"服务器不支持WebSocket或路径不存在\n请检查\n1. 服务器是否正常运行\n2. WebSocket路径是否正确\n3. 服务器是否支持WebSocket协议";
console.error("WebSocket握手失败可能原因", {
url: this.buildSocketUrl(),
errorCode: error.errCode,
errorMsg: errorMsg,
suggestion: "服务器可能返回了404或500错误请检查服务器日志",
});
} else if (errorMsg.includes("timeout")) {
userFriendlyMsg = "连接超时,请检查网络连接";
} else if (errorMsg.includes("fail")) {
userFriendlyMsg = "网络连接失败,请检查网络和服务器地址";
}
this.connectionError = userFriendlyMsg;
this.socketConnected = false;
});
this.socketTask.onMessage((event) => {
this.handleSocketMessage(event);
console.log("WebSocket <UNK>接收到事件", event);
});
},
bindGlobalSocketEvents() {
if (this.usingGlobalSocketEvents) return;
this.usingGlobalSocketEvents = true;
this.globalSocketHandlers = {
open: () => {
console.log("NFC WebSocket 已连接 (全局事件)");
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
this.socketConnected = true;
this.connectionError = "";
this.lastMessage = "连接成功,等待刷卡...";
},
close: (event) => {
console.warn("NFC WebSocket 连接关闭 (全局事件)", event);
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
this.socketConnected = false;
if (!this.connectionError) {
this.connectionError = "连接已断开";
}
},
error: (error) => {
console.error("NFC WebSocket 错误 (全局事件)", error);
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
// 解析错误信息
let errorMsg = error.errMsg || error.message || "连接失败";
let userFriendlyMsg = "连接失败";
// 处理 Invalid HTTP status 错误
if (
errorMsg.includes("Invalid HTTP status") ||
error.errCode === 1004
) {
userFriendlyMsg =
"服务器不支持WebSocket或路径不存在\n请检查\n1. 服务器是否正常运行\n2. WebSocket路径是否正确\n3. 服务器是否支持WebSocket协议";
console.error("WebSocket握手失败可能原因", {
url: this.buildSocketUrl(),
errorCode: error.errCode,
errorMsg: errorMsg,
suggestion: "服务器可能返回了404或500错误请检查服务器日志",
});
} else if (errorMsg.includes("timeout")) {
userFriendlyMsg = "连接超时,请检查网络连接";
} else if (errorMsg.includes("fail")) {
userFriendlyMsg = "网络连接失败,请检查网络和服务器地址";
}
this.connectionError = userFriendlyMsg;
this.socketConnected = false;
},
message: (event) => {
this.handleSocketMessage(event);
},
};
uni.onSocketOpen && uni.onSocketOpen(this.globalSocketHandlers.open);
uni.onSocketClose && uni.onSocketClose(this.globalSocketHandlers.close);
uni.onSocketError && uni.onSocketError(this.globalSocketHandlers.error);
uni.onSocketMessage &&
uni.onSocketMessage(this.globalSocketHandlers.message);
},
unbindGlobalSocketEvents() {
if (!this.usingGlobalSocketEvents || !this.globalSocketHandlers) return;
const { open, close, error, message } = this.globalSocketHandlers;
uni.offSocketOpen && open && uni.offSocketOpen(open);
uni.offSocketClose && close && uni.offSocketClose(close);
uni.offSocketError && error && uni.offSocketError(error);
uni.offSocketMessage && message && uni.offSocketMessage(message);
this.usingGlobalSocketEvents = false;
this.globalSocketHandlers = null;
},
handleSocketMessage(event) {
let message = event?.data;
this.lastMessage = "";
try {
if (typeof message === "string") {
const trimmed = message.trim();
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
const parsed = JSON.parse(trimmed);
if (parsed.cardNo || parsed.cardNumber || parsed.nfcNo) {
this.cardNo = parsed.cardNo || parsed.cardNumber || parsed.nfcNo;
this.lastMessage = "已接收卡号";
uni.showToast({ title: "收到卡号", icon: "success" });
return;
}
this.lastMessage = parsed.msg || trimmed;
return;
} else {
this.cardNo = trimmed.replace(/^['"]|['"]$/g, "");
console.log("接收到的卡号", this.cardNo);
this.lastMessage = "已接收卡号";
uni.showToast({ title: "收到卡号", icon: "success" });
return;
}
} else if (typeof message === "object" && message) {
const data = message.data || message;
if (data.cardNo || data.cardNumber || data.nfcNo) {
this.cardNo = data.cardNo || data.cardNumber || data.nfcNo;
this.lastMessage = "已接收卡号";
uni.showToast({ title: "收到卡号", icon: "success" });
return;
}
}
this.lastMessage = "收到未知消息";
} catch (error) {
console.error("解析WebSocket消息失败", error, message);
this.lastMessage = "消息解析失败";
}
},
resetCard() {
this.cardNo = "";
this.lastMessage = "";
},
handleRetry() {
this.initSocket();
},
// 测试服务器连接(用于诊断)
async testServerConnection() {
try {
const { baseUrl } = getRequestConfig();
console.log("测试服务器连接:", baseUrl);
const token = getToken();
if (!token) {
uni.showModal({
title: "未登录",
content: "请先登录后再测试服务器连接",
showCancel: false,
});
return;
}
// 测试 WebSocket 服务器是否可达
const isHttps = baseUrl.startsWith("https://");
const protocol = isHttps ? "wss" : "ws";
const host = baseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
const testUrl = `${protocol}://${host}${WS_PATH}?token=${encodeURIComponent(token)}&mac=${FIXED_MAC}`;
console.log("测试 WebSocket URL:", testUrl.replace(token, "***"));
uni.showLoading({ title: "测试连接中...", mask: true });
// 尝试连接 WebSocket 来测试服务器
const testSocket = uni.connectSocket({
url: testUrl,
success: () => {
console.log("WebSocket 连接测试:连接请求已发送");
},
fail: (err) => {
console.error("WebSocket 连接测试失败:", err);
uni.hideLoading();
uni.showModal({
title: "服务器连接测试",
content: `无法连接到 WebSocket 服务器\n错误: ${err.errMsg || "未知错误"}\n\n请检查\n1. 服务器地址是否正确 (${host})\n2. WebSocket 服务是否运行\n3. 网络是否正常\n4. Token 是否有效`,
showCancel: false,
});
},
});
// 设置超时
const timeout = setTimeout(() => {
testSocket.close();
uni.hideLoading();
uni.showModal({
title: "连接超时",
content: `WebSocket 连接超时\n\n请检查\n1. 服务器地址: ${host}\n2. WebSocket 路径: ${WS_PATH}\n3. 服务器是否正常运行`,
showCancel: false,
});
}, 5000);
testSocket.onOpen(() => {
clearTimeout(timeout);
testSocket.close();
uni.hideLoading();
uni.showToast({
title: "服务器连接正常",
icon: "success",
duration: 2000,
});
});
testSocket.onError((err) => {
clearTimeout(timeout);
uni.hideLoading();
uni.showModal({
title: "服务器连接测试",
content: `WebSocket 连接失败\n错误: ${err.errMsg || "未知错误"}\n\n请检查\n1. 服务器地址是否正确\n2. WebSocket 服务是否运行\n3. Token 是否有效`,
showCancel: false,
});
});
} catch (error) {
console.error("测试连接异常:", error);
uni.hideLoading();
uni.showToast({
title: "测试失败",
icon: "none",
});
}
},
/**
* 加载牌位信息
* @param {string} id - 牌位ID
*/
async loadMemorialInfo(id) {
if (!id) {
console.warn("loadMemorialInfo: id 为空");
return;
}
try {
console.log("开始加载牌位信息, id:", id);
const res = await getMemorialDetail(id);
console.log("牌位信息API响应:", res);
if (res && res.code === 200 && res.data) {
const data = res.data;
this.memorialInfo = {
regionName: data.regionName || "",
code: data.code || "",
mac: data.mac || "",
};
// 如果接口返回了 unitId也更新
if (data.id) {
this.unitId = data.id;
this.memorialId = data.id;
}
// 如果接口返回了 mac也更新 deviceMac
if (data.mac) {
this.deviceMac = data.mac;
}
console.log("牌位信息加载成功:", this.memorialInfo);
} else {
console.error("牌位信息API响应无效:", res);
uni.showToast({
title: "获取牌位信息失败",
icon: "none",
duration: 2000,
});
}
} catch (error) {
console.error("加载牌位信息失败:", error);
uni.showToast({
title: "获取牌位信息失败",
icon: "none",
duration: 2000,
});
}
},
async handleBind() {
if (!this.canSubmit || this.binding) return;
this.binding = true;
uni.showLoading({ title: "提交中...", mask: true });
try {
const payload = {
// memorialMac: this.deviceMac,
nfcMac: this.cardNo,
};
// 优先使用 memorialId如果没有则使用 unitId
const memorialId = this.memorialId || this.unitId;
if (memorialId) {
payload.memorialId = memorialId;
}
const res = await bindNfcCard(payload);
const success = res && (res.code === 200 || res.status === 200);
const message = (res && res.msg) || "";
const displayMessage = message || (success ? "读取成功" : "读取失败");
this.lastMessage = displayMessage;
const toastDuration = success ? 2600 : 2600;
await this.showStableToast({
title: displayMessage,
icon: success ? "success" : "none",
duration: toastDuration,
});
if (success) {
uni.navigateBack({ delta: 1 });
}
} catch (error) {
console.error("提交绑定失败", error);
uni.showToast({ title: "提交失败,请重试", icon: "none" });
} finally {
this.binding = false;
uni.hideLoading();
}
},
// NFC 相关方法
initNFC() {
// 检查是否支持 NFC微信小程序环境
// #ifdef MP-WEIXIN
if (typeof wx === "undefined" || !wx.getNFCAdapter) {
console.warn("当前环境不支持 NFC 功能");
this.nfcSupported = false;
return;
}
try {
// 获取 NFC 适配器
const nfcAdapter = wx.getNFCAdapter();
if (!nfcAdapter) {
console.warn("无法获取 NFC 适配器");
this.nfcSupported = false;
return;
}
this.nfcAdapter = nfcAdapter;
this.nfcSupported = true;
// 开始发现 NFC 标签
this.startNFCDiscovery();
} catch (error) {
console.error("初始化 NFC 失败:", error);
this.nfcSupported = false;
this.nfcError = error.message || "初始化失败";
}
// #endif
// #ifndef MP-WEIXIN
// 非微信小程序环境,不支持 NFC
console.warn("NFC 功能仅在微信小程序中支持");
this.nfcSupported = false;
// #endif
},
startNFCDiscovery() {
if (!this.nfcAdapter) return;
try {
// 监听 NFC 标签发现事件
this.nfcAdapter.onDiscovered((res) => {
this.handleNfcDiscovered(res);
});
// 开始发现 NFC 标签
this.nfcAdapter.startDiscovery({
success: () => {
console.log("NFC 发现已启动");
this.nfcEnabled = true;
this.nfcError = "";
this.lastMessage = "NFC 已开启,请将卡片贴近手机";
},
fail: (err) => {
console.error("启动 NFC 发现失败:", err);
this.nfcEnabled = false;
this.nfcError = err.errMsg || "启动失败";
// 如果是权限问题,给出提示
if (err.errMsg && err.errMsg.includes("permission")) {
this.nfcError = "需要 NFC 权限,请在设置中开启";
} else if (err.errMsg && err.errMsg.includes("not support")) {
this.nfcError = "设备不支持 NFC 功能";
this.nfcSupported = false;
}
},
});
} catch (error) {
console.error("启动 NFC 发现异常:", error);
this.nfcEnabled = false;
this.nfcError = error.message || "启动异常";
}
},
handleNfcDiscovered(res) {
console.log("发现 NFC 标签:", res);
try {
let cardNo = "";
console.log("移动号获取NFC:res", res);
console.log("res.id 类型:", typeof res.id, res.id);
console.log("res.techs:", res.techs);
// NFC 卡号主要存储在 res.id 中,格式为 ArrayBuffer
// 对于 MIFARE Classic 等卡片UID 就存储在 id 字段
// 根据调试信息:[[ArrayBufferData]]: 1312 表示4字节的数值
if (res.id) {
// id 通常是 ArrayBuffer需要转换为字符串
if (res.id instanceof ArrayBuffer) {
// 将 ArrayBuffer 转换为十六进制字符串
const uint8Array = new Uint8Array(res.id);
const bytes = Array.from(uint8Array);
console.log("=== NFC 卡号提取详情 ===");
console.log("ArrayBuffer 字节长度:", res.id.byteLength);
console.log("ArrayBuffer 字节数组(十进制):", bytes);
console.log(
"ArrayBuffer 字节数组(十六进制):",
bytes.map(
(b) => "0x" + b.toString(16).padStart(2, "0").toUpperCase(),
),
);
// 转换为十进制字符串
// NFC 卡号通常按照字节顺序(大端序)存储
const baseCardNo = bytes.join("").toUpperCase();
// 计算异或校验码(按位异或)
const checksum = bytes.reduce((acc, curr) => acc ^ curr, 0);
const checksumBinary = checksum.toString(2).padStart(8, "0");
cardNo = `${baseCardNo}${checksum}`;
console.log("转换后的卡号(十进制字符串+校验):", cardNo);
console.log("基础卡号部分:", baseCardNo);
console.log("校验码(二进制):", checksumBinary);
console.log("校验码(十进制):", checksum);
console.log("卡号长度:", cardNo.length, "字符");
console.log("========================");
} else if (typeof res.id === "string") {
cardNo = res.id;
console.log("卡号(字符串):", cardNo);
} else {
console.warn("res.id 格式未知:", res.id);
}
}
// 如果 id 字段没有值,尝试从 techs 中获取
if (!cardNo && res.techs && res.techs.length > 0) {
// 尝试从 techs 中读取数据
for (const tech of res.techs) {
if (tech.id) {
if (tech.id instanceof ArrayBuffer) {
const uint8Array = new Uint8Array(tech.id);
cardNo = Array.from(uint8Array)
.map((b) => b.toString(16).padStart(2, "0"))
.join("")
.toUpperCase();
break;
} else if (typeof tech.id === "string") {
cardNo = tech.id;
break;
}
}
}
}
// 如果还是没有获取到,尝试从其他字段获取
if (!cardNo) {
// 尝试从 res 的其他字段获取
if (res.serialNumber) {
cardNo = res.serialNumber;
} else if (res.uid) {
cardNo = res.uid;
} else if (res.cardId) {
cardNo = res.cardId;
}
}
if (cardNo) {
// 清理卡号格式(去除空格、冒号等)
cardNo = cardNo.replace(/[\s:]/g, "").toUpperCase();
this.cardNo = cardNo;
this.lastMessage = "已通过 NFC 读取卡号";
// uni.showToast({
// title: "读取成功",
// icon: "success",
// duration: 1500,
// });
console.log("NFC 读取到的卡号:", cardNo);
this.reportSwipeCard(cardNo);
} else {
console.warn("无法从 NFC 标签中提取卡号:", res);
uni.showToast({
title: "无法读取卡号",
icon: "none",
duration: 2000,
});
}
} catch (error) {
console.error("处理 NFC 标签数据失败:", error);
uni.showToast({
title: "读取失败",
icon: "none",
duration: 2000,
});
}
},
cleanupNFC() {
if (this.nfcAdapter) {
try {
// 停止 NFC 发现
this.nfcAdapter.stopDiscovery({
success: () => {
console.log("NFC 发现已停止");
},
fail: (err) => {
console.warn("停止 NFC 发现失败:", err);
},
});
} catch (error) {
console.warn("清理 NFC 资源失败:", error);
}
this.nfcAdapter = null;
}
this.nfcEnabled = false;
},
async showStableToast(options = {}) {
const { duration = 2000, ...rest } = options;
return new Promise((resolve) => {
uni.showToast({
duration,
...rest,
complete: () => {
setTimeout(resolve, duration);
},
});
});
},
async reportSwipeCard(cardNo) {
if (!cardNo || this.reportingSwipe) {
return;
}
this.reportingSwipe = true;
uni.showLoading({
title: "处理中...",
mask: true,
});
try {
const res = await swipeNfcCard({ nfcMac: cardNo });
if (res && res.code === 200) {
const message = (res && res.msg) || "";
const displayMessage = message || "读取成功";
this.lastMessage = displayMessage;
await this.showStableToast({
title: displayMessage,
icon: "success",
duration: 2600,
});
} else {
await this.showStableToast({
title: (res && res.msg) || "操作失败",
icon: "none",
duration: 2600,
});
}
} catch (error) {
console.error("NFC 卡号上报失败:", error);
uni.showToast({
title: "请求失败,请重试",
icon: "none",
duration: 2000,
});
} finally {
this.reportingSwipe = false;
uni.hideLoading();
}
},
},
};
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
width: 100%;
padding-bottom: 40rpx;
box-sizing: border-box;
}
.content {
padding: 0 32rpx 60rpx;
box-sizing: border-box;
}
.status-card,
.form-card {
background: rgba(255, 255, 255, 0.92);
border-radius: 24rpx;
padding: 32rpx;
margin-top: 32rpx;
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.06);
}
.status-header {
display: flex;
align-items: center;
gap: 16rpx;
}
.status-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #f0b400;
}
.status-dot.online {
background: #3ac569;
}
.status-dot.offline {
background: #f56c6c;
}
.status-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
}
.status-desc {
margin-top: 12rpx;
font-size: 24rpx;
color: #999;
line-height: 1.5;
}
.nfc-hint {
display: block;
margin-top: 8rpx;
}
.nfc-hint-text {
color: #4a90e2;
font-size: 22rpx;
}
.nfc-status {
margin-top: 24rpx;
padding: 20rpx;
background: rgba(74, 144, 226, 0.08);
border-radius: 16rpx;
border-left: 4rpx solid #4a90e2;
}
.nfc-status-header {
display: flex;
align-items: center;
gap: 12rpx;
}
.nfc-status-title {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
.nfc-error-text {
margin-top: 12rpx;
padding: 12rpx;
background: #fef0f0;
border-radius: 8rpx;
}
.card-box {
margin-top: 24rpx;
border: 2rpx dashed #f0b400;
border-radius: 20rpx;
padding: 24rpx;
background: #fff9eb;
}
.card-box.ready {
border-color: #3ac569;
background: #effbf4;
}
.card-label {
font-size: 24rpx;
color: #666;
}
.card-value {
display: block;
margin-top: 16rpx;
font-size: 36rpx;
font-weight: 600;
color: #333;
word-break: break-all;
}
.error-text {
margin-top: 16rpx;
padding: 16rpx;
background: #fef0f0;
border-radius: 12rpx;
border-left: 4rpx solid #f56c6c;
}
.error-content {
color: #f56c6c;
font-size: 24rpx;
line-height: 1.6;
white-space: pre-line;
word-break: break-all;
}
.hint-text {
margin-top: 16rpx;
color: #999;
font-size: 24rpx;
}
.status-actions {
margin-top: 24rpx;
display: flex;
gap: 32rpx;
}
.text-btn {
font-size: 26rpx;
color: #4a90e2;
}
.field {
margin-bottom: 32rpx;
}
.field.readonly {
padding: 24rpx;
background: #f9f9f9;
border-radius: 16rpx;
}
.memorial-info-section {
margin-bottom: 24rpx;
padding: 24rpx;
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
border-radius: 16rpx;
border-left: 4rpx solid #4a90e2;
}
.memorial-info-section .field.readonly {
background: rgba(255, 255, 255, 0.8);
margin-bottom: 16rpx;
}
.memorial-info-section .field.readonly:last-child {
margin-bottom: 0;
}
.label {
font-size: 26rpx;
color: #666;
margin-bottom: 12rpx;
display: block;
}
.input {
width: 100%;
height: 88rpx;
border-radius: 16rpx;
background: #f8f8f8;
padding: 0 24rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
}
.placeholder {
color: #bbb;
}
.unit-value {
font-size: 30rpx;
color: #333;
word-break: break-all;
}
.primary-btn {
height: 96rpx;
border-radius: 16rpx;
background: linear-gradient(135deg, #f0b400, #f08400);
color: #fff;
font-size: 30rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
margin-top: 12rpx;
}
.primary-btn.disabled {
opacity: 0.5;
}
</style>