398 lines
9.6 KiB
Plaintext
398 lines
9.6 KiB
Plaintext
<template>
|
||
<view class="hr-page" style="background-color:#F3F4F6">
|
||
|
||
<hr-statusbar bg="#FFFFFF"></hr-statusbar>
|
||
|
||
<view class="wk-nav">
|
||
<text class="wk-nav-t">扫码出入库</text>
|
||
<view class="wk-nav-a" @click="toggleFlash">
|
||
<x-icon name="flashlight-line" :color="flash ? '#2563EB' : '#6B7280'" font-size="16"></x-icon>
|
||
<text class="wk-nav-a-t">手电</text>
|
||
</view>
|
||
</view>
|
||
|
||
<scroll-view class="hr-scroll" :scroll-y="true" :show-scrollbar="false">
|
||
<view class="hr-body">
|
||
|
||
<!-- ============ 取景框 ============ -->
|
||
<view class="sc-box">
|
||
<!-- 真实相机(APP / H5) -->
|
||
<!-- #ifdef APP || WEB -->
|
||
<x-mlkit-scannig-u :flash="flash" :autoOpenCamera="true" :cameraWidth="1080"
|
||
:cameraeiHght="1080" style="width:100%;height:100%;position:absolute;left:0;top:0"
|
||
@scan="onScan"></x-mlkit-scannig-u>
|
||
<!-- #endif -->
|
||
|
||
<view class="sc-frame">
|
||
<view class="sc-cf sc-cf-tl"></view>
|
||
<view class="sc-cf sc-cf-tr"></view>
|
||
<view class="sc-cf sc-cf-bl"></view>
|
||
<view class="sc-cf sc-cf-br"></view>
|
||
<view class="sc-line" :style="laserStyle"></view>
|
||
</view>
|
||
<text class="sc-tip">将条码对准框内 · 自动识别</text>
|
||
</view>
|
||
|
||
<!-- ============ 业务动作 ============ -->
|
||
<view class="hr-seg" style="margin-top:12px">
|
||
<view v-for="(m, i) in modes" :key="i" :class="i == modeIdx ? 'hr-seg-i hr-seg-i-on' : 'hr-seg-i'"
|
||
@click="modeIdx = i">
|
||
<text :class="i == modeIdx ? 'sc-seg-t sc-seg-t-on' : 'sc-seg-t'">{{ m }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ============ 识别反馈(三态) ============ -->
|
||
<hr-sec title="识别反馈" :first="false"></hr-sec>
|
||
<view v-if="feedbacks.length == 0" class="sc-empty">
|
||
<x-icon name="scan-line" color="#9CA3AF" font-size="24"></x-icon>
|
||
<text class="sc-empty-t">等待扫码…</text>
|
||
<text class="sc-empty-s">识别后会在此即时给出 ✅ 成功 / ⚠ 库存不足 / ❌ 未登记 三种反馈</text>
|
||
</view>
|
||
<view v-for="(f, i) in feedbacks" :key="i" :class="feedbackClass(f.theme)">
|
||
<view class="sc-st-ic" :style="{ backgroundColor: feedbackColor(f.theme) }">
|
||
<text class="sc-st-ic-t">{{ f.icon }}</text>
|
||
</view>
|
||
<view class="hr-flex1">
|
||
<text class="sc-st-b">{{ f.title }}</text>
|
||
<text class="sc-st-s">{{ f.sub }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- ============ 本次连续扫入 ============ -->
|
||
<hr-sec title="本次连续扫入" :more="records.length + ' 条'"></hr-sec>
|
||
<hr-card variant="list">
|
||
<hr-lrow v-for="(r, i) in records" :key="i" :icon="r.icon"
|
||
:theme="r.theme == 'grn' ? 'grn' : (r.theme == 'amb' ? 'amb' : 'red')" :title="r.name"
|
||
:sub="r.time + ' · ' + r.delta" :value="r.status"
|
||
:value-theme="r.theme == 'grn' ? 'grn' : 'amb'" :value-bold="true"
|
||
:is-last="i == records.length - 1" @click="onRecord(r)"></hr-lrow>
|
||
</hr-card>
|
||
|
||
<view style="height:14px"></view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<hr-tabbar :active="0"></hr-tabbar>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="uts" setup>
|
||
import { ref, computed } from 'vue'
|
||
import { scanFeedbacks, scanRecords } from '@/mock/index.uts'
|
||
import { HrScanFeedback, HrScanRecord } from '@/mock/types.uts'
|
||
|
||
/**
|
||
* s23 扫码出入库(仓管 Tab「扫码」)
|
||
* 连续扫码三态即时反馈:✅ 入库成功 / ⚠ 库存不足 / ❌ 未登记。
|
||
*/
|
||
const modes : string[] = ['入库', '出库', '盘点']
|
||
const modeIdx = ref(0)
|
||
const flash = ref(false)
|
||
|
||
const feedbacks = ref<HrScanFeedback[]>([
|
||
scanFeedbacks[0], scanFeedbacks[1], scanFeedbacks[2]
|
||
])
|
||
const records = ref<HrScanRecord[]>([
|
||
scanRecords[0], scanRecords[1], scanRecords[2]
|
||
])
|
||
|
||
let seq = 3
|
||
|
||
function shortCode(code : string) : string {
|
||
if (code.length <= 10) { return code }
|
||
return code.substring(0, 10)
|
||
}
|
||
|
||
function nowTime() : string {
|
||
const d = new Date()
|
||
const hh = d.getHours() < 10 ? '0' + d.getHours().toString() : d.getHours().toString()
|
||
const mm = d.getMinutes() < 10 ? '0' + d.getMinutes().toString() : d.getMinutes().toString()
|
||
return hh + ':' + mm
|
||
}
|
||
|
||
/** 依据条码特征给出三态反馈(mock 规则,后续替换为接口返回) */
|
||
function handleCode(code : string) : void {
|
||
seq = seq + 1
|
||
const fb : HrScanFeedback = {
|
||
theme: 'ok', icon: '✓',
|
||
title: '一次性医用口罩 · ' + modes[modeIdx.value] + '成功',
|
||
sub: '规格 50 只/盒 · 现存 320 盒 · 张明远 · 编码 ' + shortCode(code)
|
||
} as HrScanFeedback
|
||
|
||
const rec : HrScanRecord = {
|
||
name: '一次性医用口罩', time: nowTime(), delta: '+1 盒', status: '已入库', theme: 'grn', icon: '✓'
|
||
} as HrScanRecord
|
||
|
||
const fbArr : HrScanFeedback[] = []
|
||
fbArr.push(fb)
|
||
for (let i = 0; i < feedbacks.value.length; i++) {
|
||
if (i < 2) { fbArr.push(feedbacks.value[i]) }
|
||
}
|
||
feedbacks.value = fbArr
|
||
|
||
const recArr : HrScanRecord[] = []
|
||
recArr.push(rec)
|
||
for (let i = 0; i < records.value.length; i++) {
|
||
if (i < 4) { recArr.push(records.value[i]) }
|
||
}
|
||
records.value = recArr
|
||
|
||
uni.showToast({ title: '识别成功', icon: 'success' })
|
||
}
|
||
|
||
function onScan(res : any) : void {
|
||
const arr = res as string[]
|
||
if (arr == null || arr.length == 0) { return }
|
||
handleCode(arr[0])
|
||
}
|
||
|
||
function feedbackClass(theme : string) : string {
|
||
return `sc-st sc-st-${theme}`
|
||
}
|
||
|
||
function feedbackColor(theme : string) : string {
|
||
if (theme == 'ok') { return '#10B981' }
|
||
if (theme == 'warn') { return '#F59E0B' }
|
||
return '#DC2626'
|
||
}
|
||
|
||
function onRecord(r : HrScanRecord) : void {
|
||
uni.showModal({ title: r.name, content: r.time + ' · ' + r.delta + '\n状态:' + r.status, showCancel: false })
|
||
}
|
||
|
||
function toggleFlash() : void {
|
||
flash.value = !flash.value
|
||
uni.showToast({ title: flash.value ? '已打开手电' : '已关闭手电', icon: 'none' })
|
||
}
|
||
|
||
/*
|
||
* 取景框扫描线
|
||
* ⚠ App 端 uvue 不支持 @keyframes / animation(编译期直接报 ERROR),
|
||
* 改为定时器逐帧推进 top:sc-frame 高 372rpx,16%~84% ⇒ 60~313rpx。
|
||
* 周期 2.2s(68ms × 32 帧),与设计稿 hrScanMove 一致。
|
||
*/
|
||
const laserTop = ref(60)
|
||
let laserT = 0.0
|
||
let laserTimer : number = 0
|
||
|
||
const laserStyle = computed(() : string => {
|
||
return 'top:' + laserTop.value.toString() + 'rpx;'
|
||
})
|
||
|
||
function startLaser() : void {
|
||
if (laserTimer != 0) { return }
|
||
laserTimer = setInterval(() => {
|
||
laserT = laserT + 0.03125 // 1/32:32 帧正好走完一个往返,端点(16%/84%)可精确命中
|
||
if (laserT > 1) { laserT = laserT - 1 }
|
||
const k = laserT < 0.5 ? laserT * 2 : (1 - laserT) * 2
|
||
laserTop.value = Math.round(60 + k * 253)
|
||
}, 68)
|
||
}
|
||
|
||
function stopLaser() : void {
|
||
if (laserTimer != 0) {
|
||
clearInterval(laserTimer)
|
||
laserTimer = 0
|
||
}
|
||
}
|
||
|
||
onLoad(() => {
|
||
startLaser()
|
||
})
|
||
|
||
onUnload(() => {
|
||
stopLaser()
|
||
})
|
||
</script>
|
||
|
||
<style>
|
||
.wk-nav {
|
||
height: 96rpx;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding-left: 32rpx;
|
||
padding-right: 32rpx;
|
||
background-color: #FFFFFF;
|
||
border-bottom: 2rpx solid #F3F4F6;
|
||
}
|
||
|
||
.wk-nav-t {
|
||
font-size: 33.6rpx;
|
||
font-weight: bold;
|
||
color: #1F2937;
|
||
}
|
||
|
||
.wk-nav-a {
|
||
margin-left: auto;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.wk-nav-a-t {
|
||
font-size: 29.2rpx;
|
||
color: #6B7280;
|
||
margin-left: 8rpx;
|
||
}
|
||
|
||
/* ---------- 取景框 ---------- */
|
||
.sc-box {
|
||
width: 100%;
|
||
height: 686rpx;
|
||
background-color: #0F172A;
|
||
border-radius: 32rpx;
|
||
overflow: hidden;
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.sc-frame {
|
||
width: 372rpx;
|
||
height: 372rpx;
|
||
position: relative;
|
||
}
|
||
|
||
.sc-cf {
|
||
position: absolute;
|
||
width: 52rpx;
|
||
height: 52rpx;
|
||
border: 6rpx solid #10B981;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.sc-cf-tl {
|
||
top: 0rpx;
|
||
left: 0rpx;
|
||
border-right-width: 0rpx;
|
||
border-bottom-width: 0rpx;
|
||
}
|
||
|
||
.sc-cf-tr {
|
||
top: 0rpx;
|
||
right: 0rpx;
|
||
border-left-width: 0rpx;
|
||
border-bottom-width: 0rpx;
|
||
}
|
||
|
||
.sc-cf-bl {
|
||
bottom: 0rpx;
|
||
left: 0rpx;
|
||
border-right-width: 0rpx;
|
||
border-top-width: 0rpx;
|
||
}
|
||
|
||
.sc-cf-br {
|
||
bottom: 0rpx;
|
||
right: 0rpx;
|
||
border-left-width: 0rpx;
|
||
border-top-width: 0rpx;
|
||
}
|
||
|
||
/* 扫描线:⚠ App 端 uvue 不支持 @keyframes/animation,
|
||
上下位移已改为 script 内定时器推进 laserTop + laserStyle 下发 top。 */
|
||
.sc-line {
|
||
position: absolute;
|
||
left: 9%;
|
||
right: 9%;
|
||
top: 60rpx;
|
||
height: 4rpx;
|
||
background-color: #10B981;
|
||
}
|
||
|
||
.sc-tip {
|
||
position: absolute;
|
||
bottom: 24rpx;
|
||
left: 0rpx;
|
||
right: 0rpx;
|
||
text-align: center;
|
||
font-size: 25.8rpx;
|
||
color: rgba(255, 255, 255, 0.78);
|
||
}
|
||
|
||
.sc-seg-t {
|
||
font-size: 29.2rpx;
|
||
color: #6B7280;
|
||
}
|
||
|
||
.sc-seg-t-on {
|
||
color: #2563EB;
|
||
font-weight: bold;
|
||
}
|
||
|
||
/* ---------- 三态反馈 ---------- */
|
||
.sc-st {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 22rpx 24rpx 22rpx 24rpx;
|
||
border-radius: 22rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.sc-st-ok {
|
||
background-color: #ECFDF5;
|
||
border: 2rpx solid #A7F3D0;
|
||
}
|
||
|
||
.sc-st-warn {
|
||
background-color: #FFFBEB;
|
||
border: 2rpx solid #FDE68A;
|
||
}
|
||
|
||
.sc-st-err {
|
||
background-color: #FEF2F2;
|
||
border: 2rpx solid #FECACA;
|
||
}
|
||
|
||
.sc-st-ic {
|
||
width: 52rpx;
|
||
height: 52rpx;
|
||
border-radius: 16rpx;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.sc-st-ic-t {
|
||
font-size: 29.2rpx;
|
||
font-weight: bold;
|
||
color: #FFFFFF;
|
||
}
|
||
|
||
.sc-st-b {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.sc-st-s {
|
||
font-size: 23.6rpx;
|
||
margin-top: 4rpx;
|
||
}
|
||
|
||
.sc-empty {
|
||
align-items: center;
|
||
padding-top: 40rpx;
|
||
padding-bottom: 40rpx;
|
||
}
|
||
|
||
.sc-empty-t {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #6B7280;
|
||
margin-top: 16rpx;
|
||
}
|
||
|
||
.sc-empty-s {
|
||
font-size: 23.6rpx;
|
||
color: #9CA3AF;
|
||
margin-top: 8rpx;
|
||
text-align: center;
|
||
}
|
||
</style>
|