ry_app/pages/enter/enter.vue

676 lines
19 KiB
Vue
Raw Normal View History

2026-01-19 16:52:24 +08:00
<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>