master
parent
51aebf505e
commit
ead30c3479
|
|
@ -0,0 +1,11 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 根据用户ID查询门店列表
|
||||
export function PostData(data) {
|
||||
return request({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: `/mall/product/getByBarcodes`,
|
||||
method: 'post',
|
||||
data:data
|
||||
})
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name" : "海驰24",
|
||||
"appid" : "__UNI__B012241",
|
||||
"appid" : "__UNI__7302921",
|
||||
"description" : "111",
|
||||
"versionName" : "1.2.0",
|
||||
"versionCode" : "100",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,12 @@
|
|||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/translation/translation",
|
||||
"style": {
|
||||
"navigationBarTitleText": "录入清单"
|
||||
}
|
||||
}
|
||||
],
|
||||
"subPackages": [
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@
|
|||
<view class="form-item" v-if="productInfo.expirationManagement">
|
||||
<text class="item-label">临期提醒天数</text>
|
||||
<view class="warning-days-display">
|
||||
<text class="warning-days-value">{{ warningDaysDisplay }}</text>
|
||||
<text class="warning-days-value">{{ productInfo.shelfLife }}</text>
|
||||
<text class="warning-days-unit">天</text>
|
||||
</view>
|
||||
</view>
|
||||
|
|
@ -222,11 +222,37 @@
|
|||
<button class="save-button" @tap="handleSave">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 微信小程序全屏扫码覆盖层 -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view v-if="showScanner" class="scanner-overlay">
|
||||
<camera
|
||||
class="scanner-camera"
|
||||
mode="scanCode"
|
||||
device-position="back"
|
||||
flash="off"
|
||||
@scancode="onOverlayScanResult"
|
||||
>
|
||||
<cover-view class="scanner-mask">
|
||||
<cover-view class="scanner-box">
|
||||
<cover-view class="box-corner tl"></cover-view>
|
||||
<cover-view class="box-corner tr"></cover-view>
|
||||
<cover-view class="box-corner bl"></cover-view>
|
||||
<cover-view class="box-corner br"></cover-view>
|
||||
</cover-view>
|
||||
<cover-view class="scanner-actions">
|
||||
<cover-view class="scanner-btn" @tap="closeScanner">关闭</cover-view>
|
||||
</cover-view>
|
||||
</cover-view>
|
||||
</camera>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addProductWithFile } from '@/api/product'
|
||||
import { getStoreId } from '@/utils/auth'
|
||||
import apiUrl from '@/utils/api'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
|
@ -240,11 +266,14 @@ export default {
|
|||
cost: '',
|
||||
expirationManagement: false,
|
||||
shelfCode: '',
|
||||
shelfLife:'',
|
||||
productCode: '',
|
||||
expirationDays: '',
|
||||
warningDays: '',
|
||||
productionDate: ''
|
||||
}
|
||||
productionDate: '',
|
||||
url:''
|
||||
},
|
||||
showScanner: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -265,31 +294,85 @@ export default {
|
|||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
console.log('页面参数:', options);
|
||||
console.log('页面参数:', options,apiUrl);
|
||||
this.url = apiUrl
|
||||
// 从扫码页面传入的条形码
|
||||
if (options && options.fromData) {
|
||||
try {
|
||||
this.productInfo.barcode = options.barcode
|
||||
const fromData = JSON.parse(options.fromData)[0];
|
||||
this.productInfo.name = fromData.productName || '';
|
||||
this.productInfo.cost = fromData.costPrice || '';
|
||||
this.productInfo.barcode = fromData.productBarCode || '';
|
||||
this.productInfo.price = fromData.storePrice || '';
|
||||
this.productInfo.quantity = fromData.stockQuantity || '';
|
||||
this.productInfo.productCode = fromData.productCode || '';
|
||||
this.productInfo.image = this.url + fromData.mainImage || '';
|
||||
this.productInfo.shelfCode = fromData.shelfCode || '';
|
||||
this.productInfo.expirationManagement = fromData.shelfLife?true:false;
|
||||
if(fromData.shelfLife){
|
||||
console.log(fromData.productionDate);
|
||||
this.productInfo.productionDate = fromData.productionDate || '2026-12-19'
|
||||
this.productInfo.shelfLife = fromData.shelfLife;
|
||||
}
|
||||
console.log(this.productInfo);
|
||||
} catch (e) {
|
||||
this.productInfo.barcode = options.barcode
|
||||
}
|
||||
}
|
||||
|
||||
if (options.fromData) {
|
||||
try {
|
||||
const fromData = JSON.parse(decodeURIComponent(options.fromData));
|
||||
console.log('接收到的商品数据:', fromData);
|
||||
this.productInfo.name = fromData.productName || '';
|
||||
this.productInfo.barcode = fromData.productBarCode || '';
|
||||
this.productInfo.price = fromData.storePrice || '';
|
||||
this.productInfo.quantity = fromData.stockQuantity || '';
|
||||
this.productInfo.productCode = fromData.productCode || '';
|
||||
this.productInfo.image = fromData.mainImage || '';
|
||||
|
||||
} catch (error) {
|
||||
console.error('解析商品数据失败:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 扫码功能
|
||||
handleScan() {
|
||||
uni.showToast({ title: '扫码功能(模拟)', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
this.productInfo.barcode = '6921168558032'
|
||||
// #ifdef MP-WEIXIN
|
||||
this.showScanner = true
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.scanCode({
|
||||
onlyFromCamera: true,
|
||||
scanType: ['barCode'],
|
||||
success: (res) => {
|
||||
const code = (res.result || '').trim()
|
||||
const type = (res.scanType || res.codeType || '').toString().toUpperCase()
|
||||
const isBarcode = type.indexOf('QR') === -1
|
||||
const numericOk = /^\d{8,20}$/.test(code)
|
||||
if (isBarcode || numericOk) {
|
||||
this.productInfo.barcode = code
|
||||
uni.showToast({ title: '扫码成功', icon: 'success' })
|
||||
} else {
|
||||
uni.showToast({ title: '请扫条形码', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '扫码失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
closeScanner() {
|
||||
this.showScanner = false
|
||||
},
|
||||
onOverlayScanResult(e) {
|
||||
if (!this.showScanner) return
|
||||
const { result, scanType } = e.detail || {}
|
||||
const code = (result || '').trim()
|
||||
const type = (scanType || '').toString().toUpperCase()
|
||||
const isBarcode = type.indexOf('QR') === -1
|
||||
const numericOk = /^\d{8,20}$/.test(code)
|
||||
if (isBarcode || numericOk) {
|
||||
this.productInfo.barcode = code
|
||||
this.showScanner = false
|
||||
uni.showToast({ title: '扫码成功', icon: 'success' })
|
||||
}, 500)
|
||||
} else {
|
||||
uni.showToast({ title: '请扫条形码', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
// 选择图片
|
||||
|
|
@ -329,7 +412,7 @@ export default {
|
|||
if (!this.productInfo.price) { uni.showToast({ title: '请输入售价', icon: 'none' });return false }
|
||||
// 开启保质期管理 必校验三项
|
||||
if (this.productInfo.expirationManagement) {
|
||||
if (!this.productInfo.expirationDays) { uni.showToast({ title: '请选择保质期天数', icon: 'none' });return false }
|
||||
if (!this.productInfo.shelfLife) { uni.showToast({ title: '请选择保质期天数', icon: 'none' });return false }
|
||||
// 临期提醒天数自动设置,不需要手动输入
|
||||
if (!this.productInfo.productionDate) { uni.showToast({ title: '请选择生产日期', icon: 'none' });return false }
|
||||
}
|
||||
|
|
@ -349,7 +432,7 @@ export default {
|
|||
productBarCode: this.productInfo.barcode,
|
||||
storePrice: this.productInfo.price,
|
||||
stockQuantity: this.productInfo.quantity,
|
||||
costPrice: this.productInfo.cost,
|
||||
costPrice: this.productInfo.costPrice,
|
||||
productCode: this.productInfo.productCode,
|
||||
shelfCode: this.productInfo.shelfCode,
|
||||
expirationManagement: this.productInfo.expirationManagement ? 1 : 0,
|
||||
|
|
@ -463,4 +546,50 @@ export default {
|
|||
.recommend-list { flex-direction:column; }
|
||||
.recommend-item { margin:10rpx 0; }
|
||||
}
|
||||
|
||||
/* 微信小程序全屏扫码覆盖层样式 */
|
||||
.scanner-overlay {
|
||||
position: fixed;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
background: #000;
|
||||
z-index: 9999;
|
||||
}
|
||||
.scanner-camera {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.scanner-mask {
|
||||
position: absolute;
|
||||
left: 0; top: 0; right: 0; bottom: 0;
|
||||
}
|
||||
.scanner-box {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 520rpx;
|
||||
height: 520rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.box-corner {
|
||||
position: absolute;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 4rpx solid #fff;
|
||||
}
|
||||
.box-corner.tl { left: 0; top: 0; border-right: none; border-bottom: none; }
|
||||
.box-corner.tr { right: 0; top: 0; border-left: none; border-bottom: none; }
|
||||
.box-corner.bl { left: 0; bottom: 0; border-right: none; border-top: none; }
|
||||
.box-corner.br { right: 0; bottom: 0; border-left: none; border-top: none; }
|
||||
.scanner-actions {
|
||||
position: absolute;
|
||||
left: 0; right: 0; bottom: 80rpx;
|
||||
display: flex; justify-content: center;
|
||||
}
|
||||
.scanner-btn {
|
||||
padding: 20rpx 40rpx;
|
||||
color: #fff;
|
||||
border: 2rpx solid rgba(255,255,255,0.6);
|
||||
border-radius: 40rpx;
|
||||
background: rgba(0,0,0,0.35);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<view class="one">
|
||||
<view class="barsOne" @click="showDrawer('showLeft')" ><uni-icons type="bars" size="30" class="barsIcons"></uni-icons></view>
|
||||
<view class="scanOne"><uni-icons type="scan" size="30" class="scanIcons"></uni-icons></view>
|
||||
<view class="scanOne" @click="codeData"><uni-icons type="scan" size="30" class="scanIcons"></uni-icons></view>
|
||||
<view class="gearOne" @click="goSetting"><uni-icons type="gear" size="30" class="gearIcons"></uni-icons></view>
|
||||
</view>
|
||||
|
||||
|
|
@ -308,6 +308,13 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
//扫码
|
||||
codeData(){
|
||||
uni.showToast({
|
||||
title: `扫码`,
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
// 新增:切换选中店铺的方法
|
||||
selectShop(type) {
|
||||
this.selectedShop = type;
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@
|
|||
<view class="goods-info">
|
||||
<text class="goods-name">{{ item.productName }}</text>
|
||||
<text class="goods-barcode">{{ item.productBarCode }}</text>
|
||||
<text class="goods-price">¥{{ item.storePrice ? item.storePrice.toFixed(2) : '0.00' }}</text>
|
||||
<text class="goods-price">¥{{ item.storePrice ? (item.storePrice * 1).toFixed(2) : '0.00' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-divider"></view>
|
||||
|
|
@ -402,10 +402,10 @@
|
|||
},
|
||||
|
||||
addNewGoods() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/addProduct/addProduct'
|
||||
// url: '/pages/enter/enter'
|
||||
});
|
||||
uni.navigateTo({
|
||||
url: '/pages/translation/translation'
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
batchOperation() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,636 @@
|
|||
<template>
|
||||
<!-- 扫码页面整体容器 -->
|
||||
<view class="scan-page">
|
||||
<!-- 顶部扫描卡片(摄像头预览 + 叠加提示 + 操作按钮) -->
|
||||
<view class="scan-card" :class="{ zoomed: zoomed }">
|
||||
<!-- App(真机打包):使用 HTML5+ 原生条码识别视图,嵌入在当前 card 区域 -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view class="camera-host"><!-- 仅用于测量与承载原生扫码视图 --></view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 微信小程序:使用 camera 组件的扫码模式进行连续识别 -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<camera
|
||||
class="camera-view"
|
||||
mode="scanCode"
|
||||
device-position="back"
|
||||
flash="off"
|
||||
@scancode="onScanResult"
|
||||
>
|
||||
<!-- 双击捕获层:用于切换放大/缩小视角 -->
|
||||
<cover-view class="wx-tap-capture" @tap="onTapArea"></cover-view>
|
||||
<cover-view class="wx-overlay">
|
||||
<cover-view class="wx-overlay-tips">对准商品条码,自动识别</cover-view>
|
||||
<cover-view class="wx-corners">
|
||||
<cover-view class="wx-corner tl"></cover-view>
|
||||
<cover-view class="wx-corner tr"></cover-view>
|
||||
<cover-view class="wx-corner bl"></cover-view>
|
||||
<cover-view class="wx-corner br"></cover-view>
|
||||
</cover-view>
|
||||
<cover-view class="wx-controls">
|
||||
<cover-view class="wx-btn" @tap="toggleTorch">
|
||||
<cover-view>{{ flashOn ? '关闭手电' : '开启手电' }}</cover-view>
|
||||
</cover-view>
|
||||
<cover-view class="wx-btn" @tap="togglePause">
|
||||
<cover-view>{{ paused ? '继续扫码' : '暂停扫码' }}</cover-view>
|
||||
</cover-view>
|
||||
</cover-view>
|
||||
</cover-view>
|
||||
</camera>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 其它平台:回退提示(下方按钮触发系统扫码) -->
|
||||
<!-- #ifndef APP-PLUS || MP-WEIXIN -->
|
||||
<view class="camera-fallback">
|
||||
<text class="fallback-text">当前平台不支持内嵌连续扫码,请使用下方按钮进行扫码</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 叠加层(提示文案、四角描边、控制按钮)- 非 App/微信小程序平台 -->
|
||||
<!-- #ifndef APP-PLUS || MP-WEIXIN -->
|
||||
<view class="tap-capture" @tap="onTapArea"></view>
|
||||
<view class="overlay">
|
||||
<text class="overlay-tips">对准商品条码,自动识别</text>
|
||||
<view class="corners">
|
||||
<view class="corner tl"></view>
|
||||
<view class="corner tr"></view>
|
||||
<view class="corner bl"></view>
|
||||
<view class="corner br"></view>
|
||||
</view>
|
||||
<view class="controls">
|
||||
<view class="control-btn" @tap="toggleTorch">
|
||||
<text>{{ flashOn ? '关闭手电' : '开启手电' }}</text>
|
||||
</view>
|
||||
<view class="control-btn" @tap="togglePause">
|
||||
<text>{{ paused ? '继续扫码' : '暂停扫码' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
<!-- App 平台:将控制按钮放在扫描卡片下方,避免被原生视图覆盖 -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view class="controls controls-bar">
|
||||
<view class="control-btn" @tap="toggleTorch">
|
||||
<text>{{ flashOn ? '关闭手电' : '开启手电' }}</text>
|
||||
</view>
|
||||
<view class="control-btn" @tap="togglePause">
|
||||
<text>{{ paused ? '继续扫码' : '暂停扫码' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- 下方内容卡片(占位样式与截图一致) -->
|
||||
<view class="empty-card">
|
||||
<view class="empty-icon"></view>
|
||||
<text class="empty-title">暂未录入商品</text>
|
||||
<view class="empty-tip">
|
||||
<text>请在上方选择商品录入方式</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 回退平台的操作按钮:调用系统扫码 -->
|
||||
<!-- #ifndef APP-PLUS || MP-WEIXIN -->
|
||||
<view class="h5-actions">
|
||||
<button type="primary" class="scan-btn" @tap="triggerSystemScan">开始扫码</button>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { PostData } from '@/api/translation.js'
|
||||
import { getStoreId } from '@/utils/auth'
|
||||
/*
|
||||
页面功能说明:
|
||||
1)支持条形码与二维码识别
|
||||
2)支持打开/关闭手电筒(闪光灯)
|
||||
3)支持暂停/继续扫码
|
||||
4)根据平台差异选择实现:
|
||||
- App(APP-PLUS):使用 HTML5+ 的 plus.barcode 原生视图,连续识别并可控闪光灯
|
||||
- 微信小程序(MP-WEIXIN):使用 camera 组件的 scanCode 模式,事件回调处理结果,并通过 CameraContext 控制闪光灯
|
||||
- 其它平台(如 H5):使用 uni.scanCode 作为回退方案(不嵌入连续预览)
|
||||
*/
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
storeId:null,
|
||||
// 是否暂停识别
|
||||
paused: false,
|
||||
// 手电筒(闪光灯)开关状态
|
||||
flashOn: false,
|
||||
// 视角放大/缩小状态(双击切换)
|
||||
zoomed: false,
|
||||
// 最近一次点击时间(用于双击判断)
|
||||
lastTapTime: 0,
|
||||
// 最近一次识别结果(仅示例展示,可用于业务处理)
|
||||
lastResult: '',
|
||||
// 去重防抖:上一条结果与时间
|
||||
lastScanValue: '',
|
||||
lastScanAt: 0,
|
||||
// App 原生条码视图实例
|
||||
barcodeView: null,
|
||||
// 小程序摄像头上下文
|
||||
cameraCtx: null,
|
||||
// 自动重新扫码的定时器
|
||||
rescanTimer: null
|
||||
}
|
||||
},
|
||||
onReady() {
|
||||
// 页面渲染完成后,按平台初始化扫码能力
|
||||
// #ifdef APP-PLUS
|
||||
this.initAppScanner()
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
this.initMpScanner()
|
||||
// #endif
|
||||
},
|
||||
onUnload() {
|
||||
// 页面卸载时,释放资源
|
||||
// #ifdef APP-PLUS
|
||||
this.destroyAppScanner()
|
||||
// #endif
|
||||
},
|
||||
onLoad() {
|
||||
this.storeId = getStoreId()
|
||||
},
|
||||
methods: {
|
||||
/* ========== 通用:处理识别结果 ========== */
|
||||
async handleScanResult(type, result) {
|
||||
// 暂停时忽略结果
|
||||
if (this.paused) return
|
||||
this.lastResult = typeof result === 'string' ? result.trim() : String(result || '')
|
||||
// 防抖与去重:相同结果在 1200ms 内忽略
|
||||
const now = Date.now()
|
||||
if (this.lastScanValue === this.lastResult && (now - this.lastScanAt) < 1200) {
|
||||
return
|
||||
}
|
||||
this.lastScanValue = this.lastResult
|
||||
this.lastScanAt = now
|
||||
// 判断是否为二维码,是则提示请扫条形码;否则跳转到添加商品页面并传递条码
|
||||
if (this.isQRType(type)) {
|
||||
uni.showToast({ title: '请扫条形码', icon: 'none' })
|
||||
this.rescanAfter(1200)
|
||||
return
|
||||
}
|
||||
// 非二维码但格式不符合条形码要求时,提示并自动重新扫码
|
||||
if (!this.isValidBarcode(this.lastResult)) {
|
||||
uni.showToast({ title: '请扫条形码', icon: 'none' })
|
||||
this.rescanAfter(1200)
|
||||
return
|
||||
}
|
||||
// 扫到条形码后自动关闭手电筒
|
||||
this.setTorch(false)
|
||||
let query = {
|
||||
"barcodes": [this.lastResult],
|
||||
"storeId":this.storeId
|
||||
}
|
||||
let data = '';
|
||||
PostData(query).then(res=>{
|
||||
if(res.code == 200){
|
||||
data = res.data;
|
||||
uni.navigateTo({
|
||||
url: `/pages/addProduct/addProduct?barcode=${this.lastResult}&fromData=${JSON.stringify(data)}`
|
||||
})
|
||||
}
|
||||
else{
|
||||
uni.showToast({ title: res.msg, icon: 'none' })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
},
|
||||
|
||||
// 判断是否为二维码(适配不同平台的类型标识)
|
||||
isQRType(t) {
|
||||
// App 原生视图:使用 plus.barcode 常量
|
||||
// #ifdef APP-PLUS
|
||||
if (typeof t === 'number') {
|
||||
return t === plus.barcode.QR
|
||||
}
|
||||
// #endif
|
||||
// 小程序/H5:字符串类型,包含 QR 即认为是二维码
|
||||
if (typeof t === 'string') {
|
||||
return t.toUpperCase().indexOf('QR') !== -1
|
||||
}
|
||||
return false
|
||||
},
|
||||
// 简单条形码校验:纯数字且长度 8-20
|
||||
isValidBarcode(code) {
|
||||
return /^\d{8,20}$/.test(code || '')
|
||||
},
|
||||
// 双击切换视角(放大/缩小),单击不触发
|
||||
onTapArea() {
|
||||
// App 平台使用原生条码视图,页面缩放与原生视图不一致,禁用双击缩放
|
||||
// #ifdef APP-PLUS
|
||||
return
|
||||
// #endif
|
||||
const now = Date.now()
|
||||
if (now - this.lastTapTime <= 300) {
|
||||
this.zoomed = !this.zoomed
|
||||
this.lastTapTime = 0
|
||||
} else {
|
||||
this.lastTapTime = now
|
||||
}
|
||||
},
|
||||
// 自动重新扫码(根据平台差异处理)
|
||||
rescanAfter(delay = 800) {
|
||||
if (this.rescanTimer) {
|
||||
clearTimeout(this.rescanTimer)
|
||||
this.rescanTimer = null
|
||||
}
|
||||
this.rescanTimer = setTimeout(() => {
|
||||
// #ifdef APP-PLUS
|
||||
if (this.barcodeView) {
|
||||
try {
|
||||
this.barcodeView.cancel && this.barcodeView.cancel()
|
||||
this.barcodeView.start && this.barcodeView.start()
|
||||
} catch (e) {}
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
// 小程序 camera 的 scanCode 为持续识别,无需主动重启
|
||||
// #endif
|
||||
// #ifndef APP-PLUS || MP-WEIXIN
|
||||
this.triggerSystemScan()
|
||||
// #endif
|
||||
this.rescanTimer = null
|
||||
}, delay)
|
||||
},
|
||||
|
||||
/* ========== 控制:手电筒开关 ========== */
|
||||
toggleTorch() {
|
||||
this.setTorch(!this.flashOn)
|
||||
},
|
||||
// 显式设置手电筒开关(用于扫码成功后关闭)
|
||||
setTorch(isOn) {
|
||||
this.flashOn = !!isOn
|
||||
// App
|
||||
// #ifdef APP-PLUS
|
||||
if (this.barcodeView && typeof this.barcodeView.setFlash === 'function') {
|
||||
this.barcodeView.setFlash(this.flashOn)
|
||||
}
|
||||
// #endif
|
||||
// 微信小程序
|
||||
// #ifdef MP-WEIXIN
|
||||
if (this.cameraCtx && typeof this.cameraCtx.setTorch === 'function') {
|
||||
this.cameraCtx.setTorch({ isTorchOn: this.flashOn })
|
||||
}
|
||||
// #endif
|
||||
// 其它平台
|
||||
// #ifndef APP-PLUS || MP-WEIXIN
|
||||
if (isOn) {
|
||||
uni.showToast({ title: '当前平台不支持手电筒', icon: 'none' })
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
/* ========== 控制:暂停/继续扫码 ========== */
|
||||
togglePause() {
|
||||
this.paused = !this.paused
|
||||
// App:调用 cancel()/start() 控制识别流程
|
||||
// #ifdef APP-PLUS
|
||||
if (this.barcodeView) {
|
||||
if (this.paused) {
|
||||
this.barcodeView.cancel && this.barcodeView.cancel()
|
||||
} else {
|
||||
this.barcodeView.start && this.barcodeView.start()
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
// 微信小程序:事件回调里根据 paused 忽略结果即可
|
||||
// 其它平台:系统扫码为一次性,无需处理
|
||||
},
|
||||
|
||||
/* ========== H5/其它:系统扫码回退 ========== */
|
||||
async triggerSystemScan() {
|
||||
let query = {
|
||||
"barcodes": ['6901028064835'],
|
||||
"storeId":'2'
|
||||
}
|
||||
PostData(query).then(res=>{
|
||||
if(res.code == 200){
|
||||
let data = res.data;
|
||||
uni.navigateTo({
|
||||
url: `/pages/addProduct/addProduct?barcode=6901028064835&fromData=${JSON.stringify(data)}`
|
||||
})
|
||||
}
|
||||
else{
|
||||
uni.showToast({ title: res.msg, icon: 'none' })
|
||||
}
|
||||
})
|
||||
// 使用系统提供的扫码能力(一次性)
|
||||
uni.scanCode({
|
||||
onlyFromCamera: true,
|
||||
success: (res) => {
|
||||
// res.result 为识别出的文本内容
|
||||
this.handleScanResult(res.scanType || 'UNKNOWN', res.result)
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '扫码失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/* ========== 微信小程序:初始化摄像头上下文 ========== */
|
||||
// 小程序 camera 的识别结果事件
|
||||
onScanResult(e) {
|
||||
// e.detail 包含 { result, scanType, charSet, rawData } 等
|
||||
const { result, scanType } = e.detail || {}
|
||||
this.handleScanResult(scanType || 'WEAPP', result || '')
|
||||
},
|
||||
initMpScanner() {
|
||||
// 创建摄像头上下文,用于控制手电筒
|
||||
try {
|
||||
this.cameraCtx = uni.createCameraContext()
|
||||
} catch (err) {
|
||||
this.cameraCtx = null
|
||||
}
|
||||
},
|
||||
|
||||
/* ========== App:创建原生条码识别视图 ========== */
|
||||
initAppScanner() {
|
||||
// 通过选择器获取扫描卡片的尺寸,用以放置原生 Barcode 视图
|
||||
this.$nextTick(() => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('.scan-card')
|
||||
.boundingClientRect((rect) => {
|
||||
if (!rect) return
|
||||
// 获取当前页面的原生 Webview
|
||||
const webview = this.$scope && this.$scope.$getAppWebview
|
||||
? this.$scope.$getAppWebview()
|
||||
: (this.$mp && this.$mp.page && this.$mp.page.$getAppWebview
|
||||
? this.$mp.page.$getAppWebview()
|
||||
: null)
|
||||
if (!webview) return
|
||||
|
||||
// 创建原生条码识别视图(支持二维码与常见条形码)
|
||||
const types = [
|
||||
plus.barcode.QR,
|
||||
plus.barcode.EAN13,
|
||||
plus.barcode.EAN8,
|
||||
plus.barcode.UPCA,
|
||||
plus.barcode.UPCE,
|
||||
plus.barcode.CODE128,
|
||||
plus.barcode.CODE39,
|
||||
plus.barcode.ITF,
|
||||
plus.barcode.PDF417,
|
||||
plus.barcode.DATAMATRIX
|
||||
]
|
||||
const styles = {
|
||||
top: rect.top + 'px',
|
||||
left: rect.left + 'px',
|
||||
width: rect.width + 'px',
|
||||
height: rect.height + 'px'
|
||||
}
|
||||
const barcode = plus.barcode.create('scan-barcode', types, {
|
||||
frameColor: '#FFFFFF',
|
||||
scanbarColor: '#FFFFFF'
|
||||
})
|
||||
barcode.setStyle(styles)
|
||||
// 将原生视图添加到页面
|
||||
webview.append(barcode)
|
||||
// 识别结果事件
|
||||
barcode.onmarked = (type, result) => {
|
||||
this.handleScanResult(type, result)
|
||||
}
|
||||
// 启动识别
|
||||
barcode.start()
|
||||
this.barcodeView = barcode
|
||||
})
|
||||
.exec()
|
||||
})
|
||||
},
|
||||
destroyAppScanner() {
|
||||
// 关闭并释放原生条码视图
|
||||
if (this.barcodeView && typeof this.barcodeView.close === 'function') {
|
||||
try {
|
||||
this.barcodeView.close()
|
||||
} catch (e) {}
|
||||
this.barcodeView = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 页面背景与基本布局 */
|
||||
.scan-page {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 顶部扫描卡片 */
|
||||
.scan-card {
|
||||
position: relative;
|
||||
background: #000;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
height: 240px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.scan-card.zoomed {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
.camera-host {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.camera-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
/* 微信小程序覆盖层(使用 cover-view) */
|
||||
/* 双击捕获层(非小程序使用 view,微信小程序使用 cover-view) */
|
||||
.tap-capture {
|
||||
position: absolute;
|
||||
left: 0; top: 0; right: 0; bottom: 0;
|
||||
z-index: 2;
|
||||
background: transparent;
|
||||
}
|
||||
.wx-tap-capture {
|
||||
position: absolute;
|
||||
left: 0; top: 0; right: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.wx-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.wx-overlay-tips {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 10px;
|
||||
transform: translateX(-50%);
|
||||
color: #fff;
|
||||
background: rgba(0,0,0,0.35);
|
||||
padding: 6px 12px;
|
||||
border-radius: 18px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.wx-corners .wx-corner {
|
||||
position: absolute;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
.wx-corners .tl { left: 10px; top: 10px; border-right: none; border-bottom: none; }
|
||||
.wx-corners .tr { right: 10px; top: 10px; border-left: none; border-bottom: none; }
|
||||
.wx-corners .bl { left: 10px; bottom: 10px; border-right: none; border-top: none; }
|
||||
.wx-corners .br { right: 10px; bottom: 10px; border-left: none; border-top: none; }
|
||||
.wx-controls {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 28px;
|
||||
}
|
||||
.wx-btn {
|
||||
min-width: 96px;
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 18px;
|
||||
background: rgba(0,0,0,0.45);
|
||||
border: 1px solid rgba(255,255,255,0.35);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
.camera-fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #222;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.fallback-text {
|
||||
color: #bbb;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 叠加层(提示 + 四角 + 控制按钮) */
|
||||
.overlay {
|
||||
pointer-events: none; /* 使叠加层不阻挡相机事件,按钮区域再单独打开事件 */
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.overlay-tips {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 10px;
|
||||
transform: translateX(-50%);
|
||||
color: #fff;
|
||||
background: rgba(0,0,0,0.35);
|
||||
padding: 6px 12px;
|
||||
border-radius: 18px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.corners .corner {
|
||||
position: absolute;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
.corners .tl { left: 10px; top: 10px; border-right: none; border-bottom: none; }
|
||||
.corners .tr { right: 10px; top: 10px; border-left: none; border-bottom: none; }
|
||||
.corners .bl { left: 10px; bottom: 10px; border-right: none; border-top: none; }
|
||||
.corners .br { right: 10px; bottom: 10px; border-left: none; border-top: none; }
|
||||
|
||||
.controls {
|
||||
pointer-events: auto; /* 允许点击按钮 */
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 28px;
|
||||
}
|
||||
.control-btn {
|
||||
min-width: 96px;
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 18px;
|
||||
background: rgba(0,0,0,0.45);
|
||||
border: 1px solid rgba(255,255,255,0.35);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.control-btn text {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* App 平台下方控制条 */
|
||||
.controls-bar {
|
||||
margin-top: 8px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 28px;
|
||||
}
|
||||
|
||||
/* 下方内容卡片 */
|
||||
.empty-card {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 24px 16px 28px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.empty-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 30px;
|
||||
background: #ffecec;
|
||||
}
|
||||
.empty-title {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
||||
.empty-tip {
|
||||
margin-top: 4px;
|
||||
background: #f7f7f7;
|
||||
padding: 8px 12px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
.empty-tip text {
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* H5 回退按钮 */
|
||||
.h5-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.scan-btn {
|
||||
width: 200px;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
const apiUrl = 'http://193.112.94.36:8081'
|
||||
|
||||
export default apiUrl
|
||||
Loading…
Reference in New Issue