ry_app/pages/enter/enter.vue

635 lines
15 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" @click="goNoCode">/</button>
</view>
<!-- -->
<view class="scan-area">
<view class="scan-tip">对准商品条码自动识别</view>
<!-- App环境扫码 -->
<!-- #ifdef APP-PLUS -->
<view class="scan-view" id="barcode-view"></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>
<!-- #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="goods-list" v-if="goodsList.length > 0">
<view class="goods-item" v-for="(item, index) in goodsList" :key="index">
<view class="goods-info">
<image :src="item.image || '/static/default-goods.png'" class="goods-img"></image>
<view class="goods-detail">
<text class="goods-name">{{ item.name }}</text>
<text class="goods-barcode">{{ item.barcode }}</text>
<text class="goods-price">¥{{ item.price }}</text>
</view>
</view>
<view class="goods-actions">
<view class="action-btn remove" @click="removeGoods(index)"></view>
</view>
<view class="goods-form">
<view class="form-item">
<text class="label required">进货数量</text>
<input type="number" v-model="item.quantity" class="input" placeholder="请输入数量" />
</view>
<view class="form-item">
<text class="label">进货价</text>
<input type="number" v-model="item.price" class="input" placeholder="请输入价格" />
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view class="empty-area" v-else>
<text class="empty-title">暂未录入商品</text>
<text class="empty-desc">请在上方选择商品录入方式</text>
</view>
<!-- 提交按钮 -->
<view class="submit-area">
<button class="submit-btn" @click="submitList" :disabled="goodsList.length === 0"></button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
flashOn: false,
scanPaused: false,
isScanning: false,
barcodeInstance: null,
goodsList: [
{
name: '怡宝饮用纯净水350ml',
barcode: '6901285991240',
price: 1,
quantity: 1,
image: '/static/yibao.png'
}
]
};
},
mounted() {
// #ifdef APP-PLUS
console.log('页面 mounted 触发');
// 关键修复:等待 Webview 完全渲染后再初始化
this.$nextTick(() => {
setTimeout(() => {
this.initScan();
}, 300); // 延迟 300ms 确保原生层准备就绪
});
// #endif
},
onShow() {
// #ifdef APP-PLUS
// 从后台返回或从设置返回时,重新检查
if (!this.barcodeInstance) {
this.$nextTick(() => {
setTimeout(() => {
this.requestCameraAuth().then(() => {
this.initBarcodeScan();
}).catch(err => {
console.log('权限不足,无法重新初始化');
});
}, 300);
});
}
// #endif
},
onUnload() {
this.destroyScan();
},
methods: {
goBack() {
this.destroyScan();
uni.navigateBack({ delta: 1 });
},
goImport() {
uni.navigateTo({ url: '/pages/Import/Import' });
},
goNoCode() {
uni.navigateTo({ url: '/pages/NoCode/NoCode' });
},
goRecord() {
uni.showToast({ title: '记录功能开发中', icon: 'none' });
},
initScan() {
this.requestCameraAuth().then(() => {
this.initBarcodeScan();
}).catch(err => {
console.error('权限获取失败:', err);
uni.showModal({
title: '权限提示',
content: '请前往设置开启相机权限',
confirmText: '去设置',
success: (res) => {
if (res.confirm) {
this.openAppSettings();
}
}
});
});
},
requestCameraAuth() {
return new Promise((resolve, reject) => {
// ... 这里保持原来的权限申请代码不变 ...
// (如果不确定可以保留之前修复的代码)
const platform = uni.getSystemInfoSync().platform;
if (platform === 'android') {
const main = plus.android.runtimeMainActivity();
const Permission = plus.android.importClass('android.Manifest.permission');
const PackageManager = plus.android.importClass('android.content.pm.PackageManager');
if (main.checkSelfPermission(Permission.CAMERA) === PackageManager.PERMISSION_GRANTED) {
resolve();
} else {
plus.android.requestPermissions(['android.permission.CAMERA'], (res) => {
res[0].granted ? resolve() : reject('用户拒绝');
}, reject);
}
} else {
resolve();
}
});
},
openAppSettings() {
// ... 这里保持原来的跳转设置代码不变 ...
try {
plus.android.importClass('android.content.Intent');
plus.android.importClass('android.provider.Settings');
plus.android.importClass('android.net.Uri');
const mainActivity = plus.android.runtimeMainActivity();
const intent = new android.content.Intent();
intent.setAction(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
const uri = android.net.Uri.fromParts('package', mainActivity.getPackageName(), null);
intent.setData(uri);
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
mainActivity.startActivity(intent);
} catch (e) {
uni.showToast({ title: '跳转失败,请手动开启', icon: 'none' });
}
},
initBarcodeScan() {
if (!plus || !plus.barcode) return;
this.destroyScan();
this.isScanning = true;
const sysInfo = uni.getSystemInfoSync();
const statusBarHeight = sysInfo.statusBarHeight || 0;
const navBarHeight = 44;
const totalNavBarHeight = statusBarHeight + navBarHeight;
// 调试:打印高度计算结果
console.log('高度计算:', { statusBarHeight, navBarHeight, totalNavBarHeight });
// 调整位置:确保扫码框在搜索栏下方
const scanTop = totalNavBarHeight + 80;
const scanWidth = sysInfo.windowWidth - 30;
const scanHeight = 250; // 固定高度测试避免宽高比计算错误导致高度为0
// 创建控件
const barcode = plus.barcode.create('barcode',
[plus.barcode.CODE_128, plus.barcode.EAN_13, plus.barcode.EAN_8],
{
top: scanTop,
left: 15,
width: scanWidth,
height: scanHeight,
scanbarColor: '#e60012',
frameColor: '#e60012',
background: '#000',
zIndex: 9999 // 强制置顶
}
);
barcode.onmarked = (type, result) => {
if (result) {
console.log('扫码结果:', result);
this.addGoodsByBarcode(result);
}
};
// 关键:获取当前 Webview 并挂载
const currentWebview = this.$scope.$getAppWebview();
if (currentWebview) {
currentWebview.append(barcode);
console.log('扫码控件已挂载到 Webview');
} else {
console.error('获取 Webview 失败,无法挂载');
}
barcode.start();
this.barcodeInstance = barcode;
},
destroyScan() {
if (this.barcodeInstance) {
this.barcodeInstance.close();
this.barcodeInstance = null;
}
this.isScanning = false;
},
toggleFlash() {
if (this.barcodeInstance) {
this.flashOn = !this.flashOn;
this.barcodeInstance.setFlash(this.flashOn);
}
},
pauseScan() {
if (this.barcodeInstance) {
this.scanPaused = !this.scanPaused;
this.scanPaused ? this.barcodeInstance.pause() : this.barcodeInstance.resume();
}
},
addGoodsByBarcode(barcode) {
const mockGoods = {
name: '测试商品-' + barcode,
barcode: barcode,
price: 10,
quantity: 1,
image: '/static/yibao.png'
};
this.goodsList.push(mockGoods);
uni.showToast({ title: '识别成功', icon: 'success' });
},
removeGoods(index) {
this.goodsList.splice(index, 1);
},
submitList() {
uni.showToast({ title: '提交成功', icon: 'success' });
setTimeout(() => uni.navigateBack(), 1000);
}
}
};
</script>
<style scoped>
/* 页面基础样式 */
.input-list-page {
width: 100%;
min-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: calc(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;
}
/* 扫码区域关键修复增加z-index */
.scan-area {
width: 100%;
padding: 15px;
box-sizing: border-box;
position: relative;
margin-top: 10px;
background-color: #fff;
z-index: 1; /* 确保扫码区域在最上层 */
}
.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;
z-index: 2; /* 关键修复:确保扫码容器层级 */
}
.scan-controls {
position: absolute;
bottom: 15px;
left: 0;
width: 100%;
display: flex;
justify-content: center;
gap: 40px;
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);
min-width: 60px;
}
.control-text {
font-size: 12px;
margin-top: 5px;
text-align: center;
}
/* H5端提示 */
.h5-tip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
background-color: #f9f9f9;
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;
}
/* 商品列表 */
.goods-list {
flex: 1;
padding: 15px;
box-sizing: border-box;
}
.goods-item {
background-color: #fff;
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
}
.goods-info {
display: flex;
align-items: flex-start;
margin-bottom: 10px;
}
.goods-img {
width: 48px;
height: 48px;
border-radius: 4px;
margin-right: 10px;
}
.goods-detail {
flex: 1;
}
.goods-name {
font-size: 16px;
color: #333;
display: block;
margin-bottom: 4px;
}
.goods-barcode {
font-size: 12px;
color: #999;
display: block;
margin-bottom: 4px;
}
.goods-price {
font-size: 14px;
color: #e60012;
font-weight: bold;
}
.goods-actions {
display: flex;
justify-content: flex-end;
margin-bottom: 10px;
}
.action-btn {
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
}
.remove {
background-color: #fef0f0;
color: #ff4d4f;
border: 1px solid #ffccc7;
}
/* 商品表单 */
.goods-form {
display: flex;
gap: 20px;
}
.form-item {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.label {
font-size: 14px;
color: #333;
}
.required::before {
content: '*';
color: #e60012;
margin-right: 2px;
}
.input {
height: 32px;
border: 1px solid #eee;
border-radius: 4px;
padding: 0 8px;
font-size: 14px;
}
/* 空状态 */
.empty-area {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
}
.empty-title {
font-size: 16px;
color: #333;
margin-bottom: 8px;
}
.empty-desc {
font-size: 14px;
color: #999;
}
/* 提交按钮区域 */
.submit-area {
padding: 15px;
box-sizing: border-box;
background-color: #fff;
}
.submit-btn {
width: 100%;
height: 44px;
background-color: #e60012;
color: #fff;
border: none;
border-radius: 4px;
font-size: 16px;
}
.submit-btn:disabled {
background-color: #ccc;
color: #999;
}
</style>