ry_app/pages/enter/enter.vue

676 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>