You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1403 lines
35 KiB

2 weeks ago
<template>
<scroll-view class="index-bg" scroll-y @scrolltolower="loadMorePlans" lower-threshold="100"
@refresherrefresh="refreshPlans" refresher-enabled="true" :refresher-triggered="planLoading">
<view class="index-content">
<view class="btn-group">
<button class="main-btn" @click="scanInventory"></button>
<button class="main-btn outline" @click="scanView"></button>
</view>
<!-- 标签盘点功能区域 -->
<view class="inventory-section">
<view class="inventory-header">
<text class="inventory-title">标签盘点</text>
<text class="tags-amount">标签数量: {{tagsAmount}}</text>
</view>
<!-- 标签列表 -->
<scroll-view class="tag-list" scroll-y @scrolltolower="loadmore" lower-threshold="50">
<view class="list-item-head">
<text class="list-item-text-id">序号</text>
<text class="list-item-text-epc">标签信息</text>
<text class="list-item-text-count">次数</text>
<text class="list-item-text-rssi">信号强度</text>
</view>
<view class="list-item" v-for="item in dataList" :key="item.id">
<text class="list-item-text-id">{{item.id+1}}</text>
<text class="list-item-text-epc">{{item.epc}}</text>
<text class="list-item-text-count">{{item.count}}</text>
<text class="list-item-text-rssi">{{item.rssi}}</text>
</view>
</scroll-view>
<!-- 设置选项 -->
<view class="inventory-options">
<!-- <checkbox-group @change="onAsyncChange" class="option-item">
<label class="checkbox-label">
<checkbox value="async_checkbox" :disabled="cbDisabled" />
<text class="option-text">异步</text>
</label>
</checkbox-group> -->
<!-- <checkbox-group @change="onVoiceChange" class="option-item">
<label class="checkbox-label">
<checkbox value="voice_checkbox" :disabled="cbDisabled" :checked="voiceFlag" />
<text class="option-text">声音</text>
</label>
</checkbox-group> -->
<!-- #ifdef APP-PLUS -->
<!-- <checkbox-group @change="onBarcodeChange" class="option-item">
<label class="checkbox-label">
<checkbox value="barcode_checkbox" :disabled="cbDisabled" :checked="barcodeFlag" />
<text class="option-text">手柄扫码</text>
</label>
</checkbox-group> -->
<!-- #endif -->
</view>
<!-- 操作按钮 -->
<view class="inventory-btn-box">
<button type="primary" @click="handleInventory" class="inventory-btn">{{btn1Info}}</button>
<button type="primary" :disabled="btn2Disabled" @click="viewMaterials" class="inventory-btn">查看物资</button>
</view>
<view>
<button type="primary" :disabled="btn3Disabled" @click="clearTags" class="inventory-btn clear-btn">清空</button>
</view>
</view>
<view class="task-section">
<view class="task-title">物资列表</view>
<!-- 表头 -->
<view class="task-list">
<view class="task-item" style="font-weight:600;">
<view class="task-info">
<text class="task-name">物资名称</text>
</view>
<view class="task-info" style="flex-direction: row; align-items: center; gap: 8rpx;">
<text class="task-time">库存</text>
</view>
<view class="task-info" style="width: 120rpx; text-align: right;">
<text class="task-time">操作</text>
</view>
</view>
<!-- 数据行 -->
<view class="task-item" v-for="(item, idx) in taskList" :key="idx">
<view class="task-info">
<text class="task-name">{{item.zichanmingcheng}}</text>
</view>
<view class="task-info" style="flex-direction: row; align-items: center; gap: 8rpx;">
<text class="task-time">{{item.total_num}}</text>
</view>
<view class="task-info" style="width: 120rpx; display:flex; justify-content:flex-end;">
<button size="mini" type="primary" @click.stop="goInventoryFromList(item)">盘点</button>
</view>
</view>
</view>
</view>
<!-- 盘点计划 -->
<view class="inventory-plan-section">
<view class="inventory-plan-header">
<text class="inventory-plan-title">盘点计划</text>
<!-- <text class="plan-total">总计: {{planTotal}}</text> -->
</view>
<!-- 表头 -->
<view class="plan-list">
<view class="plan-item" style="font-weight:600;">
<view class="plan-info plan-name-col">
<text class="plan-name">计划名称</text>
</view>
<view class="plan-info plan-status-col">
<text class="plan-time">状态</text>
</view>
<view class="plan-info plan-date-col">
<text class="plan-time">开始日期</text>
</view>
<view class="plan-info plan-date-col">
<text class="plan-time">结束日期</text>
</view>
<view class="plan-info plan-action-col">
<text class="plan-time">操作</text>
</view>
</view>
<!-- 数据行 -->
<view class="plan-item" v-for="item in inventoryPlanList" :key="item.id">
<view class="plan-info plan-name-col">
<text class="plan-name">{{item.name}}</text>
<!-- <text class="plan-no">{{item.no || '暂无'}}</text> -->
</view>
<view class="plan-info plan-status-col">
<text class="plan-status" :class="'status-' + item.status">{{getStatusText(item.status)}}</text>
</view>
<view class="plan-info plan-date-col">
<text class="plan-time">{{formatDate(item.start_date)}}</text>
</view>
<view class="plan-info plan-date-col">
<text class="plan-time">{{formatDate(item.end_date)}}</text>
</view>
<view class="plan-info plan-action-col">
<button size="mini" type="primary" @click.stop="viewPlanDetail(item)">查看</button>
</view>
</view>
</view>
<!-- 加载更多提示 -->
<view class="load-more" v-if="planLoading">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<view class="load-more" v-else-if="!planHasMore && inventoryPlanList.length > 0">
<text class="no-more-text"> 没有更多数据了 </text>
</view>
</view>
<!-- H5扫码弹窗 -->
<div v-if="showH5Scan" class="h5-scan-modal">
<div id="reader" style="width:300px;height:300px;margin:0 auto;"></div>
<button @click="closeH5Scan"></button>
</div>
</view>
</scroll-view>
</template>
<script>
let keyDownTime = 1
let runtimeBarcodeFlag = false
let runtimeAsyncFlag = false
let runtimeVoiceFlag = true
import { getInventoryTaskList,getInventoryPlanList } from '@/api.js';
// #ifdef H5
import { Html5Qrcode, Html5QrcodeSupportedFormats } from 'html5-qrcode';
// #endif
export default {
data() {
return {
currentDate: '',
taskList: [],
showH5Scan: false,
html5QrCode: null,
scanType: '', // 'inventory' or 'view'
// 标签盘点相关数据
btn1Info: "开始盘点",
tagsAmount: 0, //读取到的标签总数
btn2Disabled: false, //禁用按键
btn3Disabled: false, //禁用按键
cbDisabled: false, //是否禁用
dataList: [], // list展示的标签列表首次最多加载offset个待上拉加载更多时加载tempList中的数据
pageNum: 100, // 每页加载数量
offset: 50, // 第一页加载的数量/已加载的数量
epcList: [], // 过滤所用列表只存标签的EPC信息
tempList: [], // 标签缓存列表,缓存标签信息,等待上拉至列表底部时,按页加载缓存列表中的数据
asyncFlag: false, // 异步盘点标志
voiceFlag: true, // 声音播放标志
barcodeFlag: false, // 二维码扫描标志
main: null, // Android主Activity
hhwUHFController: null, // UHF控制器
globalEvent: null, // 事件监听
receiver: null, // Android广播接收器
inventoryPlanList: [], // 盘点计划列表
planPage: 1, // 盘点计划当前页码
planPageSize: 5, // 盘点计划每页数量
planTotal: 0, // 盘点计划总数
planLoading: false, // 盘点计划加载状态
planHasMore: true // 是否还有更多数据
}
},
created() {
runtimeAsyncFlag = this.asyncFlag
runtimeVoiceFlag = this.voiceFlag
runtimeBarcodeFlag = this.barcodeFlag
},
onLoad() {
this.updateDate()
// #ifdef APP-PLUS
this.initAndroidComponents()
// #endif
this.initUHFEvent()
// 加载盘点计划列表
this.getInventoryPlanList(true)
},
onShow() {
// #ifdef APP-PLUS
console.log("inventory Show")
// 初始化二维码扫描,以防扫描服务处于关闭状态,而无法调用扫描
this.initBarcodeScan()
// 屏蔽二维码扫描扳机以便app可以自定义触发
this.disableBarcodeScanKey()
// 监听功能按键,触发扫描
this.registerKeyReceiver()
// #endif
},
onHide() {
// #ifdef APP-PLUS
console.log("inventory Hide")
// 注销按键监听
if (this.main && this.receiver) {
this.main.unregisterReceiver(this.receiver)
}
// #endif
},
methods: {
updateDate() {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
this.currentDate = `${year}-${month}-${day}`
},
handleScan(type) {
this.scanType = type;
// #ifdef H5
// 先请求摄像头权限
navigator.mediaDevices.getUserMedia({
video: {
facingMode: "environment",
width: { ideal: 1280 },
height: { ideal: 720 }
}
})
.then(() => {
this.showH5Scan = true;
this.$nextTick(() => {
if (!window.Html5Qrcode) {
window.Html5Qrcode = Html5Qrcode;
}
const config = {
fps: 10,
qrbox: { width: 250, height: 250 },
aspectRatio: 1.0,
formatsToSupport: [ Html5QrcodeSupportedFormats.QR_CODE ]
};
this.html5QrCode = new window.Html5Qrcode("reader");
this.html5QrCode.start(
{ facingMode: "environment" },
config,
qrCodeMessage => {
this.closeH5Scan();
// 直接使用扫码结果作为 id
let id = qrCodeMessage.trim();
console.log("id:", id)
// 规范化:允许类似 3715.000 的形式,转换为整数部分字符串
id = this.normalizeScannedId(id)
if (!id) {
uni.showToast({ title: '二维码无效', icon: 'none' });
return;
}
// 验证是否为数字(包括字符串类型的数字)
if (!/^\d+$/.test(id)) {
uni.showToast({ title: '二维码信息错误', icon: 'none' });
return;
}
if (this.scanType === 'inventory') {
uni.navigateTo({ url: `/pages/inventory/inventory?code=${encodeURIComponent(id)}` });
} else {
uni.navigateTo({ url: `/pages/inventory/inventory?code=${encodeURIComponent(id)}&view=1` });
}
},
errorMessage => {
console.log('扫码错误:', errorMessage);
}
).catch(err => {
console.error('启动扫码失败:', err);
uni.showToast({
title: '启动扫码失败,请检查摄像头权限',
icon: 'none',
duration: 2000
});
this.closeH5Scan();
});
});
})
.catch(err => {
console.error('摄像头权限错误:', err);
uni.showModal({
title: '提示',
content: '请允许访问摄像头以使用扫码功能',
confirmText: '确定',
showCancel: false,
success: () => {
// 引导用户去设置页面开启权限
if (uni.getSystemInfoSync().platform === 'android') {
uni.showToast({
title: '请在系统设置中开启摄像头权限',
icon: 'none',
duration: 2000
});
}
}
});
});
// #endif
// #ifndef H5
uni.scanCode({
success: (res) => {
// 直接使用扫码结果作为 id
let id = res.result.trim();
console.log("id2:", id)
// 规范化:允许类似 3715.000 的形式,转换为整数部分字符串
id = this.normalizeScannedId(id)
if (!id) {
uni.showToast({ title: '二维码无效', icon: 'none' });
return;
}
// 验证是否为数字(包括字符串类型的数字)
if (!/^\d+$/.test(id)) {
uni.showToast({ title: '二维码信息错误', icon: 'none' });
return;
}
if (type === 'inventory') {
uni.navigateTo({ url: `/pages/inventory/inventory?code=${encodeURIComponent(id)}` });
} else {
uni.navigateTo({ url: `/pages/inventory/inventory?code=${encodeURIComponent(id)}&view=1` });
}
},
fail: () => {
uni.showToast({ title: '扫码失败', icon: 'none' });
}
});
// #endif
},
scanInventory() {
this.handleScan('inventory');
},
scanView() {
this.handleScan('view');
},
closeH5Scan() {
this.showH5Scan = false;
if (this.html5QrCode) {
this.html5QrCode.stop().then(() => {
this.html5QrCode.clear();
});
}
},
// 获取盘点计划列表
async getInventoryPlanList(isRefresh = false) {
if (this.planLoading) return;
try {
this.planLoading = true;
// 如果是刷新,重置页码
if (isRefresh) {
this.planPage = 1;
this.planHasMore = true;
}
const params = {
page: this.planPage,
page_size: this.planPageSize
};
const res = await getInventoryPlanList(params);
console.log("盘点计划列表响应:", res);
if(res.data && res.data.errcode===40001){
uni.showToast({
title: res.data?.errmsg || '获取盘点计划失败',
icon: 'none'
});
uni.reLaunch({
url: '/pages/login/login'
});
return;
}
if (res.data && res.data.list) {
const data = res.data.list;
const newList = data.data || [];
if (isRefresh) {
this.inventoryPlanList = newList;
} else {
this.inventoryPlanList = [...this.inventoryPlanList, ...newList];
}
this.planTotal = data.total || 0;
this.planHasMore = newList.length === this.planPageSize;
if (this.planHasMore) {
this.planPage++;
}
} else {
uni.showToast({
title: res.data?.message || '获取盘点计划失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取盘点计划列表失败:', error);
uni.showToast({
title: '获取盘点计划失败',
icon: 'none'
});
} finally {
this.planLoading = false;
}
},
// 刷新盘点计划列表
refreshPlans() {
this.getInventoryPlanList(true);
},
// 加载更多盘点计划
loadMorePlans() {
if (!this.planHasMore || this.planLoading) return;
this.getInventoryPlanList(false);
},
// 获取状态文本
getStatusText(status) {
const statusMap = {
0: '未开始',
1: '进行中',
2: '已完成'
};
return statusMap[status] || '未知状态';
},
// 获取盘点类型文本
getTypeText(type) {
const typeMap = {
1: '年度',
2: '季度'
};
return typeMap[type] || '未知类型';
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
// 查看计划详情
viewPlanDetail(item) {
console.log('查看计划详情:', item);
// 将计划信息作为参数传递
const planInfoStr = encodeURIComponent(JSON.stringify(item));
uni.navigateTo({
url: `/pages/plan-detail/plan-detail?planInfo=${planInfoStr}&planId=${item.id}`
});
},
// 计算计划进度
getProgress(item) {
if (item.status === 2) return 100; // 已完成
if (item.status === 0) return 0; // 未开始
// 进行中的计划,根据时间计算进度
const now = new Date();
const startDate = new Date(item.start_date);
const endDate = new Date(item.end_date);
if (now < startDate) return 0;
if (now > endDate) return 100;
const totalTime = endDate.getTime() - startDate.getTime();
const passedTime = now.getTime() - startDate.getTime();
return Math.round((passedTime / totalTime) * 100);
},
// 初始化Android组件
initAndroidComponents() {
// #ifdef APP-PLUS
try {
this.main = plus.android.runtimeMainActivity()
// UHF控制器在App.vue中初始化
this.hhwUHFController = getApp().globalData.hhwUHFController
// 事件监听在App.vue中初始化
this.globalEvent = getApp().globalData.globalEvent
} catch (e) {
console.error('初始化Android组件失败:', e)
}
// #endif
},
// 初始化UHF事件监听
initUHFEvent() {
// #ifdef APP-PLUS
if (this.globalEvent) {
this.globalEvent.addEventListener('uhf_tag_event', (e) => {
this.handleUHFEvent(e)
})
}
// #endif
},
// 处理UHF事件
handleUHFEvent(e) {
console.log(e.tag_info_list)
var result = e.tag_info_list
if (result == null) {
// 接收到停止盘点的回调消息
var event = e.inventory_event
if (event == "stopInventory") {
uni.showToast({
title: "停止盘点",
icon: "none"
})
this.btn2Disabled = false
this.btn3Disabled = false
this.cbDisabled = false
this.btn1Info = "开始盘点"
}
return
}
// 接收盘点到的标签信息
for (var i = 0; i < result.length; i++) {
var id = i
var epcHex = this.bytes2HexString(result[i].EpcId)
var epc = this.hexToString(epcHex)
var rssi = result[i].RSSI
var tag = {
id: id,
epc: epc,
count: 1,
rssi: rssi,
}
var index = this.epcList.indexOf(epc)
if (index == -1) {
tag.id = this.epcList.length
if (this.dataList.length < this.offset) {
this.dataList.push(tag)
}
this.tempList.push(tag)
this.epcList.push(epc)
} else {
tag.id = index
tag.count = this.tempList[index].count + 1
if (index < this.dataList.length) {
this.$set(this.dataList, index, tag)
}
this.$set(this.tempList, index, tag)
}
}
this.tagsAmount = this.epcList.length
},
// 注册按键接收器
registerKeyReceiver() {
// #ifdef APP-PLUS
if (!this.main) return
var IntentFilter = plus.android.importClass('android.content.IntentFilter')
var filter = new IntentFilter()
filter.addAction("android.rfid.FUN_KEY")
this.receiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', {
onReceive: (context, intent) => {
plus.android.importClass(intent)
var code = intent.getIntExtra("keyCode", 0)
var keyDown = intent.getBooleanExtra("keydown", false)
if (keyDown && keyDownTime == 1 && code == 137) {
console.log("inventory", "receive keyUp code: " + code)
if (runtimeBarcodeFlag) {
// 开始扫描
this.startBarcodeScan()
} else {
// 开始超高频
this.startInventory()
}
keyDownTime++
} else if (!keyDown) {
if (runtimeBarcodeFlag) {
// 停止扫描
this.stopBarcodeScan()
} else {
// 停止超高频
this.stopInventory()
}
keyDownTime = 1
}
}
})
this.main.registerReceiver(this.receiver, filter)
// #endif
},
// 屏蔽二维码扫描扳机
disableBarcodeScanKey() {
// #ifdef APP-PLUS
if (!this.main) return
var Intent = plus.android.importClass("android.content.Intent")
var intent = new Intent("com.rfid.KEY_SET")
var keyValueArray = ["137"]
intent.putExtra("keyValueArray", keyValueArray)
intent.putExtra("137", false)
this.main.sendBroadcast(intent)
// #endif
},
// 字节数组转十六进制字符
bytes2HexString(byteArray) {
return Array.from(byteArray, function(byte) {
return ('0' + (byte & 0xFF).toString(16)).slice(-2)
}).join('')
},
// 十六进制字符串转字节数组
hexString2Bytes(str) {
var pos = 0
var len = str.length
if (len % 2 != 0) {
return null
}
len /= 2
var hexA = new Array()
for (var i = 0; i < len; i++) {
var s = str.substr(pos, 2)
var v = parseInt(s, 16)
hexA.push(v)
pos += 2
}
return hexA
},
// 十六进制字符串转原始数字字符串反向操作parseInt(id, 10).toString(16)
hexToString(hexStr) {
if (!hexStr) return ''
// 去除空格并转为大写
let clean = hexStr.replace(/\s+/g, '').toUpperCase()
// 将十六进制字符串转换为十进制数字,再转为字符串
const num = parseInt(clean, 16)
if (isNaN(num)) {
return hexStr
}
return num.toString()
},
// 格式化物资列表数据
formatMaterialList(res) {
const list = res && res.data && res.data.list && res.data.list.data ? res.data.list.data : []
console.log("list:", list)
return list.map((item) => ({
// 用于跳转的ID后端字段兼容
id: item.id || item.material_info_id || item.inventory_id || '',
// 表格展示字段
zichanmingcheng: item.zichanmingcheng || '',
total_num: item.total_num ?? item.inventorys_total ?? item.zaikushuliang ?? 0
}))
},
// 规范化扫码ID允许 "3715.000" => "3715"
normalizeScannedId(value) {
if (!value) return ''
const v = String(value).trim()
// 若是纯数字,直接返回
if (/^\d+$/.test(v)) return v
// 数字(可带小数),取整数部分
if (/^\d+(?:\.\d+)?$/.test(v)) {
const num = Number(v)
if (!Number.isNaN(num) && Number.isFinite(num)) {
return Math.trunc(num).toString()
}
}
// 其他情况返回空,触发无效提示
return ''
},
// 从物资列表进入盘点页面
goInventoryFromList(item) {
const code = item && (item.id)
if (!code) {
uni.showToast({ title: '无法获取物资ID', icon: 'none' })
return
}
uni.navigateTo({ url: `/pages/inventory/inventory?code=${encodeURIComponent(code)}` });
},
// 初始化二维码扫描
initBarcodeScan() {
// #ifdef APP-PLUS
if (!this.main) return
var Intent = plus.android.importClass("android.content.Intent")
var intent = new Intent("com.rfid.SCAN_INIT")
this.main.sendBroadcast(intent)
// #endif
},
// 触发二维码扫描
startBarcodeScan() {
// #ifdef APP-PLUS
if (!this.main) return
var Intent = plus.android.importClass("android.content.Intent")
var intent = new Intent("com.rfid.SCAN_CMD")
this.main.sendBroadcast(intent)
// #endif
},
// 暂停二维码扫描
stopBarcodeScan() {
// #ifdef APP-PLUS
if (!this.main) return
var Intent = plus.android.importClass("android.content.Intent")
var intent = new Intent("com.rfid.STOP_SCAN")
this.main.sendBroadcast(intent)
// #endif
},
// 处理盘点按钮点击
handleInventory() {
if (this.btn1Info == "开始盘点") {
// 按钮点击始终进行超高频盘点,不受手柄扫码选项影响
this.startInventory()
} else {
// 停止超高频盘点
this.stopInventory()
}
},
// 开始盘点
startInventory() {
// #ifdef APP-PLUS
if (!this.hhwUHFController) {
uni.showToast({
title: "UHF控制器未初始化",
icon: "none"
})
return
}
// #endif
this.btn2Disabled = true
this.btn3Disabled = true
this.cbDisabled = true
this.btn1Info = "停止盘点"
// #ifdef APP-PLUS
this.hhwUHFController.setCancleInventoryFilter()
if (runtimeAsyncFlag) {
// 大量标签场景200张标签以上开始异步盘点手动调用停止盘点后停止盘点
this.hhwUHFController.startInventory(30, 0, true, 0, runtimeVoiceFlag, (result) => {
console.log("inventory inventory", "startInventory " + result)
})
} else {
// 少量标签场景200张标签以下开始同步盘点手动调用停止盘点后停止盘点
console.log("async_flag")
this.hhwUHFController.startInventory(30, 0, false, 0, runtimeVoiceFlag, (result) => {
console.log("inventory inventory", "startInventory " + result)
})
}
// #endif
},
// 停止盘点
stopInventory() {
// #ifdef APP-PLUS
if (!this.hhwUHFController) {
return
}
// 停止盘点注意stopInventory中的参数值需要和startInventory第一个参数值对应
if (runtimeAsyncFlag) {
this.hhwUHFController.stopInventory(true)
} else {
this.hhwUHFController.stopInventory(false)
}
// #endif
},
// 清空标签
clearTags() {
this.dataList = []
this.tempList = []
this.epcList = []
this.tagsAmount = 0
this.offset = 50
this.taskList = []
},
// 查看物资
viewMaterials() {
const idsSource = this.epcList.length ? this.epcList : this.dataList.map(item => item.epc).filter(Boolean)
if (!idsSource.length) {
uni.showToast({
title: '请先获取标签数据',
icon: 'none'
})
return
}
const ids = idsSource.join(',')
uni.showLoading({
title: '查询中...'
})
getInventoryTaskList({ ids:ids,page:1,page_size:999 })
.then(res => {
uni.hideLoading()
console.log("res:", res)
const materialList = this.formatMaterialList(res)
if (materialList.length === 0) {
this.taskList = []
uni.showToast({
title: '未查询到物资信息',
icon: 'none'
})
return
}
this.taskList = materialList
})
.catch(err => {
console.error('viewMaterials error', err)
uni.hideLoading()
uni.showToast({
title: '获取物资失败',
icon: 'none'
})
})
},
// 加载更多
loadmore() {
console.log("inventory loadmore", "dataList size1: " + this.dataList.length, "temList size: " + this.tempList.length)
if (this.dataList.length >= this.tempList.length) {
console.log("inventory loadmore", "nomore")
return
}
// 每次加载pageNum个
var size
if (this.tempList.length - this.offset >= this.pageNum) {
size = this.pageNum
} else {
size = this.tempList.length - this.offset
}
for (var i = this.offset; i < size + this.offset; i++) {
this.dataList.push(this.tempList[i])
}
this.offset = this.offset + size
},
// 异步选项改变
onAsyncChange(e) {
this.asyncFlag = e.detail.value[0] === 'async_checkbox'
runtimeAsyncFlag = this.asyncFlag
},
// 声音选项改变
onVoiceChange(e) {
this.voiceFlag = e.detail.value[0] === 'voice_checkbox'
runtimeVoiceFlag = this.voiceFlag
},
// 二维码扫描选项改变
onBarcodeChange(e) {
this.barcodeFlag = e.detail.value[0] === 'barcode_checkbox'
runtimeBarcodeFlag = this.barcodeFlag
}
}
}
</script>
<style>
.index-bg {
height: 100vh;
background: linear-gradient(180deg, #eaf1fb 0%, #f7fafd 100%);
}
.index-content {
width: 100%;
max-width: 600px;
display: flex;
flex-direction: column;
align-items: center;
padding: 4vw 0 6vw 0;
box-sizing: border-box;
margin: 0 auto;
}
.btn-group {
width: 90%;
max-width: 500px;
display: flex;
flex-direction: column;
margin: 0 auto 48rpx auto;
}
.main-btn {
height: 110rpx;
font-size: 38rpx;
font-weight: 800;
border-radius: 28rpx;
background: linear-gradient(135deg, #409eff 0%, #3b7cff 100%);
color: #fff;
box-shadow: 0 8px 24px rgba(64,158,255,0.25);
letter-spacing: 10rpx;
margin: 0;
border: none;
outline: none;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
margin-bottom: 36rpx;
position: relative;
overflow: hidden;
}
.main-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
transition: left 0.6s;
}
.main-btn:active::before {
left: 100%;
}
.main-btn:last-child {
margin-bottom: 0;
}
.main-btn:active {
background: linear-gradient(135deg, #337ecc 0%, #2a5db0 100%);
box-shadow: 0 4px 16px rgba(64,158,255,0.3);
transform: scale(0.98);
}
.main-btn.outline {
background: #fff;
color: #409eff;
border: 3px solid #409eff;
box-shadow: 0 8px 24px rgba(64,158,255,0.15);
}
.main-btn.outline::before {
background: linear-gradient(90deg, transparent, rgba(64,158,255,0.1), transparent);
}
.main-btn.outline:active {
background: #f0f7ff;
transform: scale(0.98);
box-shadow: 0 4px 16px rgba(64,158,255,0.2);
}
.task-section {
width: 92%;
max-width: 520px;
margin: 0 auto 48rpx auto;
background: #fff;
border-radius: 22rpx;
box-shadow: 0 4px 18px rgba(64,158,255,0.07);
padding: 28rpx 20rpx 18rpx 20rpx;
}
.task-title {
font-size: 28rpx;
color: #222;
font-weight: 700;
margin-bottom: 18rpx;
}
.task-list {
display: flex;
flex-direction: column;
gap: 14rpx;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14rpx 0;
border-bottom: 1px solid #f0f0f0;
}
.task-item:last-child {
border-bottom: none;
}
.task-info {
display: flex;
flex-direction: column;
}
.task-name {
font-size: 24rpx;
color: #333;
font-weight: 600;
}
.task-time {
font-size: 20rpx;
color: #999;
margin-top: 2rpx;
}
.task-list {
display: flex;
flex-direction: column;
gap: 14rpx;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14rpx 0;
border-bottom: 1px solid #f0f0f0;
}
.task-item:last-child {
border-bottom: none;
}
.task-info {
display: flex;
flex-direction: column;
}
.task-name {
font-size: 24rpx;
color: #333;
font-weight: 600;
}
.task-time {
font-size: 20rpx;
color: #999;
margin-top: 2rpx;
}
.task-status {
font-size: 20rpx;
padding: 4rpx 14rpx;
border-radius: 16rpx;
}
.task-status.pending {
background-color: #fff3e0;
color: #ff9800;
}
.task-status.completed {
background-color: #e8f5e9;
color: #4caf50;
}
.task-status.in-progress {
background-color: #e8f5e9;
color: #4caf50;
}
.h5-scan-modal {
position: fixed;
left: 0; top: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.6);
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.h5-scan-modal #reader {
width: 300px !important;
height: 300px !important;
margin: 0 auto;
background: #fff;
border-radius: 8px;
overflow: hidden;
}
.h5-scan-modal #reader video {
width: 100% !important;
height: 100% !important;
object-fit: cover;
}
.h5-scan-modal #reader__scan_region {
width: 100% !important;
height: 100% !important;
}
.h5-scan-modal #reader__scan_region video {
width: 100% !important;
height: 100% !important;
}
.h5-scan-modal button {
margin-top: 20px;
padding: 8px 20px;
background: #409eff;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
.h5-scan-modal button:hover {
background: #66b1ff;
}
/* 标签盘点区域样式 */
.inventory-section {
width: 92%;
max-width: 520px;
margin: 0 auto 48rpx auto;
background: #fff;
border-radius: 22rpx;
box-shadow: 0 4px 18px rgba(64,158,255,0.07);
padding: 28rpx 20rpx 18rpx 20rpx;
}
.inventory-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 18rpx;
}
.inventory-title {
font-size: 28rpx;
color: #222;
font-weight: 700;
}
.tags-amount {
font-size: 24rpx;
color: #409eff;
font-weight: 600;
}
.tag-list {
max-height: 200rpx;
background-color: #ebebeb;
border-radius: 12rpx;
margin-bottom: 18rpx;
}
.list-item-head {
display: flex;
flex-direction: row;
background-color: #f5f5f5;
border-radius: 12rpx 12rpx 0 0;
padding: 10rpx 0;
}
.list-item {
display: flex;
flex-direction: row;
background-color: #fff;
border-bottom: 1px solid #f0f0f0;
}
.list-item:last-child {
border-bottom: none;
}
.list-item-text-id {
width: 65px;
padding: 8rpx 10rpx;
font-size: 24rpx;
text-align: center;
flex-shrink: 0;
}
.list-item-text-epc {
flex: 1;
padding: 8rpx 10rpx;
font-size: 24rpx;
word-break: break-all;
min-width: 0;
}
.list-item-text-count {
width: 65px;
padding: 8rpx 10rpx;
font-size: 24rpx;
text-align: center;
flex-shrink: 0;
}
.list-item-text-rssi {
width: 100px;
padding: 8rpx 10rpx;
font-size: 24rpx;
text-align: center;
flex-shrink: 0;
}
.inventory-options {
display: flex;
flex-direction: row;
align-items: center;
padding: 18rpx 0;
flex-wrap: wrap;
gap: 20rpx;
}
.option-item {
display: flex;
align-items: center;
}
.checkbox-label {
display: flex;
flex-direction: row;
align-items: center;
}
.option-text {
margin-left: 8rpx;
font-size: 24rpx;
color: #333;
}
.inventory-btn-box {
display: flex;
flex-direction: row;
align-items: center;
gap: 10rpx;
margin-top: 5rpx;
}
.inventory-btn {
flex: 1;
/* height: 80rpx; */
font-size: 28rpx;
border-radius: 12rpx;
margin: 0;
}
.inventory-btn.clear-btn {
flex: 0 0 100rpx;
margin-top:10rpx;
}
/* 盘点计划区域样式 */
.inventory-plan-section {
width: 92%;
max-width: 520px;
margin: 0 auto 48rpx auto;
background: #fff;
border-radius: 22rpx;
box-shadow: 0 4px 18px rgba(64,158,255,0.07);
padding: 28rpx 20rpx 18rpx 20rpx;
}
.inventory-plan-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 18rpx;
}
.inventory-plan-title {
font-size: 28rpx;
color: #222;
font-weight: 700;
}
.plan-total {
font-size: 24rpx;
color: #409eff;
font-weight: 600;
}
.plan-list {
display: flex;
flex-direction: column;
gap: 14rpx;
}
.plan-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14rpx 0;
border-bottom: 1px solid #f0f0f0;
}
.plan-item:last-child {
border-bottom: none;
}
.plan-info {
display: flex;
flex-direction: column;
}
.plan-name-col {
flex: 1;
min-width: 0;
}
.plan-status-col {
width: 120rpx;
align-items: center;
}
.plan-date-col {
width: 140rpx;
align-items: center;
}
.plan-action-col {
width: 120rpx;
display: flex;
justify-content: flex-end;
align-items: center;
}
.plan-name {
font-size: 24rpx;
color: #333;
font-weight: 600;
}
.plan-no {
font-size: 20rpx;
color: #999;
margin-top: 4rpx;
}
.plan-time {
font-size: 20rpx;
color: #999;
margin-top: 2rpx;
}
.plan-status {
font-size: 20rpx;
padding: 4rpx 14rpx;
border-radius: 16rpx;
white-space: nowrap;
}
.plan-status.status-0 {
background-color: #fff3e0;
color: #ff9800;
}
.plan-status.status-1 {
background-color: #e3f2fd;
color: #2196f3;
}
.plan-status.status-2 {
background-color: #e8f5e9;
color: #4caf50;
}
/* 加载更多样式优化 */
.load-more {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
padding: 30rpx 20rpx;
font-size: 24rpx;
color: #999;
}
.loading-spinner {
width: 32rpx;
height: 32rpx;
border: 3rpx solid #f0f0f0;
border-top: 3rpx solid #409eff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
color: #409eff;
font-weight: 500;
}
.no-more-text {
color: #ccc;
font-size: 22rpx;
}
</style>