| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353 |
- <template>
- <view class="page">
- <!-- 导航按钮 -->
- <button @click="logout" class="nav-btn">
- <text class="icon fa fa-sign-out"></text>
- </button>
- <button @click="toggleServerConfig" class="nav-btn">
- <text class="icon fa fa-cog"></text>
- </button>
- <scroll-view scroll-y class="scroll-area">
- <view v-if="!showServerConfig" class="form-section">
- <view class="section-title">
- <text class="title"></text>
- </view>
- <!-- RFID扫描区域 -->
- <view class="rfid-card">
- <text class="icon fa fa-qrcode scan-icon"></text>
- <view class="btn-group">
- <button class="manual-btn" @click="init" :disabled="isDisable">
- <text class="fa fa-power-off"></text> {{ isDeviceReady ? '已开启' : '开启设备' }}
- </button>
- <!-- 释放端口按钮 -->
- <!-- <button class="btn" type="primary" @click="release" :disabled="isReDisabled">release</button> -->
- <button class="scan-btn" @click="toggleContinuousScan" :disabled="!isDisable">
- <text class="fa fa-camera"></text> {{ isContinuousScanning ? '停止持续扫描' : '开始持续扫描' }}
- </button>
- </view>
- <input class="input-box" v-model="form.earId" disabled placeholder="当前FID标签号码将显示在这里" style="height: 30px;width: 95%;" />
- <!-- 存储的FID标签数据 -->
- <scroll-view scroll-y class="sv">
- <view v-for="(item, index) in dataList" :key="index" class="data-item">
- <p style="margin-left: 10px;">{{ item.id }}</p>
- <picker @change="onTypeChange($event, index)" :value="item.typeIndex" :range="types">
- <view class="picker">
- {{ types[item.typeIndex] }}
- </view>
- </picker>
- <button class="delete-btn" @click="deleteItem(index)">删除</button>
- </view>
- </scroll-view>
- </view>
- <!-- 操作按钮部分 -->
- <view class="btn-group">
- <button class="manual-btn" @click="resetForm">重置</button>
- <button class="scan-btn" @click="submitForm">提交数据</button>
- </view>
- </view>
- </scroll-view>
- </view>
- </template>
- <script>
- // 导入API配置
- import API from '@/api/index.js'
- export default {
- data() {
- return {
- dataList: [], // 存储扫描的耳标数据
- types: ['正常', '淘汰', '死亡'], // 耳标类型选项
- scanProgress: 0, // 当前扫描进度
- scanTotalAttempts: 0, // 总扫描尝试次数
- isDisable: false, // 设备禁用状态
- isDeviceReady: false, // 设备就绪状态
- isInitializing: false, // 设备初始化中状态
- currentDate: '', // 当前日期
- showServerConfig: false,// 是否显示服务器配置
- uhfSFHelper: null, // UHF插件实例
- scanTimeout: null, // 扫描超时计时器
- retryTimeout: null, // 重试计时器
- maxScanTimer: null, // 最大扫描时间计时器
- settingChangeListener: null, // 设置变化监听器
- isContinuousScanning: false, // 是否正在持续扫描
- continuousScanInterval: null, // 持续扫描间隔计时器
-
- // 列表数据
- buildingList: [], // 栋舍列表
- roomList: [], // 房间列表
- Fieldnumber: [], // 栏位列表
-
- // 表单数据
- form: {
- earId: '', // 耳标ID
- buildingName: '', // 栋舍名称
- roomName: '', // 房间名称
- penNo: '', // 栏位编号
- status: 'healthy', // 状态
- note: '' // 备注
- },
-
- // 提交状态
- isSubmitting: false,
-
- }
- },
-
- mounted() {
- // 标记组件已挂载
- this._isMounted = true;
-
- // 初始化日期
- const now = new Date()
- this.currentDate = now.toISOString().split('T')[0]
-
- // 加载已保存的设置
- this.loadSavedSettings();
-
- // 获取列表数据(即使插件未初始化也可以加载)
- this.fetchBuildingList();
-
- // 监听登录成功事件,重新加载用户设置并清空数据
- this.reloadUserSettingsListener = uni.$on('reloadUserSettings', () => {
- // 清空数据
- this.resetForm();
-
- this.loadSavedSettings();
- // 重新加载栋舍列表,确保使用最新的用户信息
- this.fetchBuildingList();
- });
-
- // 初始化插件实例
- this.initializePluginWithRetry(0);
-
- // 监听全局设置变化
- this.settingChangeListener = uni.$on('settingsUpdated', (settings) => {
- console.log('监听到设置更新事件,更新设置:', settings);
- this.loadSavedSettings(settings);
- });
- },
-
- /**
- * 带重试的插件初始化
- * @param {number} retryCount - 当前重试次数
- */
- initializePluginWithRetry(retryCount = 0) {
- console.log('设备初始化成功');
- this.isDisable = false;
- },
- beforeUnmount() {
- // 标记组件已卸载
- this._isMounted = false;
- // 停止持续扫描
- this.isContinuousScanning = false;
- if (this.continuousScanInterval) {
- clearTimeout(this.continuousScanInterval);
- this.continuousScanInterval = null;
- }
- // 清除所有计时器
- this.cancelScan();
- // 释放设备
- this.releaseDevice();
- // 清理插件实例
- this.uhfSFHelper = null;
- // 移除事件监听
- if (this.settingChangeListener) {
- uni.$off('settingsUpdated', this.settingChangeListener);
- }
- // 移除登录成功事件监听
- if (this.reloadUserSettingsListener) {
- uni.$off('reloadUserSettings', this.reloadUserSettingsListener);
- }
- },
-
- /**
- * 页面显示时触发,确保获取最新的编号信息并检查登录状态
- */
- onShow() {
- this.loadSavedSettings();
- this.checkTokenExpiration();
- },
-
- methods: {
- /**
- * 检查token是否过期
- */
- checkTokenExpiration() {
- const app = getApp();
- // 获取过期时间和token
- let expireAt = app.globalData.expireAt || uni.getStorageSync('token_expire_time') || '';
- const token = app.globalData.token || uni.getStorageSync('equipment_token') || '';
- // 如果没有token或过期时间,视为未登录
- if (!token || !expireAt) {
- console.log('未登录或缺少登录信息');
- // 不要直接跳转登录页,让App.vue的checkLoginStatus来处理
- return;
- }
- try {
- // 解析过期时间和当前时间
- let expireTime;
-
- // 处理不同类型的expireAt
- if (typeof expireAt === 'string') {
- // 尝试直接解析为数字
- const timestamp = parseInt(expireAt);
- if (!isNaN(timestamp)) {
- expireTime = timestamp;
- } else {
- // 如果不是数字字符串,尝试作为日期字符串解析
- expireTime = new Date(expireAt).getTime();
- }
- } else if (typeof expireAt === 'number') {
- // 如果已经是数字类型,直接使用
- expireTime = expireAt;
- }
- // 确保过期时间有效
- if (isNaN(expireTime)) {
- console.error('无效的过期时间:', expireAt);
- return;
- }
- const currentTime = new Date().getTime();
- // 提前1分钟检查过期,给用户预留时间
- const earlyCheckTime = 60 * 1000;
- if (currentTime + earlyCheckTime > expireTime) {
- console.log('登录即将过期或已过期');
- // 不要直接跳转登录页,让App.vue的checkLoginStatus来处理
- }
- } catch (error) {
- console.error('检查token过期时发生错误:', error);
- }
- },
- /**
- * 带重试的插件初始化
- * @param {number} retryCount - 当前重试次数
- */
- initializePluginWithRetry(retryCount = 0) {
- const MAX_RETRIES = 3; // 增加重试次数到3次
- const RETRY_DELAY = 2000; // 2秒
-
- console.log(`开始插件初始化尝试 ${retryCount + 1}/${MAX_RETRIES}`);
-
- const isPluginInitialized = this.initPluginInstance();
- if (!isPluginInitialized) {
- console.warn(`插件初始化失败 (attempt ${retryCount + 1}/${MAX_RETRIES})`);
- this.isDisable = true; // 禁用依赖插件的按钮
-
- // 如果未达到最大重试次数,继续重试
- if (retryCount < MAX_RETRIES) {
- console.log(`Retrying plugin initialization after ${RETRY_DELAY}ms`);
- setTimeout(() => {
- if (this._isMounted) {
- this.initializePluginWithRetry(retryCount + 1);
- }
- }, RETRY_DELAY);
- } else {
- console.error('Max retries reached for plugin initialization');
- uni.showToast({
- title: '设备初始化失败,请重启应用',
- icon: 'none',
- duration: 3000
- });
- }
- } else {
- console.log('插件初始化成功');
- this.isDisable = false;
- uni.showToast({
- title: '设备初始化成功',
- icon: 'success',
- duration: 2000
- });
- }
- },
- /**
- * 获取栋舍列表(带重试机制)
- * @param {number} retryCount - 当前重试次数(默认0)
- */
- fetchBuildingList(retryCount = 0) {
- const MAX_RETRIES = 2; // 最大重试次数
-
- // 显示加载提示
- if (retryCount === 0) {
- uni.showLoading({ title: '加载栋舍列表...', mask: true });
- }
-
- //获取栋舍列表
- uni.request({
- url: API.getBuilding,
- method: 'GET',
- timeout: 10000, // 增加超时时间到10秒
- header: {
- 'content-type': 'application/x-www-form-urlencoded',
- "x-token": uni.getStorageSync('equipment_token') || ''
- },
- success: (res) => {
- // 隐藏加载提示
- if (retryCount === 0) {
- uni.hideLoading();
- }
- this.buildingList = res.data.data || [];
- },
- complete: () => {
- // 确保最终隐藏loading
- if (retryCount === MAX_RETRIES) {
- uni.hideLoading();
- }
- }
- });
- },
- /**
- * 加载已保存的设置
- */
- loadSavedSettings(settings = null) {
-
- const app = getApp();
- console.log('当前全局数据完整内容:', JSON.stringify(app.globalData));
-
- const buildingName = uni.getStorageSync('building') || '';
- const roomName = uni.getStorageSync('room') || '';
- const penNo = uni.getStorageSync('pen') || '';
- this.form.buildingName = buildingName;
- this.form.roomName = roomName;
- this.form.penNo = penNo;
- console.log('表单最终赋值结果', this.form.buildingName);
- console.log('表单最终赋值结果', this.form.roomName);
- console.log('表单最终赋值结果', this.form.penNo);
-
- // 如果栋舍不为空且房间列表为空,加载房间列表
- if (buildingName && this.roomList.length === 0) {
- this.fetchRoomList(buildingName);
- } else if (buildingName && this.roomList.length > 0 && roomName) {
- // 如果房间已变更,更新房间列表
- this.fetchRoomList(buildingName);
- }
-
- // 如果房间不为空且栏位列表为空,加载栏位列表
- if (roomName && this.Fieldnumber.length === 0) {
- this.fetchFieldList(roomName);
- } else if (roomName && this.Fieldnumber.length > 0 && penNo) {
- // 如果栏位已变更,更新栏位列表
- this.fetchFieldList(roomName);
- }
- },
-
- /**
- * 手动刷新全局数据
- */
- refreshGlobalData() {
- console.log('手动刷新全局数据');
-
- // 尝试从本地存储恢复最新数据到全局数据
- try {
- const app = getApp();
- const token = uni.getStorageSync('equipment_token') || '';
- const expireAt = uni.getStorageSync('token_expire_time') || '';
- const userInfo = uni.getStorageSync('user_info') || {};
- const building = uni.getStorageSync('building') || '';
- const room = uni.getStorageSync('room') || '';
- const pen = uni.getStorageSync('pen') || '';
-
- if (token && expireAt) {
- app.globalData.token = token;
- app.globalData.expireAt = expireAt;
- app.globalData.userInfo = userInfo;
- app.globalData.isLoggedIn = true;
- app.globalData.building = building;
- app.globalData.room = room;
- app.globalData.pen = pen;
- app.globalData.buildingName = building;
- app.globalData.roomName = room;
- app.globalData.penNo = pen;
- console.log('从本地存储恢复全局数据成功');
- }
- } catch (e) {
- console.error('从本地存储恢复全局数据失败:', e);
- }
-
- this.loadSavedSettings();
- // 刷新栋舍列表
- this.fetchBuildingList();
- },
-
- /**
- * 根据栋舍获取房间列表
- * @param {string} buildingName - 栋舍名称
- * @param {number} retryCount - 当前重试次数
- */
- fetchRoomList(buildingName, retryCount = 0) {
- // 显示加载提示
- if (retryCount === 0) {
- uni.showLoading({ title: '加载房间列表...', mask: true });
- }
-
- // 发送请求到API获取房间列表
- uni.request({
- url: API.getRoom,
- method: 'GET',
- data: { building: buildingName },
- timeout: 10000, // 增加超时时间到10秒
- header: {
- "x-token": uni.getStorageSync('equipment_token') || ''
- },
- success: (res) => {
- // 隐藏加载提示
- if (retryCount === 0) {
- uni.hideLoading();
- }
- // 更新房间列表
- this.roomList = res.data.data || [];
- // 重置房间和栏位选择
- this.form.roomName = '';
- this.Fieldnumber = [];
- this.form.penNo = '';
- }
- });
- },
-
- /**
- * 根据房间名称获取栏位列表
- * @param {string} roomName - 房间名称
- * @param {number} retryCount - 当前重试次数(默认0)
- */
- fetchFieldList(roomName, retryCount = 0) {
- // 显示加载提示
- if (retryCount === 0) {
- uni.showLoading({ title: '加载栏位列表...', mask: true });
- }
- const MAX_RETRIES = 1;
- const RETRY_DELAY = 2000; // 2秒
- // 发送请求到API获取栏位列表
- uni.request({
- url: API.getPen,
- method: 'GET',
- timeout: 10000, // 增加超时时间到10秒
- header: {
- "x-token": uni.getStorageSync('equipment_token') || ''
- },
- success: (res) => {
- // 隐藏加载提示
- if (retryCount === 0) {
- uni.hideLoading();
- }
- // 更新栏位列表
- this.Fieldnumber = res.data.data || [];
- // 重置栏位选择
- this.form.penNo = '';
- },
- fail: (err) => {
- // 隐藏加载提示
- if (retryCount === 0) {
- uni.hideLoading();
- }
- // 如果未达到最大重试次数,尝试重试
- if (retryCount < MAX_RETRIES) {
- console.log(`Retrying request (${retryCount + 1}/${MAX_RETRIES})`);
- setTimeout(() => {
- this.fetchFieldList(roomName, retryCount + 1);
- }, RETRY_DELAY);
- } else {
-
- }
- }
- });
- },
- /**
- * 初始化UHF插件实例
- * @returns {boolean} 初始化是否成功
- */
- initPluginInstance() {
- if (this.uhfSFHelper) {
- console.log('插件实例已存在');
- return true;
- }
-
- try {
- console.log('初始化UHF插件实例');
-
- // 检查运行环境是否支持原生插件
- if (typeof uni.requireNativePlugin !== 'function') {
- console.error('当前环境不支持uni.requireNativePlugin');
- return false;
- }
-
- // 尝试加载插件
- this.uhfSFHelper = uni.requireNativePlugin('Alvin-CBZUhfModule');
-
- // 验证插件实例是否有效
- if (!this.uhfSFHelper) {
- return false;
- }
-
- if (typeof this.uhfSFHelper.doInitDevice !== 'function') {
- //插件实例缺少必要方法: doInitDevice
- this.uhfSFHelper = null;
- return false;
- }
- console.log('UHF插件实例创建成功');
- return true;
- } catch (e) {
- console.error('加载UHF插件失败:', e.message);
- console.error('错误栈:', e.stack);
- this.uhfSFHelper = null;
- uni.showToast({
- title: '设备功能不可用: ' + e.message,
- icon: 'none',
- duration: 3000
- });
- return false;
- }
- },
-
- /**
- * 检查并恢复插件实例
- * @param {number} retryCount - 当前重试次数(默认0)
- * @returns {boolean} 插件实例是否有效
- */
- checkAndRestorePluginInstance(retryCount = 0) {
- // 设置最大重试次数和超时时间
- const MAX_RETRIES = 1;
- const RETRY_DELAY = 1000; // 1秒
-
- // 如果实例不存在,尝试初始化
- if (!this.uhfSFHelper) {
- console.log(`没有插件实例,尝试初始化 (重试: ${retryCount})`);
- const result = this.initPluginInstance();
-
- // 如果初始化失败且未达到最大重试次数,递归重试
- if (!result && retryCount < MAX_RETRIES) {
- console.log(`初始化失败,重试 (${retryCount + 1}/${MAX_RETRIES}) 后 ${RETRY_DELAY}ms`);
- // 延迟后重试
- setTimeout(() => {
- this.checkAndRestorePluginInstance(retryCount + 1);
- }, RETRY_DELAY);
- return false;
- }
-
- return result;
- }
-
- // 检查实例方法是否存在且有效
- const requiredMethods = ['doInitDevice', 'doStartScan', 'doReleaseDevice'];
- const missingMethods = requiredMethods.filter(method => typeof this.uhfSFHelper[method] !== 'function');
-
- if (missingMethods.length > 0) {
- console.error(`插件实例缺少必要方法: ${missingMethods.join(', ')}`);
- this.uhfSFHelper = null;
-
- // 如果未达到最大重试次数,尝试重新初始化
- if (retryCount < MAX_RETRIES) {
- console.log(`Attempting to reinitialize plugin (${retryCount + 1}/${MAX_RETRIES}) after ${RETRY_DELAY}ms`);
- setTimeout(() => {
- this.checkAndRestorePluginInstance(retryCount + 1);
- }, RETRY_DELAY);
- return false;
- }
-
- return false;
- }
-
- console.log('插件实例有效且准备使用');
- return true;
- },
-
- /**
- * 取消当前扫描操作
- */
- cancelScan() {
- // 清除所有相关计时器
- if (this.scanTimeout) {
- clearTimeout(this.scanTimeout);
- this.scanTimeout = null;
- }
-
- if (this.retryTimeout) {
- clearTimeout(this.retryTimeout);
- this.retryTimeout = null;
- }
-
- if (this.maxScanTimer) {
- clearTimeout(this.maxScanTimer);
- this.maxScanTimer = null;
- }
-
- // 隐藏加载提示
- uni.hideLoading();
-
- console.log('扫描已取消');
- },
-
- /**
- * 释放设备资源
- */
- releaseDevice() {
- if (this.isDeviceReady && this.uhfSFHelper) {
- try {
- this.uhfSFHelper.doReleaseDevice()
- } catch (e) {
- console.error('释放设备失败', e)
- }
- this.isDeviceReady = false
- this.isDisable = false
- }
- },
-
- /**
- * 初始化设备
- */
- init() {
- // 确保插件实例已初始化且有效
- if (!this.checkAndRestorePluginInstance()) {
- return;
- }
-
- if (this.isInitializing) {
- console.log('设备初始化已在进行中');
- return;
- }
-
- if (this.isDeviceReady) {
- console.log('设备已初始化');
- return uni.showToast({ title: '设备已开启', icon: 'none' });
- }
-
- this.isDisable = false;
- this.isInitializing = true;
-
- try {
- console.log('开始初始化设备');
- // 再次检查插件实例是否有效
- if (!this.checkAndRestorePluginInstance()) {
- this.isInitializing = false;
- return;
- }
-
- this.uhfSFHelper.doInitDevice(res => {
- this.isInitializing = false;
-
- if (res === true) {
- this.isDeviceReady = true;
- this.isDisable = true;
- console.log('Device initialized successfully');
- uni.showToast({ title: '设备已开启', icon: 'success' });
- } else {
- this.isDisable = false;
- console.error('设备初始化失败');
- uni.showToast({ title: '初始化失败', icon: 'none' });
- }
- });
- } catch (e) {
- this.isInitializing = false;
- console.error('Error during device initialization:', e);
- this.isDisable = false;
- // 清除插件实例,以便下次初始化尝试
- this.uhfSFHelper = null;
- uni.showToast({ title: '初始化异常', icon: 'none' });
- }
- },
-
- /**
- * 切换持续扫描状态
- */
- toggleContinuousScan() {
- // 确保插件实例已初始化且有效
- if (!this.checkAndRestorePluginInstance()) {
- return;
- }
-
- if (!this.isDeviceReady) {
- return uni.showToast({ title: '请先开启设备', icon: 'none' })
- }
-
- if (this.isContinuousScanning) {
- // 停止持续扫描
- this.isContinuousScanning = false;
-
- // 调用cancelScan方法清除所有计时器和加载提示
- this.cancelScan();
-
- // 清除持续扫描间隔计时器(双重保障)
- if (this.continuousScanInterval) {
- clearTimeout(this.continuousScanInterval);
- this.continuousScanInterval = null;
- }
-
- // 显示扫描停止提示
- uni.showToast({ title: '已停止扫描', icon: 'none' });
- } else {
- // 开始持续扫描
- this.isContinuousScanning = true;
-
- // 显示持续扫描提示
- uni.showLoading({
- title: '持续扫描中...',
- mask: true
- });
-
- // 开始持续扫描
- this.performContinuousScan();
- }
- },
-
- /**
- * 开始扫描耳标(单次)
- */
- scan() {
- // 确保插件实例已初始化且有效
- if (!this.checkAndRestorePluginInstance()) {
- return;
- }
-
- if (!this.isDeviceReady) {
- return uni.showToast({ title: '请先开启设备', icon: 'none' })
- }
-
- // 如果正在持续扫描,先停止
- if (this.isContinuousScanning) {
- this.toggleContinuousScan();
- }
-
- // 设置扫描参数 - 优化参数以提高成功率
- const scanConfig = {
- retryCount: 3, // 增加重试次数
- currentRetry: 0,
- timeout: 2000, // 增加超时时间
- interval: 400, // 增加间隔,给设备恢复时间
- signalThreshold: 0.5, // 降低信号阈值,接受更多信号
- continuous: false // 非持续扫描模式
- };
-
- // 初始化扫描进度变量
- this.scanProgress = 0;
- this.scanTotalAttempts = scanConfig.retryCount;
-
- // 显示扫描中提示,添加mask以禁止背景操作
- uni.showLoading({
- title: '正在扫描耳标...',
- mask: true
- });
-
- // 执行扫描函数
- this.performScan(scanConfig);
- },
-
- /**
- * 执行持续扫描
- */
- performContinuousScan() {
- // 检查是否仍在持续扫描状态
- if (!this.isContinuousScanning || !this._isMounted || !this.isDeviceReady) {
- if (this.isContinuousScanning) {
- uni.hideLoading();
- }
- return;
- }
-
- // 检查并恢复插件实例
- if (!this.checkAndRestorePluginInstance()) {
- uni.hideLoading();
- this.isContinuousScanning = false;
- return uni.showToast({ title: '设备功能异常,无法持续扫描', icon: 'none' });
- }
-
- // 设置持续扫描参数
- const scanConfig = {
- retryCount: 100, //重试次数
- currentRetry: 0,
- timeout: 1500, // 增加超时时间到1.5秒
- interval: 300, // 减少重试间隔到300ms
- signalThreshold: 0.3, // 降低信号阈值,提高扫描成功率
- continuous: true // 持续扫描模式
- };
-
- // 执行扫描
- this.performScan(scanConfig);
- },
-
- /**
- * 执行扫描(带重试机制)
- * @param {object} config - 扫描配置参数
- * @param {number} config.retryCount - 最大重试次数
- * @param {number} config.currentRetry - 当前重试次数
- * @param {number} config.timeout - 单次扫描超时时间(毫秒)
- * @param {number} config.interval - 重试间隔(毫秒)
- */
- /**
- * 处理类型选择变化
- */
- onTypeChange(e, index) {
- if (index >= 0 && index < this.dataList.length) {
- this.dataList[index].typeIndex = e.detail.value;
- console.log(`耳标 ${this.dataList[index].id} 类型变更为: ${this.types[e.detail.value]}`);
- }
- },
- /**
- * 删除耳标条目
- */
- deleteItem(index) {
- if (index >= 0 && index < this.dataList.length) {
- const deletedItem = this.dataList.splice(index, 1);
- console.log(`删除耳标: ${deletedItem[0].id}`);
- uni.showToast({ title: '删除成功', icon: 'success' });
- }
- },
- performScan(config) {
- // 检查组件是否已卸载或设备是否已准备好
- if (!this._isMounted || !this.isDeviceReady) {
- uni.hideLoading();
- console.log('Scan aborted: component not mounted or device not ready');
- return;
- }
-
- // 非持续扫描模式下才检查最大重试次数
- if (!config.continuous && config.currentRetry >= config.retryCount) {
- uni.hideLoading();
- console.log('Scan failed after maximum retries');
- return uni.showToast({ title: '扫描失败,请调整位置重试', icon: 'none' });
- }
-
- // 非持续扫描模式下才添加最大扫描时间限制
- if (!config.continuous) {
- const maxScanTime = config.timeout * config.retryCount;
- if (!this.maxScanTimer) {
- this.maxScanTimer = setTimeout(() => {
- console.log('Maximum scan time exceeded');
- this.cancelScan();
- }, maxScanTime);
- }
- }
-
- // 增加重试计数
- config.currentRetry++;
- this.scanProgress = config.currentRetry;
-
- // 持续扫描模式下不更新加载提示,以避免干扰停止操作
- if (!config.continuous && (config.currentRetry % 2 === 0 || config.currentRetry === config.retryCount)) {
- uni.hideLoading();
- uni.showLoading({
- title: `扫描中 (${Math.round(config.currentRetry/config.retryCount*100)}%)...`,
- mask: true
- });
- }
-
- console.log(`开始扫描尝试 ${config.currentRetry}/${config.retryCount},超时时间: ${config.timeout}ms`);
-
- // 清除之前的超时
- if (this.scanTimeout) {
- clearTimeout(this.scanTimeout);
- this.scanTimeout = null;
- }
-
- // 设置超时机制
- this.scanTimeout = setTimeout(() => {
- console.log(`Scan timeout, retrying (${config.currentRetry}/${config.retryCount})`);
- // 重试扫描前先检查实例
- if (this._isMounted) {
- // 检查并恢复插件实例
- if (this.checkAndRestorePluginInstance()) {
- this.retryTimeout = setTimeout(() => this.performScan(config), config.interval);
- } else {
- console.error('Failed to restore plugin instance before retry');
- uni.hideLoading();
- return uni.showToast({ title: '设备功能异常,无法重试', icon: 'none' });
- }
- }
- }, config.timeout);
-
- // 执行扫描
- try {
- // 清除之前可能存在的重试计时器
- if (this.retryTimeout) {
- clearTimeout(this.retryTimeout);
- this.retryTimeout = null;
- }
- // 检查插件实例是否有效
- if (!this.checkAndRestorePluginInstance()) {
- // 清除超时计时器
- if (this.scanTimeout) {
- clearTimeout(this.scanTimeout);
- this.scanTimeout = null;
- }
-
- console.error('Invalid plugin instance for scanning after restore attempt');
- // 尝试重新初始化设备
- this.init(); // 修复:使用init而不是不存在的initDevice方法
- uni.hideLoading();
- return uni.showToast({ title: '设备功能异常,正在重新初始化', icon: 'none' });
- }
-
- // 保存当前实例引用,防止闭包中实例变化
- const currentPluginInstance = this.uhfSFHelper;
-
- // 执行扫描命令
- try {
- currentPluginInstance.doStartScan(result => {
- // 再次检查插件实例是否与执行扫描时相同
- if (this.uhfSFHelper !== currentPluginInstance) {
- console.warn('Plugin instance changed during scan, ignoring result');
- // 尝试重新扫描
- if (this._isMounted) {
- this.retryTimeout = setTimeout(() => this.performScan(config), config.interval);
- }
- return;
- }
-
- // 检查组件是否已卸载
- if (!this._isMounted) return;
-
- // 清除超时计时器
- if (this.scanTimeout) {
- clearTimeout(this.scanTimeout);
- this.scanTimeout = null;
- }
-
- if (result) {
- // 如果result只是ID字符串,将其包装成对象
- const scanResult = typeof result === 'string' ? { id: result, signalStrength: 1 } : result;
-
- // 检查信号强度是否足够
- if (scanResult.signalStrength >= config.signalThreshold) {
- // 清除所有计时器
- if (this.maxScanTimer) {
- clearTimeout(this.maxScanTimer);
- this.maxScanTimer = null;
- }
- if (this.retryTimeout) {
- clearTimeout(this.retryTimeout);
- this.retryTimeout = null;
- }
-
- if (!config.continuous) {
- uni.hideLoading();
- }
- console.log('扫描成功:', scanResult);
- this.form.earId = scanResult.id;
-
- // 检查是否重复扫描
- const isDuplicate = this.dataList.some(item => item.id === scanResult.id);
-
- if (isDuplicate) {
- console.log('耳标已存在:', scanResult.id);
- // 持续扫描模式下也显示重复提示,但持续时间较短
- uni.showToast({
- title: '该耳标已扫描过',
- icon: 'none',
- duration: config.continuous ? 500 : 2000
- });
- } else {
- // 默认添加为'正常'类型
- this.dataList.push({ id: scanResult.id, typeIndex: 0 });
- if (!config.continuous) {
- uni.showToast({ title: '扫描成功', icon: 'success' });
- } else {
- // 持续扫描模式下,短暂提示后继续
- uni.showToast({ title: '扫描到耳标', icon: 'success', duration: 500 });
- }
- }
-
- // 持续扫描模式下,确保继续扫描
- if (config.continuous && this.isContinuousScanning) {
- // 清除可能存在的旧计时器
- if (this.continuousScanInterval) {
- clearTimeout(this.continuousScanInterval);
- this.continuousScanInterval = null;
- }
- // 设置新的计时器,确保持续扫描
- this.continuousScanInterval = setTimeout(() => {
- // 再次检查持续扫描状态
- if (this.isContinuousScanning && this.isDeviceReady) {
- this.performContinuousScan();
- }
- }, 500); // 短暂延迟后继续扫描
- }
- } else {
- console.log(`Scan result with weak signal (${scanResult.signalStrength}), retrying`);
- // 信号太弱,继续重试前检查实例
- if (this._isMounted) {
- if (this.checkAndRestorePluginInstance()) {
- this.retryTimeout = setTimeout(() => this.performScan(config), config.interval);
- } else {
- console.error('Failed to restore plugin instance for retry');
- uni.hideLoading();
- return uni.showToast({ title: '设备功能异常', icon: 'none' });
- }
- }
- }
- } else {
- console.log(`Scan failed, retrying (${config.currentRetry}/${config.retryCount})`);
- // 重试扫描前检查实例
- if (this._isMounted) {
- if (this.checkAndRestorePluginInstance()) {
- this.retryTimeout = setTimeout(() => this.performScan(config), config.interval);
- } else {
- console.error('Failed to restore plugin instance for retry');
- uni.hideLoading();
- return uni.showToast({ title: '设备功能异常', icon: 'none' });
- }
- }
- }
- });
- } catch (e) {
- console.error('Exception during scan execution:', e);
- // 清除超时计时器
- if (this.scanTimeout) {
- clearTimeout(this.scanTimeout);
- this.scanTimeout = null;
- }
-
- // 检查是否是实例不可用错误,增强检测逻辑
- if (e.message && (
- e.message.includes('instance is not available') ||
- e.message.includes('receiveTasks') ||
- e.message.includes('Failed to receiveTasks')
- )) {
- console.error('Plugin instance not available during scan');
- // 尝试重新初始化
- this.uhfSFHelper = null;
- if (this.checkAndRestorePluginInstance()) {
- // 重新执行扫描
- if (this._isMounted) {
- this.retryTimeout = setTimeout(() => this.performScan(config), config.interval * 2);
- }
- } else {
- uni.hideLoading();
- return uni.showToast({ title: '设备功能异常,无法扫描', icon: 'none' });
- }
- } else {
- // 其他错误
- if (this._isMounted) {
- this.retryTimeout = setTimeout(() => this.performScan(config), config.interval);
- }
- }
- }
- } catch (e) {
- // 清除超时计时器
- if (this.scanTimeout) {
- clearTimeout(this.scanTimeout);
- this.scanTimeout = null;
- }
-
- console.error('Error during scan setup:', e);
- // 检查错误是否与实例不可用相关,增强检测逻辑
- if (e.message && (
- e.message.includes('instance is not available') ||
- e.message.includes('receiveTasks') ||
- e.message.includes('Failed to receiveTasks')
- )) {
- console.error('Plugin instance not available during scan setup');
- // 清除当前实例
- this.uhfSFHelper = null;
- // 尝试重新初始化
- if (this.checkAndRestorePluginInstance()) {
- // 重新执行扫描
- if (this._isMounted) {
- this.retryTimeout = setTimeout(() => this.performScan(config), config.interval * 2); // 增加间隔
- }
- } else {
- uni.hideLoading();
- return uni.showToast({ title: '设备功能异常', icon: 'none' });
- }
- } else {
- // 其他错误,继续重试
- if (this._isMounted) {
- this.retryTimeout = setTimeout(() => this.performScan(config), config.interval);
- }
- }
- }
- },
-
- /**
- * 提交表单数据
- */
- submitForm() {
- // 在验证前先尝试重新加载设置
- this.loadSavedSettings();
- console.log('提交表单数据:', this.form);
-
- // 验证编号是否已选择
- let missingField = '';
- if (!this.form.buildingName) missingField = '栋舍';
- else if (!this.form.roomName) missingField = '房间';
- else if (!this.form.penNo) missingField = '栏位';
- if (missingField) {
- return uni.showToast({ title: `未选择${missingField}编号`, icon: 'none', duration: 3000 })
- }
- // 验证耳标是否已扫描
- if (this.dataList.length === 0) {
- return uni.showToast({ title: '请先扫描耳标', icon: 'none', duration: 3000 })
- }
- // 验证用户ID是否存在
- const app = getApp();
- const userInfo = app.globalData.userInfo;
- console.log('用户信息:', userInfo);
- // 更安全地获取用户ID,增加多重检查
- const userId = userInfo.ID;
- if (!userId) {
- return uni.showToast({ title: '用户未登录', icon: 'none', duration: 3000 })
- }
- uni.showLoading({ title: '提交中...', mask: true });
- const rfidString = this.dataList.map(item => {
- return `${item.id}:${this.types[item.typeIndex]}`;
- }).join(',');
- // 获取设备信息
- let deviceInfo = {};
- try {
- deviceInfo = uni.getSystemInfoSync();
- } catch (e) {
- console.error('获取设备信息失败:', e);
- }
- // 准备提交数据
- const submitData = {
- token: uni.getStorageSync('equipment_token'),
- rfid: rfidString,
- buildingName: this.form.buildingName,
- roomName: this.form.roomName,
- penNo: this.form.penNo,
- userId: userId,
- username: userInfo.username || '',
- time: new Date().toISOString(),
- deviceModel: deviceInfo.model || '未获取到设备型号', // 设备型号
- deviceVersion: deviceInfo.system || '未获取到设备版本号' // 设备版本号
- };
- // 发送请求到API
- this.submitData(submitData);
- },
-
- /**
- * 提交数据
- * @param {object} data - 要提交的数据
- */
- submitData(data) {
- console.log('token',uni.getStorageSync('equipment_token'))
- console.log('提交的数据',data)
- // 设置独立的提交加载状态标志
- this.isSubmitting = true;
-
- uni.request({
- url: API.postListAdd,
- method: 'POST',
- data: data,
- header: {
- 'content-type': 'application/json',
- 'x-token': uni.getStorageSync('equipment_token') // 将token添加到请求头中
- },
- timeout: 10000, // 设置10秒超时
- success: (res) => {
- console.log('API响应:', res);
-
- // 先隐藏加载提示
- if (this.isSubmitting) {
- uni.hideLoading();
- this.isSubmitting = false;
- }
-
- if (res.data) {
- console.log('服务器返回数据:', res.data);
- const message = res.data.msg || '提交成功';
- console.log('显示提示:', message);
-
- uni.showToast({
- title: message,
- icon: res.data.code === 0 ? 'success' : 'none',
- duration: 3000
- });
-
- if (res.data.code === 0) {
- // 添加本地记录
- this.records.unshift({
- rfid: data.rfid,
- building: data.buildingName,
- roomName: data.roomName,
- pen: data.penNo,
- userId: data.userId,
- time: new Date().toLocaleTimeString()
- });
-
- // 停止持续扫描
- if (this.isContinuousScanning) {
- this.toggleContinuousScan();
- }
-
- // 清空扫描数据
- this.resetForm();
- }
- } else {
- console.error('提交失败: 响应数据格式不正确', res);
- // 显示错误提示
- uni.showToast({
- title: '提交失败,请重试',
- icon: 'none',
- duration: 3000
- });
- }
- },
- fail: (err) => {
- console.error('网络请求失败:', err);
- // 隐藏加载提示并显示错误提示
- if (this.isSubmitting) {
- uni.hideLoading();
- this.isSubmitting = false;
- }
- uni.showToast({
- title: '网络请求失败,请重试',
- icon: 'none',
- duration: 3000
- });
- // 接口失败不影响耳标扫描,无需额外处理
- },
- complete: () => {
- // 确保加载提示被隐藏
- setTimeout(() => {
- if (this.isSubmitting) {
- uni.hideLoading();
- this.isSubmitting = false;
- }
- }, 2000);
- }
- });
- },
-
- // 重置
- resetForm() {
- this.form.earId = ''
- this.form.buildingName = ''
- this.form.roomName = ''
- this.form.penNo = ''
- this.form.status = 'healthy'
- this.form.note = ''
- // 清除扫描结果列表
- this.dataList = []
- },
- // 选择
- onBuildingChange(e) {
- const buildingName = this.buildingList[e.detail.value];
- this.form.buildingName = buildingName;
- // 根据选择的栋舍加载房间列表
- if (buildingName) {
- this.fetchRoomList(buildingName);
- } else {
- // 清空房间和栏位列表
- this.roomList = [];
- this.Fieldnumber = [];
- this.form.roomName = '';
- this.form.penNo = '';
- }
- },
-
- onRoomChange(e) {
- const roomName = this.roomList[e.detail.value];
- this.form.roomName = roomName;
- // 根据选择的房间加载栏位列表
- if (roomName) {
- this.fetchFieldList(roomName);
- } else {
- // 清空栏位列表
- this.Fieldnumber = [];
- this.form.penNo = '';
- }
- },
-
- onFieldChange(e) {
- this.form.penNo = this.Fieldnumber[e.detail.value]
- },
-
- // 其他
- logout() {
- // 清空数据
- this.resetForm();
-
- // 释放设备资源
- this.releaseDevice();
-
- // 清除全局用户信息和登录状态
- const app = getApp();
- app.globalData.userInfo = null;
- app.globalData.token = '';
- app.globalData.expireAt = '';
- app.globalData.isLoggedIn = false;
- app.globalData.building = '';
- app.globalData.room = '';
- app.globalData.pen = '';
- app.globalData.buildingName = '';
- app.globalData.roomName = '';
- app.globalData.penNo = '';
-
- // 清除本地缓存中的所有登录相关信息
- try {
- uni.removeStorageSync('user_info');
- uni.removeStorageSync('equipment_token');
- uni.removeStorageSync('token_expire_time');
- // 可选:清除编号相关的缓存
- // uni.removeStorageSync('building');
- // uni.removeStorageSync('room');
- // uni.removeStorageSync('pen');
- // uni.removeStorageSync('buildingName');
- // uni.removeStorageSync('roomName');
- // uni.removeStorageSync('penNo');
- } catch (e) {
- console.error('清除登录信息失败:', e);
- }
- },
-
- toggleServerConfig() {
- this.showServerConfig = !this.showServerConfig
- }
- }
- }
- </script>
- <style>
- .page { display: flex; flex-direction: column; height: 100vh; width: 100%; box-sizing: border-box; padding: 0 10rpx; }
- .data-item { display: flex; justify-content: space-between; align-items: center; padding: 10rpx; margin-bottom: 10rpx; background-color: #f5f5f5; border-radius: 5rpx; }
- .picker { padding: 5rpx 10rpx; margin-left: 20px;background-color: #fff; border: 1px solid #ddd; border-radius: 5rpx; min-width: 120rpx; text-align: center; }
- .arrow::after { content: '▾'; font-size: 12rpx; margin-left: 5rpx; }
- .delete-btn { background-color: #ff4d4f; color: white; font-size: 24rpx; padding: 5rpx 10rpx; min-width: 80rpx; line-height: normal; }
- .nav-bar {display: flex; justify-content: space-between; align-items: center;background: #007aff; color: white; padding: 15rpx 30rpx;}
- .nav-title { font-weight: bold; font-size: 32rpx; }
- .nav-btn { background: transparent; border: none; font-size: 32rpx; }
- .scroll-area { flex: 1; }
- .section-title { display: flex; justify-content: space-between; margin-bottom: 20rpx; }
- .section-title .title { font-weight: bold; font-size: 30rpx; }
- .section-title .date { font-size: 24rpx; color: #666; }
- .rfid-card {background: #f6f6f6; border: 2rpx dashed #ccc; border-radius: 20rpx; text-align: center; }
- .scan-icon { font-size: 50rpx; color: #007aff; margin-bottom: 10rpx; display: block; }
- .btn-group { display: flex; gap: 20rpx; margin: 20rpx 0; flex-wrap: wrap; justify-content: center; }
- .scan-btn { background: #007aff; color: white; flex: 1; min-width: 200rpx; padding: 20rpx; border-radius: 12rpx; font-size: 28rpx; }
- .manual-btn { background: #ccc; color: black; flex: 1; min-width: 200rpx; padding: 20rpx; border-radius: 12rpx; font-size: 28rpx; }
- .form-item { margin-bottom: 20rpx; }
- .form-item .label { display: block; font-weight: bold; margin-bottom: 10rpx; }
- .input-box { width: 100%; border: 1rpx solid #ccc; border-radius: 10rpx; background: #fff; }
- .status-options { display: flex; gap: 20rpx; margin-top: 10rpx; }
- .status-option {flex: 1; border: 1rpx solid #ccc; border-radius: 10rpx;padding: 20rpx; text-align: center; color: #666;}
- .status-option.active { border-color: #007aff; color: #007aff; }
- .sv {height: 300rpx;margin-top: 20rpx;}
- </style>
|