676 lines
19 KiB
Vue
676 lines
19 KiB
Vue
<template>
|
||
<view class="input-list-page">
|
||
<!-- 顶部导航栏 -->
|
||
<view class="navbar">
|
||
<view class="nav-left" @click="goBack">
|
||
<uni-icons type="left" size="20" color="#fff"></uni-icons>
|
||
</view>
|
||
<view class="nav-title">录入清单</view>
|
||
<view class="nav-right" @click="goRecord">
|
||
<uni-icons type="redo" size="18" color="#fff"></uni-icons>
|
||
<text class="record-text">记录</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 功能操作栏 -->
|
||
<view class="operation-bar">
|
||
<view class="search-box">
|
||
<uni-icons type="search" size="16" color="#999"></uni-icons>
|
||
<input type="text" placeholder="搜索商品名称、条码" placeholder-class="search-placeholder" />
|
||
</view>
|
||
<button class="btn batch-import" @click="goImport">批量导入</button>
|
||
<button class="btn no-code">无码/生鲜</button>
|
||
</view>
|
||
|
||
<!-- 条码扫描区域 【修复:调整定位和层级】 -->
|
||
<view class="scan-area">
|
||
<view class="scan-tip">对准商品条码,自动识别</view>
|
||
|
||
<!-- App环境使用扫码控件 【这里的view只做占位,扫码控件是原生图层叠加】 -->
|
||
<!-- #ifdef APP-PLUS -->
|
||
<view class="scan-view" id="barcode-view"></view>
|
||
<view class="scan-status" v-if="isScanning">
|
||
<view class="scan-line"></view>
|
||
<text class="scan-status-text">正在扫描...</text>
|
||
</view>
|
||
<view class="scan-controls" v-if="isScanning">
|
||
<view class="control-item" @click.stop="toggleFlash">
|
||
<uni-icons type="flash" size="22" color="#fff"></uni-icons>
|
||
<text class="control-text">{{ flashOn ? '关闭手电' : '开启手电' }}</text>
|
||
</view>
|
||
<view class="control-item" @click.stop="pauseScan">
|
||
<uni-icons type="pause" size="22" color="#fff"></uni-icons>
|
||
<text class="control-text">{{ scanPaused ? '继续扫码' : '暂停扫码' }}</text>
|
||
</view>
|
||
<view class="control-item" @click.stop="openFullscreenScan">
|
||
<uni-icons type="scan" size="22" color="#fff"></uni-icons>
|
||
<text class="control-text">全屏扫码</text>
|
||
</view>
|
||
</view>
|
||
<!-- #endif -->
|
||
|
||
<!-- H5环境显示提示 -->
|
||
<!-- #ifndef APP-PLUS -->
|
||
<view class="h5-tip">
|
||
<text class="tip-text">扫码功能需要在App中使用</text>
|
||
<button class="scan-btn" @click="startScan">开始扫码</button>
|
||
</view>
|
||
<!-- #endif -->
|
||
</view>
|
||
|
||
<!-- 空状态区域 -->
|
||
<view class="empty-area">
|
||
<image src="/static/empty-icon.png" mode="widthFix" class="empty-icon"></image>
|
||
<text class="empty-title">暂未录入商品</text>
|
||
<text class="empty-desc">请在上方选择商品录入方式</text>
|
||
<uni-icons type="arrowup" size="14" color="#ccc"></uni-icons>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
flashOn: false,
|
||
scanPaused: false,
|
||
isScanning: false,
|
||
barcodeInstance: null
|
||
};
|
||
},
|
||
// ✅ 修复核心:用mounted初始化,页面DOM渲染完成后再创建扫码控件
|
||
mounted() {
|
||
// #ifdef APP-PLUS
|
||
console.log('=== 扫码页面 mounted ===');
|
||
this.initScan();
|
||
// #endif
|
||
},
|
||
onShow() {
|
||
// #ifdef APP-PLUS
|
||
console.log('=== 扫码页面 onShow ===');
|
||
// 每次页面显示时都检查权限并初始化(处理从设置返回的情况)
|
||
this.initScan();
|
||
// #endif
|
||
},
|
||
onUnload() {
|
||
this.destroyScan(); // 统一销毁扫码控件
|
||
},
|
||
methods: {
|
||
goBack() {
|
||
this.destroyScan(); // 返回时销毁
|
||
uni.navigateBack({ delta: 1 });
|
||
},
|
||
|
||
goRecord() {
|
||
uni.showToast({ title: '记录功能开发中', icon: 'none' });
|
||
},
|
||
|
||
goImport() {
|
||
uni.navigateTo({
|
||
url: '/pages/Import /Import '
|
||
});
|
||
},
|
||
|
||
// ✅ 新增:统一的初始化方法(处理权限和扫码初始化)
|
||
initScan() {
|
||
console.log('开始初始化扫码流程...');
|
||
console.log('当前平台:', uni.getSystemInfoSync().platform);
|
||
console.log('plus对象是否存在:', !!plus);
|
||
console.log('plus.barcode是否存在:', !!(plus && plus.barcode));
|
||
|
||
// 先申请相机权限,再初始化扫码
|
||
this.requestCameraAuth().then(() => {
|
||
console.log('相机权限已获取,开始初始化扫码');
|
||
this.initBarcodeScan();
|
||
}).catch(err => {
|
||
console.error('相机权限获取失败:', err);
|
||
|
||
// 显示友好的权限提示
|
||
uni.showModal({
|
||
title: '权限提示',
|
||
content: '需要相机权限才能使用扫码功能\n\n请点击"去设置"开启相机权限',
|
||
confirmText: '去设置',
|
||
cancelText: '取消',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// 跳转到系统设置页面
|
||
if (uni.getSystemInfoSync().platform === 'android') {
|
||
const Intent = plus.android.importClass('android.content.Intent');
|
||
const Settings = plus.android.importClass('android.provider.Settings');
|
||
const Uri = plus.android.importClass('android.net.Uri');
|
||
|
||
const intent = new Intent();
|
||
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||
const uri = Uri.fromParts('package', plus.android.runtimeActivity.getPackageName(), null);
|
||
intent.setData(uri);
|
||
|
||
plus.android.runtimeMainActivity().startActivity(intent);
|
||
} else {
|
||
// iOS 跳转到设置
|
||
plus.runtime.openURL('app-settings:');
|
||
}
|
||
} else {
|
||
// 用户取消,返回上一页
|
||
uni.navigateBack({ delta: 1 });
|
||
}
|
||
}
|
||
});
|
||
});
|
||
},
|
||
|
||
// ✅ 新增:统一销毁扫码控件,防止内存泄漏
|
||
destroyScan() {
|
||
if (this.barcodeInstance) {
|
||
this.barcodeInstance.close();
|
||
this.barcodeInstance = null;
|
||
}
|
||
this.isScanning = false;
|
||
this.scanPaused = false;
|
||
this.flashOn = false;
|
||
},
|
||
|
||
// ✅ 新增:动态申请相机权限(支持 Android 和 iOS)
|
||
requestCameraAuth() {
|
||
return new Promise((resolve, reject) => {
|
||
try {
|
||
const platform = uni.getSystemInfoSync().platform;
|
||
console.log('当前平台:', platform);
|
||
|
||
if (platform === 'android') {
|
||
// Android 平台权限申请
|
||
console.log('Android 平台,开始申请相机权限...');
|
||
|
||
// 先检查是否已有权限
|
||
const main = plus.android.runtimeMainActivity();
|
||
const Permission = plus.android.importClass('android.Manifest.permission');
|
||
const PackageManager = plus.android.importClass('android.content.pm.PackageManager');
|
||
|
||
const permission = Permission.CAMERA;
|
||
const result = main.checkSelfPermission(permission);
|
||
|
||
console.log('Android 权限检查结果:', result);
|
||
|
||
if (result === PackageManager.PERMISSION_GRANTED) {
|
||
console.log('Android 已有相机权限');
|
||
resolve();
|
||
} else {
|
||
console.log('Android 需要申请相机权限');
|
||
plus.android.requestPermissions(
|
||
['android.permission.CAMERA'],
|
||
(res) => {
|
||
console.log('Android 权限请求回调:', res);
|
||
if (res && res.length > 0) {
|
||
const authResult = res[0].granted;
|
||
console.log('Android 相机权限授予结果:', authResult);
|
||
if (authResult) {
|
||
resolve();
|
||
} else {
|
||
reject('用户拒绝了相机权限');
|
||
}
|
||
} else {
|
||
reject('权限请求返回异常');
|
||
}
|
||
},
|
||
(err) => {
|
||
console.error('Android 权限请求失败:', err);
|
||
reject(err);
|
||
}
|
||
);
|
||
}
|
||
} else if (platform === 'ios') {
|
||
// iOS 平台权限申请
|
||
console.log('iOS 平台,相机权限在使用时自动申请');
|
||
|
||
// iOS 的相机权限通常在第一次使用相机时自动申请
|
||
// 我们可以直接尝试初始化扫码,如果权限被拒绝会触发系统弹窗
|
||
resolve();
|
||
} else {
|
||
console.log('非移动端平台,跳过权限申请');
|
||
resolve();
|
||
}
|
||
} catch (error) {
|
||
console.error('权限申请异常:', error);
|
||
reject(error);
|
||
}
|
||
});
|
||
},
|
||
|
||
// ✅ 修复所有参数错误的扫码初始化核心方法
|
||
initBarcodeScan() {
|
||
if (!plus) {
|
||
console.log('plus 对象不存在,无法初始化扫码');
|
||
return;
|
||
}
|
||
|
||
// 如果已有扫码实例,先销毁再重新创建
|
||
if (this.barcodeInstance) {
|
||
console.log('已存在扫码实例,先销毁再重新创建');
|
||
this.destroyScan();
|
||
}
|
||
|
||
console.log('开始初始化扫码控件');
|
||
this.isScanning = true;
|
||
this.scanPaused = false;
|
||
|
||
// 获取屏幕宽高和系统信息,适配所有机型【无单位纯数字,核心修复】
|
||
const sysInfo = uni.getSystemInfoSync();
|
||
const statusBarHeight = sysInfo.statusBarHeight || 0;
|
||
const navBarHeight = 44;
|
||
const totalNavBarHeight = statusBarHeight + navBarHeight;
|
||
|
||
const scanWidth = sysInfo.windowWidth - 30; // 左右各留15px边距
|
||
const scanHeight = sysInfo.windowWidth * 0.7; // 宽高比适配,扫码框更美观
|
||
const scanTop = totalNavBarHeight + 80; // 动态计算顶部距离(导航栏+操作栏+间距)
|
||
|
||
console.log('系统信息:', {
|
||
statusBarHeight,
|
||
navBarHeight,
|
||
totalNavBarHeight,
|
||
scanTop,
|
||
scanWidth,
|
||
scanHeight
|
||
});
|
||
|
||
// 创建扫码控件 【修复:所有尺寸参数为纯数字,无px】
|
||
const barcode = plus.barcode.create('barcode',
|
||
[plus.barcode.CODE_128, plus.barcode.EAN_13, plus.barcode.EAN_8, plus.barcode.QR], // 增加常用码制,识别更广
|
||
{
|
||
top: scanTop,
|
||
left: 15,
|
||
width: scanWidth,
|
||
height: scanHeight,
|
||
scanbarColor: '#e60012', // 扫码线颜色
|
||
background: '#000',
|
||
frameColor: '#e60012', // 扫码框颜色
|
||
scanbarRate: 2, // 扫码线速度
|
||
scanbarStyle: 'style-radar', // 扫码线样式
|
||
conserve: true, // 保持扫码状态
|
||
filename: '_doc/barcode/' // 保存路径
|
||
}
|
||
);
|
||
|
||
console.log('扫码控件创建成功');
|
||
|
||
// 扫码成功回调
|
||
barcode.onmarked = (type, result) => {
|
||
if (!result) return; // 过滤空结果
|
||
console.log('识别成功:', result);
|
||
this.isScanning = false;
|
||
uni.showModal({
|
||
title: '扫码成功',
|
||
content: `获取到的编码号:${result}`,
|
||
showCancel: false,
|
||
success: () => {
|
||
// ✅ 修复:扫码成功后完整重置状态+继续扫码
|
||
this.isScanning = true;
|
||
this.resumeScan();
|
||
}
|
||
});
|
||
};
|
||
|
||
// 扫码错误回调
|
||
barcode.onerror = (error) => {
|
||
console.error('扫码错误:', error);
|
||
uni.showToast({ title: '扫码异常,请重试', icon: 'none' });
|
||
};
|
||
|
||
// 挂载到当前页面
|
||
const currentWebview = plus.webview.currentWebview();
|
||
console.log('当前webview:', currentWebview);
|
||
console.log('扫码控件配置:', {
|
||
top: scanTop,
|
||
left: 15,
|
||
width: scanWidth,
|
||
height: scanHeight
|
||
});
|
||
|
||
currentWebview.append(barcode);
|
||
console.log('✅ 扫码控件已成功挂载到webview');
|
||
|
||
// 启动扫码并添加回调处理
|
||
barcode.start((result) => {
|
||
console.log('扫码启动回调:', result);
|
||
if (result.code === 0) {
|
||
console.log('✅ 扫码已成功启动,相机画面应该显示');
|
||
this.barcodeInstance = barcode;
|
||
|
||
// 延迟检查扫码控件状态
|
||
setTimeout(() => {
|
||
console.log('扫码控件状态检查:', {
|
||
exists: !!this.barcodeInstance,
|
||
isScanning: this.isScanning
|
||
});
|
||
}, 1000);
|
||
} else {
|
||
console.error('❌ 扫码启动失败:', result.message);
|
||
uni.showModal({
|
||
title: '扫码启动失败',
|
||
content: `错误信息: ${result.message || '未知错误'}\n请检查相机权限是否已开启`,
|
||
showCancel: false
|
||
});
|
||
}
|
||
});
|
||
|
||
console.log('扫码控件已挂载,正在启动...');
|
||
},
|
||
|
||
// H5环境扫码(不变,兼容用)
|
||
startScan() {
|
||
uni.scanCode({
|
||
success: (res) => {
|
||
uni.showModal({ title: '扫码成功', content: `获取到的编码号:${res.result}`, showCancel: false });
|
||
},
|
||
fail: () => uni.showToast({ title: '扫码失败', icon: 'none' })
|
||
});
|
||
},
|
||
|
||
// 暂停/继续扫码(逻辑优化)
|
||
pauseScan() {
|
||
if (!this.barcodeInstance) return;
|
||
this.scanPaused = !this.scanPaused;
|
||
this.scanPaused ? this.barcodeInstance.pause() : this.barcodeInstance.resume();
|
||
uni.showToast({ title: this.scanPaused ? '扫码已暂停' : '扫码已继续', icon: 'none' });
|
||
},
|
||
|
||
// 恢复扫码
|
||
resumeScan() {
|
||
if (this.barcodeInstance && this.scanPaused) {
|
||
this.barcodeInstance.resume();
|
||
this.scanPaused = false;
|
||
this.isScanning = true;
|
||
}
|
||
},
|
||
|
||
// 手电开关(不变,逻辑正常)
|
||
toggleFlash() {
|
||
if (!this.barcodeInstance) return;
|
||
this.flashOn = !this.flashOn;
|
||
this.barcodeInstance.setFlash(this.flashOn);
|
||
uni.showToast({ title: this.flashOn ? '手电已开启' : '手电已关闭', icon: 'none' });
|
||
},
|
||
|
||
// 打开全屏扫码(使用 uni.scanCode 原生API)
|
||
openFullscreenScan() {
|
||
console.log('打开全屏扫码');
|
||
|
||
// 暂停嵌入式扫码
|
||
if (this.barcodeInstance) {
|
||
this.barcodeInstance.pause();
|
||
}
|
||
|
||
// 调用 uniapp 原生扫码API,核心配置
|
||
uni.scanCode({
|
||
scanType: ['barCode', 'qrCode'], // 支持条形码和二维码
|
||
onlyFromCamera: true, // 只使用相机,不从相册选择
|
||
success: (res) => {
|
||
console.log('全屏扫码成功:', res);
|
||
|
||
// 显示扫码结果
|
||
uni.showModal({
|
||
title: '扫码成功',
|
||
content: `获取到的编码号:${res.result}`,
|
||
showCancel: false,
|
||
success: () => {
|
||
// 恢复嵌入式扫码
|
||
this.resumeScan();
|
||
}
|
||
});
|
||
},
|
||
fail: (err) => {
|
||
console.error('全屏扫码失败:', err);
|
||
|
||
// 恢复嵌入式扫码
|
||
this.resumeScan();
|
||
|
||
if (err.errMsg !== 'scanCode:fail cancel') {
|
||
uni.showToast({
|
||
title: '扫码失败,请重试',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.input-list-page {
|
||
width: 100%;
|
||
height: 100vh;
|
||
background-color: #f5f5f5;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.navbar {
|
||
width: 100%;
|
||
height: calc(var(--status-bar-height) + 44px);
|
||
background-color: #e60012;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 15px;
|
||
box-sizing: border-box;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
z-index: 999;
|
||
}
|
||
|
||
.nav-left, .nav-right {
|
||
display: flex;
|
||
align-items: center;
|
||
color: #fff;
|
||
}
|
||
|
||
.nav-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
}
|
||
|
||
.record-text {
|
||
font-size: 14px;
|
||
margin-left: 5px;
|
||
color: #fff;
|
||
}
|
||
|
||
.operation-bar {
|
||
margin-top: var(--status-bar-height) + 44px;
|
||
width: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 10px 15px;
|
||
box-sizing: border-box;
|
||
background-color: #fff;
|
||
gap: 10px;
|
||
}
|
||
|
||
.search-box {
|
||
flex: 1;
|
||
height: 32px;
|
||
border: 1px solid #eee;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 10px;
|
||
background-color: #f9f9f9;
|
||
}
|
||
|
||
.search-placeholder {
|
||
font-size: 14px;
|
||
color: #999;
|
||
}
|
||
|
||
.btn {
|
||
height: 32px;
|
||
padding: 0 12px;
|
||
font-size: 14px;
|
||
border-radius: 4px;
|
||
background-color: #f5f5f5;
|
||
border: 1px solid #eee;
|
||
color: #333;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* ✅ 修复:扫码区域样式优化,适配原生扫码图层 */
|
||
.scan-area {
|
||
width: 100%;
|
||
padding: 15px;
|
||
box-sizing: border-box;
|
||
position: relative;
|
||
min-height: 350px;
|
||
margin-top: 10px;
|
||
background-color: transparent;
|
||
}
|
||
|
||
.scan-tip {
|
||
position: absolute;
|
||
top: 15px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
background-color: rgba(0,0,0,0.7);
|
||
color: #fff;
|
||
font-size: 14px;
|
||
padding: 4px 12px;
|
||
border-radius: 20px;
|
||
z-index: 10;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.scan-view {
|
||
width: 100%;
|
||
height: 300px;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
position: relative;
|
||
background-color: #000;
|
||
border: 2px solid #e60012;
|
||
}
|
||
|
||
.scan-status {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 9;
|
||
pointer-events: none; /* 不遮挡扫码识别 */
|
||
}
|
||
|
||
.scan-line {
|
||
width: 80%;
|
||
height: 2px;
|
||
background-color: #e60012;
|
||
animation: scanMove 2s infinite;
|
||
}
|
||
|
||
@keyframes scanMove {
|
||
0% { transform: translateY(0); opacity: 0; }
|
||
50% { opacity: 1; }
|
||
100% { transform: translateY(250px); opacity: 0; }
|
||
}
|
||
|
||
.scan-status-text {
|
||
color: #fff;
|
||
font-size: 16px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.scan-controls {
|
||
position: absolute;
|
||
bottom: 15px;
|
||
left: 0;
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: space-around;
|
||
padding: 0 15px;
|
||
box-sizing: border-box;
|
||
z-index: 10;
|
||
}
|
||
|
||
.control-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
padding: 8px 12px;
|
||
border-radius: 8px;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
transition: all 0.3s ease;
|
||
min-width: 60px;
|
||
}
|
||
|
||
.control-item:active {
|
||
background-color: rgba(230, 0, 18, 0.6);
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.control-text {
|
||
font-size: 12px;
|
||
margin-top: 5px;
|
||
text-align: center;
|
||
}
|
||
|
||
.h5-tip {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 60px 20px;
|
||
background-color: #fff;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.tip-text {
|
||
font-size: 16px;
|
||
color: #333;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.scan-btn {
|
||
padding: 12px 30px;
|
||
background-color: #e60012;
|
||
color: #fff;
|
||
border-radius: 8px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.empty-area {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.empty-icon {
|
||
width: 80px;
|
||
height: 80px;
|
||
margin-bottom: 15px;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.empty-title {
|
||
font-size: 16px;
|
||
color: #333;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.empty-desc {
|
||
font-size: 14px;
|
||
color: #999;
|
||
margin-bottom: 10px;
|
||
}
|
||
</style> |