fix: 扫码页优化 — 修复黑屏+右下角相册+Webview透明+扫描框位置调整
This commit is contained in:
@ -1,17 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 动态切换背景:扫码时必须全透明 -->
|
|
||||||
<view class="page" :class="{ 'is-scanning': status === 'scanning' }">
|
<view class="page" :class="{ 'is-scanning': status === 'scanning' }">
|
||||||
|
|
||||||
<!-- ================= 状态 1:主控台 (保留你的原版UI) ================= -->
|
<!-- ================= 状态 1:主控台 (你的原版) ================= -->
|
||||||
<view v-show="status === 'idle'" class="dashboard">
|
<view v-show="status === 'idle'" class="dashboard">
|
||||||
<!-- 🚀 全屏扫码大按钮 -->
|
|
||||||
<view class="scan-btn camera-btn" @tap="startBarcodeScanner">
|
<view class="scan-btn camera-btn" @tap="startBarcodeScanner">
|
||||||
<text class="scan-icon">📷</text>
|
<text class="scan-icon">📷</text>
|
||||||
<text class="scan-text">拍照扫码</text>
|
<text class="scan-text">拍照扫码</text>
|
||||||
<text class="scan-hint">全屏扫描二维码 / 条码</text>
|
<text class="scan-hint">全屏扫描二维码 / 条码</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 手动输入 -->
|
|
||||||
<view class="manual-section">
|
<view class="manual-section">
|
||||||
<text class="section-label">或手动输入</text>
|
<text class="section-label">或手动输入</text>
|
||||||
<view class="manual-input">
|
<view class="manual-input">
|
||||||
@ -23,23 +20,21 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 最近扫描 -->
|
|
||||||
<view v-if="lastScanned" class="last-scan">
|
<view v-if="lastScanned" class="last-scan">
|
||||||
<text class="last-label">最近扫描</text>
|
<text class="last-label">最近扫描</text>
|
||||||
<text class="sn-text" @tap="handleSearch">{{ lastScanned }}</text>
|
<text class="sn-text" @tap="handleSearch">{{ lastScanned }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 状态提示 -->
|
|
||||||
<view v-if="loading" class="loading">
|
<view v-if="loading" class="loading">
|
||||||
<text class="loading-icon">⏳</text>
|
<text class="loading-icon">⏳</text>
|
||||||
<text>查询中...</text>
|
<text>查询中...</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="error" class="error-box">
|
<view v-if="error" class="error-box">
|
||||||
<text class="error-icon">⚠️</text>
|
<text class="error-icon">⚠️</text>
|
||||||
<text>{{ error }}</text>
|
<text>{{ error }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
|
||||||
<view v-if="!loading && !error && !lastScanned" class="empty">
|
<view v-if="!loading && !error && !lastScanned" class="empty">
|
||||||
<text class="empty-icon">📱</text>
|
<text class="empty-icon">📱</text>
|
||||||
<text class="empty-text">扫码或手动输入身份证</text>
|
<text class="empty-text">扫码或手动输入身份证</text>
|
||||||
@ -69,7 +64,7 @@
|
|||||||
<!-- 扫描线动画 -->
|
<!-- 扫描线动画 -->
|
||||||
<view v-if="scanning && !multiCodes.length" class="scan-line" />
|
<view v-if="scanning && !multiCodes.length" class="scan-line" />
|
||||||
|
|
||||||
<!-- 多码标记层 (核心功能) -->
|
<!-- 多码标记层 -->
|
||||||
<view v-if="multiCodes.length > 0" class="multi-marks" @tap="clearMarks">
|
<view v-if="multiCodes.length > 0" class="multi-marks" @tap="clearMarks">
|
||||||
<view
|
<view
|
||||||
v-for="(mc, i) in multiCodes"
|
v-for="(mc, i) in multiCodes"
|
||||||
@ -86,18 +81,10 @@
|
|||||||
<view class="mark-hint">点击箭头选择目标条码</view>
|
<view class="mark-hint">点击箭头选择目标条码</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 底部悬浮胶囊 -->
|
<!-- 右下角相册按钮 (仿微信) -->
|
||||||
<view class="bottom-action-bar">
|
<view class="album-btn" @tap="handleAlbum">
|
||||||
<view class="action-btn" @tap="handleAlbum">
|
<text class="album-icon">🖼️</text>
|
||||||
<text class="action-text">相册</text>
|
<text class="album-text">相册</text>
|
||||||
</view>
|
|
||||||
<view class="action-btn" @tap="toggleFlash">
|
|
||||||
<text class="action-text">{{ flashMode === 'on' ? '关灯' : '照明' }}</text>
|
|
||||||
</view>
|
|
||||||
<!-- 点击手动输入,直接关闭摄像头回到你的主页 -->
|
|
||||||
<view class="action-btn primary" @tap="stopScanner">
|
|
||||||
<text class="action-text primary-text">手动输入</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -113,17 +100,12 @@ let scanTimer = null;
|
|||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
status: 'idle', // 'idle': 主页 | 'scanning': 扫码中
|
status: 'idle',
|
||||||
|
|
||||||
// 原版业务数据
|
|
||||||
serialNumber: "",
|
serialNumber: "",
|
||||||
lastScanned: "",
|
lastScanned: "",
|
||||||
loading: false,
|
loading: false,
|
||||||
error: "",
|
error: "",
|
||||||
|
|
||||||
// 扫码控制数据
|
|
||||||
scanning: true,
|
scanning: true,
|
||||||
flashMode: 'off',
|
|
||||||
multiCodes: [],
|
multiCodes: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -134,7 +116,7 @@ export default {
|
|||||||
this.stopScanner();
|
this.stopScanner();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// ================= 原版业务查询逻辑 =================
|
// ================= 原版业务查询 =================
|
||||||
async doQuery(sn) {
|
async doQuery(sn) {
|
||||||
if (!sn || sn.length < 8) { this.error = "身份证至少需要 8 位"; return; }
|
if (!sn || sn.length < 8) { this.error = "身份证至少需要 8 位"; return; }
|
||||||
this.serialNumber = sn;
|
this.serialNumber = sn;
|
||||||
@ -143,10 +125,8 @@ export default {
|
|||||||
this.error = "";
|
this.error = "";
|
||||||
try {
|
try {
|
||||||
await get(`/products/scan/${sn}`);
|
await get(`/products/scan/${sn}`);
|
||||||
// 全局存储一下,方便详情页使用
|
|
||||||
getApp().globalData = getApp().globalData || {};
|
getApp().globalData = getApp().globalData || {};
|
||||||
getApp().globalData.scannedSn = sn;
|
getApp().globalData.scannedSn = sn;
|
||||||
|
|
||||||
uni.navigateTo({ url: `/pages/scan/detail?serial=${sn}` });
|
uni.navigateTo({ url: `/pages/scan/detail?serial=${sn}` });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.error = e?.data?.detail || "未找到该产品";
|
this.error = e?.data?.detail || "未找到该产品";
|
||||||
@ -158,19 +138,22 @@ export default {
|
|||||||
this.doQuery(this.serialNumber.trim());
|
this.doQuery(this.serialNumber.trim());
|
||||||
},
|
},
|
||||||
|
|
||||||
// ================= 微信级全屏扫码逻辑 =================
|
// ================= 全屏扫码核心逻辑 =================
|
||||||
startBarcodeScanner() {
|
startBarcodeScanner() {
|
||||||
this.status = 'scanning';
|
this.status = 'scanning';
|
||||||
this.scanning = true;
|
this.scanning = true;
|
||||||
this.flashMode = 'off';
|
|
||||||
|
|
||||||
|
// 强制把当前 Webview 背景变透明,防止挡住摄像头
|
||||||
// #ifdef APP-PLUS
|
// #ifdef APP-PLUS
|
||||||
|
const currentWebview = this.$mp.page.$getAppWebview();
|
||||||
|
currentWebview.setStyle({ background: 'transparent' });
|
||||||
|
|
||||||
if (barcodeScanner) {
|
if (barcodeScanner) {
|
||||||
barcodeScanner.start();
|
barcodeScanner.start();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建透明原生摄像头
|
// 创建原生摄像头 (去掉了导致黑屏的 background 透明属性)
|
||||||
barcodeScanner = new plus.barcode.Barcode('', [
|
barcodeScanner = new plus.barcode.Barcode('', [
|
||||||
plus.barcode.QR, plus.barcode.EAN13, plus.barcode.EAN8,
|
plus.barcode.QR, plus.barcode.EAN13, plus.barcode.EAN8,
|
||||||
plus.barcode.CODE128, plus.barcode.CODE39, plus.barcode.CODE93,
|
plus.barcode.CODE128, plus.barcode.CODE39, plus.barcode.CODE93,
|
||||||
@ -178,15 +161,13 @@ export default {
|
|||||||
top: '0', left: '0', width: '100%', height: '100%',
|
top: '0', left: '0', width: '100%', height: '100%',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
frameColor: 'rgba(0,0,0,0)',
|
frameColor: 'rgba(0,0,0,0)',
|
||||||
scanbarColor: 'rgba(0,0,0,0)',
|
scanbarColor: 'rgba(0,0,0,0)'
|
||||||
background: 'rgba(0,0,0,0)' // 必须透明才能透出摄像头
|
|
||||||
});
|
});
|
||||||
|
|
||||||
barcodeScanner.onmarked = (type, result) => {
|
barcodeScanner.onmarked = (type, result) => {
|
||||||
const results = Array.isArray(result) ? result : [{ type, result }];
|
const results = Array.isArray(result) ? result : [{ type, result }];
|
||||||
|
|
||||||
if (results.length > 1) {
|
if (results.length > 1) {
|
||||||
// 多码同框选点逻辑
|
|
||||||
this.scanning = false;
|
this.scanning = false;
|
||||||
this.multiCodes = results.map((r, i) => {
|
this.multiCodes = results.map((r, i) => {
|
||||||
let x = 20 + i * 25;
|
let x = 20 + i * 25;
|
||||||
@ -205,7 +186,6 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentWebview = this.$mp.page.$getAppWebview();
|
|
||||||
currentWebview.append(barcodeScanner);
|
currentWebview.append(barcodeScanner);
|
||||||
barcodeScanner.start();
|
barcodeScanner.start();
|
||||||
// #endif
|
// #endif
|
||||||
@ -217,11 +197,13 @@ export default {
|
|||||||
|
|
||||||
stopScanner() {
|
stopScanner() {
|
||||||
this.status = 'idle';
|
this.status = 'idle';
|
||||||
this.flashMode = 'off';
|
|
||||||
clearTimeout(scanTimer);
|
clearTimeout(scanTimer);
|
||||||
|
|
||||||
// #ifdef APP-PLUS
|
// #ifdef APP-PLUS
|
||||||
|
const currentWebview = this.$mp.page.$getAppWebview();
|
||||||
|
currentWebview.setStyle({ background: '#f3f4f6' }); // 恢复网页原来的背景色
|
||||||
|
|
||||||
if (barcodeScanner) {
|
if (barcodeScanner) {
|
||||||
barcodeScanner.setFlash(false);
|
|
||||||
barcodeScanner.close();
|
barcodeScanner.close();
|
||||||
barcodeScanner = null;
|
barcodeScanner = null;
|
||||||
}
|
}
|
||||||
@ -230,8 +212,8 @@ export default {
|
|||||||
|
|
||||||
handleScanSuccess(rawResult) {
|
handleScanSuccess(rawResult) {
|
||||||
const sn = (rawResult || "").replace(/[^a-zA-Z0-9]/g, "").slice(0, 16);
|
const sn = (rawResult || "").replace(/[^a-zA-Z0-9]/g, "").slice(0, 16);
|
||||||
this.stopScanner(); // 先关闭摄像头
|
this.stopScanner();
|
||||||
this.doQuery(sn); // 调用你的原版查询方法
|
this.doQuery(sn);
|
||||||
},
|
},
|
||||||
|
|
||||||
fallbackScanCode() {
|
fallbackScanCode() {
|
||||||
@ -240,9 +222,6 @@ export default {
|
|||||||
scanType: ["qrCode", "barCode"],
|
scanType: ["qrCode", "barCode"],
|
||||||
success: (res) => this.handleScanSuccess(res.result),
|
success: (res) => this.handleScanSuccess(res.result),
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
if (!err.errMsg || !err.errMsg.includes("cancel")) {
|
|
||||||
uni.showToast({ title: "扫码失败,请重试", icon: "none" });
|
|
||||||
}
|
|
||||||
this.stopScanner();
|
this.stopScanner();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -265,15 +244,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleFlash() {
|
|
||||||
this.flashMode = this.flashMode === 'on' ? 'off' : 'on';
|
|
||||||
// #ifdef APP-PLUS
|
|
||||||
if (barcodeScanner) {
|
|
||||||
barcodeScanner.setFlash(this.flashMode === 'on');
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
},
|
|
||||||
|
|
||||||
handleAlbum() {
|
handleAlbum() {
|
||||||
this.stopScanner();
|
this.stopScanner();
|
||||||
uni.chooseImage({
|
uni.chooseImage({
|
||||||
@ -295,9 +265,9 @@ export default {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 页面基础 */
|
/* 页面基础 */
|
||||||
.page { min-height: 100vh; background: #f3f4f6; transition: background 0.3s; }
|
.page { min-height: 100vh; background: #f3f4f6; }
|
||||||
/* 核心:扫码时整个网页必须透明,才能透出底层的原生摄像头 */
|
/* 扫码时强制透明 */
|
||||||
.page.is-scanning { background: transparent; }
|
.page.is-scanning { background: transparent !important; }
|
||||||
|
|
||||||
/* ================= 你的原版主页样式 ================= */
|
/* ================= 你的原版主页样式 ================= */
|
||||||
.dashboard { padding: 24px 16px 16px; }
|
.dashboard { padding: 24px 16px 16px; }
|
||||||
@ -334,14 +304,14 @@ export default {
|
|||||||
.top-desc { font-size: 12px; color: rgba(255,255,255,0.7); }
|
.top-desc { font-size: 12px; color: rgba(255,255,255,0.7); }
|
||||||
|
|
||||||
/* 扫描框 */
|
/* 扫描框 */
|
||||||
.scan-frame { position: absolute; top: 15%; left: 10%; right: 10%; bottom: 30%; pointer-events: none; z-index: 10; }
|
.scan-frame { position: absolute; top: 20%; left: 15%; right: 15%; bottom: 40%; pointer-events: none; z-index: 10; }
|
||||||
.frame-corner { position: absolute; width: 30px; height: 30px; border-color: #00ff88; border-style: solid; }
|
.frame-corner { position: absolute; width: 30px; height: 30px; border-color: #00ff88; border-style: solid; }
|
||||||
.frame-tl { top: 0; left: 0; border-width: 3px 0 0 3px; }
|
.frame-tl { top: 0; left: 0; border-width: 3px 0 0 3px; }
|
||||||
.frame-tr { top: 0; right: 0; border-width: 3px 3px 0 0; }
|
.frame-tr { top: 0; right: 0; border-width: 3px 3px 0 0; }
|
||||||
.frame-bl { bottom: 0; left: 0; border-width: 0 0 3px 3px; }
|
.frame-bl { bottom: 0; left: 0; border-width: 0 0 3px 3px; }
|
||||||
.frame-br { bottom: 0; right: 0; border-width: 0 3px 3px 0; }
|
.frame-br { bottom: 0; right: 0; border-width: 0 3px 3px 0; }
|
||||||
.scan-line { position: absolute; left: 10%; right: 10%; top: 15%; height: 2px; background: linear-gradient(90deg, transparent, #00ff88, transparent); z-index: 11; animation: scanMove 2s ease-in-out infinite; }
|
.scan-line { position: absolute; left: 15%; right: 15%; top: 20%; height: 2px; background: linear-gradient(90deg, transparent, #00ff88, transparent); z-index: 11; animation: scanMove 2.5s ease-in-out infinite; }
|
||||||
@keyframes scanMove { 0% { top: 15%; } 50% { top: 68%; } 100% { top: 15%; } }
|
@keyframes scanMove { 0% { top: 20%; } 50% { top: 58%; } 100% { top: 20%; } }
|
||||||
|
|
||||||
/* 多码标记 */
|
/* 多码标记 */
|
||||||
.multi-marks { position: absolute; inset: 0; z-index: 50; background: rgba(0,0,0,0.35); }
|
.multi-marks { position: absolute; inset: 0; z-index: 50; background: rgba(0,0,0,0.35); }
|
||||||
@ -350,11 +320,10 @@ export default {
|
|||||||
@keyframes pulse { 0% { transform: scale(1); opacity: 1; } 100% { transform: scale(2); opacity: 0; } }
|
@keyframes pulse { 0% { transform: scale(1); opacity: 1; } 100% { transform: scale(2); opacity: 0; } }
|
||||||
.mark-core { width: 16px; height: 16px; border-radius: 50%; background: #00ff88; margin-top: -28px; display: flex; align-items: flex-end; justify-content: center; }
|
.mark-core { width: 16px; height: 16px; border-radius: 50%; background: #00ff88; margin-top: -28px; display: flex; align-items: flex-end; justify-content: center; }
|
||||||
.mark-arrow { font-size: 10px; color: #00ff88; margin-bottom: -14px; }
|
.mark-arrow { font-size: 10px; color: #00ff88; margin-bottom: -14px; }
|
||||||
.mark-hint { position: absolute; bottom: 25%; left: 50%; transform: translateX(-50%); color: #fff; font-size: 14px; background: rgba(0,0,0,0.6); padding: 8px 20px; border-radius: 20px; }
|
.mark-hint { position: absolute; bottom: 30%; left: 50%; transform: translateX(-50%); color: #fff; font-size: 14px; background: rgba(0,0,0,0.6); padding: 8px 20px; border-radius: 20px; }
|
||||||
|
|
||||||
/* 底部操作胶囊 */
|
/* 右下角相册按钮 */
|
||||||
.bottom-action-bar { position: absolute; bottom: 50px; left: 50%; transform: translateX(-50%); z-index: 30; display: flex; flex-direction: row; background-color: rgba(0, 0, 0, 0.6); border-radius: 30px; padding: 6px 10px; gap: 10px; }
|
.album-btn { position: absolute; bottom: 80px; right: 30px; z-index: 30; display: flex; flex-direction: column; align-items: center; justify-content: center; width: 56px; height: 56px; background: rgba(0, 0, 0, 0.5); border-radius: 28px; }
|
||||||
.action-btn { padding: 10px 24px; border-radius: 24px; display: flex; align-items: center; justify-content: center; }
|
.album-icon { font-size: 20px; margin-bottom: 2px; }
|
||||||
.action-text { color: #fff; font-size: 14px; font-weight: 500; }
|
.album-text { color: #fff; font-size: 11px; }
|
||||||
.primary { background-color: rgba(37, 99, 235, 0.9); }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user