refactor: 扫码页简化为单页模式 — 拍照按钮+手动输入
This commit is contained in:
@ -1,14 +1,13 @@
|
||||
<template>
|
||||
<view class="page" :class="{ 'is-scanning': status === 'scanning' }">
|
||||
|
||||
<!-- ================= 状态 1:主控台 (你的原版) ================= -->
|
||||
<view v-show="status === 'idle'" class="dashboard">
|
||||
<view class="scan-btn camera-btn" @tap="startBarcodeScanner">
|
||||
<view class="page">
|
||||
<!-- 🚀 全屏扫码大按钮 -->
|
||||
<view class="scan-btn camera-btn" @tap="handleScanCamera">
|
||||
<text class="scan-icon">📷</text>
|
||||
<text class="scan-text">拍照扫码</text>
|
||||
<text class="scan-hint">全屏扫描二维码 / 条码</text>
|
||||
</view>
|
||||
|
||||
<!-- 手动输入 -->
|
||||
<view class="manual-section">
|
||||
<text class="section-label">或手动输入</text>
|
||||
<view class="manual-input">
|
||||
@ -20,103 +19,42 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 最近扫描 -->
|
||||
<view v-if="lastScanned" class="last-scan">
|
||||
<text class="last-label">最近扫描</text>
|
||||
<text class="sn-text" @tap="handleSearch">{{ lastScanned }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="loading">
|
||||
<text class="loading-icon">⏳</text>
|
||||
<text>查询中...</text>
|
||||
</view>
|
||||
|
||||
<view v-if="error" class="error-box">
|
||||
<text class="error-icon">⚠️</text>
|
||||
<text>{{ error }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="!loading && !error && !lastScanned" class="empty">
|
||||
<text class="empty-icon">📱</text>
|
||||
<text class="empty-text">扫码或手动输入身份证</text>
|
||||
<text class="empty-sub">查询产品流转进度</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ================= 状态 2:微信级全屏扫码 ================= -->
|
||||
<view v-if="status === 'scanning'" class="scan-layer">
|
||||
<!-- 顶部返回 -->
|
||||
<view class="top-bar">
|
||||
<view class="top-back" @tap="stopScanner">
|
||||
<text class="top-back-icon">✕</text>
|
||||
</view>
|
||||
<text class="top-title">扫一扫</text>
|
||||
<view class="top-desc">将二维码/条码放入框内</view>
|
||||
</view>
|
||||
|
||||
<!-- 扫描边框 -->
|
||||
<view class="scan-frame">
|
||||
<view class="frame-corner frame-tl" />
|
||||
<view class="frame-corner frame-tr" />
|
||||
<view class="frame-corner frame-bl" />
|
||||
<view class="frame-corner frame-br" />
|
||||
</view>
|
||||
|
||||
<!-- 扫描线动画 -->
|
||||
<view v-if="scanning && !multiCodes.length" class="scan-line" />
|
||||
|
||||
<!-- 多码标记层 -->
|
||||
<view v-if="multiCodes.length > 0" class="multi-marks" @tap="clearMarks">
|
||||
<view
|
||||
v-for="(mc, i) in multiCodes"
|
||||
:key="'mc'+i"
|
||||
class="mark-dot"
|
||||
:style="{ left: mc.x + '%', top: mc.y + '%' }"
|
||||
@tap.stop="selectMark(i)"
|
||||
>
|
||||
<view class="mark-pulse" />
|
||||
<view class="mark-core">
|
||||
<text class="mark-arrow">▼</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mark-hint">点击箭头选择目标条码</view>
|
||||
</view>
|
||||
|
||||
<!-- 右下角相册按钮 (仿微信) -->
|
||||
<view class="album-btn" @tap="handleAlbum">
|
||||
<text class="album-icon">🖼️</text>
|
||||
<text class="album-text">相册</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get } from "../../utils/request";
|
||||
|
||||
let barcodeScanner = null;
|
||||
let scanTimer = null;
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
status: 'idle',
|
||||
serialNumber: "",
|
||||
lastScanned: "",
|
||||
loading: false,
|
||||
error: "",
|
||||
scanning: true,
|
||||
multiCodes: [],
|
||||
};
|
||||
},
|
||||
onHide() {
|
||||
if (this.status === 'scanning') this.stopScanner();
|
||||
},
|
||||
onUnload() {
|
||||
this.stopScanner();
|
||||
},
|
||||
methods: {
|
||||
// ================= 原版业务查询 =================
|
||||
async doQuery(sn) {
|
||||
if (!sn || sn.length < 8) { this.error = "身份证至少需要 8 位"; return; }
|
||||
this.serialNumber = sn;
|
||||
@ -125,8 +63,6 @@ export default {
|
||||
this.error = "";
|
||||
try {
|
||||
await get(`/products/scan/${sn}`);
|
||||
getApp().globalData = getApp().globalData || {};
|
||||
getApp().globalData.scannedSn = sn;
|
||||
uni.navigateTo({ url: `/pages/scan/detail?serial=${sn}` });
|
||||
} catch (e) {
|
||||
this.error = e?.data?.detail || "未找到该产品";
|
||||
@ -134,128 +70,21 @@ export default {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.doQuery(this.serialNumber.trim());
|
||||
},
|
||||
handleSearch() { this.doQuery(this.serialNumber.trim()); },
|
||||
|
||||
// ================= 全屏扫码核心逻辑 =================
|
||||
startBarcodeScanner() {
|
||||
this.status = 'scanning';
|
||||
this.scanning = true;
|
||||
|
||||
// 强制把当前 Webview 背景变透明,防止挡住摄像头
|
||||
// #ifdef APP-PLUS
|
||||
const currentWebview = this.$mp.page.$getAppWebview();
|
||||
currentWebview.setStyle({ background: 'transparent' });
|
||||
|
||||
if (barcodeScanner) {
|
||||
barcodeScanner.start();
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建原生摄像头 (去掉了导致黑屏的 background 透明属性)
|
||||
barcodeScanner = new plus.barcode.Barcode('', [
|
||||
plus.barcode.QR, plus.barcode.EAN13, plus.barcode.EAN8,
|
||||
plus.barcode.CODE128, plus.barcode.CODE39, plus.barcode.CODE93,
|
||||
], {
|
||||
top: '0', left: '0', width: '100%', height: '100%',
|
||||
position: 'absolute',
|
||||
frameColor: 'rgba(0,0,0,0)',
|
||||
scanbarColor: 'rgba(0,0,0,0)'
|
||||
});
|
||||
|
||||
barcodeScanner.onmarked = (type, result) => {
|
||||
const results = Array.isArray(result) ? result : [{ type, result }];
|
||||
|
||||
if (results.length > 1) {
|
||||
this.scanning = false;
|
||||
this.multiCodes = results.map((r, i) => {
|
||||
let x = 20 + i * 25;
|
||||
let y = 40 + (i % 2) * 25;
|
||||
if (r.position) {
|
||||
x = (r.position.left / plus.screen.resolutionWidth) * 100;
|
||||
y = (r.position.top / plus.screen.resolutionHeight) * 100;
|
||||
}
|
||||
return { ...r, x: Math.min(x, 80), y: Math.min(y, 70), _index: i };
|
||||
});
|
||||
clearTimeout(scanTimer);
|
||||
scanTimer = setTimeout(() => { this.clearMarks(); }, 3000);
|
||||
} else {
|
||||
uni.vibrateShort();
|
||||
this.handleScanSuccess(results[0].result || results[0]);
|
||||
}
|
||||
};
|
||||
|
||||
currentWebview.append(barcodeScanner);
|
||||
barcodeScanner.start();
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-PLUS
|
||||
this.fallbackScanCode();
|
||||
// #endif
|
||||
},
|
||||
|
||||
stopScanner() {
|
||||
this.status = 'idle';
|
||||
clearTimeout(scanTimer);
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const currentWebview = this.$mp.page.$getAppWebview();
|
||||
currentWebview.setStyle({ background: '#f3f4f6' }); // 恢复网页原来的背景色
|
||||
|
||||
if (barcodeScanner) {
|
||||
barcodeScanner.close();
|
||||
barcodeScanner = null;
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
handleScanSuccess(rawResult) {
|
||||
const sn = (rawResult || "").replace(/[^a-zA-Z0-9]/g, "").slice(0, 16);
|
||||
this.stopScanner();
|
||||
this.doQuery(sn);
|
||||
},
|
||||
|
||||
fallbackScanCode() {
|
||||
// 📷 全屏相机扫码
|
||||
handleScanCamera() {
|
||||
uni.scanCode({
|
||||
onlyFromCamera: true,
|
||||
scanType: ["qrCode", "barCode"],
|
||||
success: (res) => this.handleScanSuccess(res.result),
|
||||
fail: (err) => {
|
||||
this.stopScanner();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
clearMarks() {
|
||||
this.multiCodes = [];
|
||||
this.scanning = true;
|
||||
// #ifdef APP-PLUS
|
||||
if (barcodeScanner) barcodeScanner.start();
|
||||
// #endif
|
||||
},
|
||||
|
||||
selectMark(index) {
|
||||
const mc = this.multiCodes[index];
|
||||
if (mc) {
|
||||
this.multiCodes = [];
|
||||
uni.vibrateShort();
|
||||
this.handleScanSuccess(mc.result);
|
||||
}
|
||||
},
|
||||
|
||||
handleAlbum() {
|
||||
this.stopScanner();
|
||||
uni.chooseImage({
|
||||
count: 1, sizeType: ['original'], sourceType: ['album'],
|
||||
success: (res) => {
|
||||
// #ifdef APP-PLUS
|
||||
plus.barcode.scan(res.tempFilePaths[0], (type, result) => {
|
||||
this.handleScanSuccess(result);
|
||||
}, () => {
|
||||
uni.showToast({ title: '未识别到条码', icon: 'none' });
|
||||
}, { filters: ['QRCODE'], scanBoth: true });
|
||||
// #endif
|
||||
const sn = (res.result || "").replace(/[^a-zA-Z0-9]/g, "").slice(0, 16);
|
||||
this.doQuery(sn);
|
||||
},
|
||||
fail: (err) => {
|
||||
if (!err.errMsg || !err.errMsg.includes("cancel")) {
|
||||
uni.showToast({ title: "扫码失败,请重试", icon: "none" });
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
@ -264,66 +93,41 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面基础 */
|
||||
.page { min-height: 100vh; background: #f3f4f6; }
|
||||
/* 扫码时强制透明 */
|
||||
.page.is-scanning { background: transparent !important; }
|
||||
.page { min-height: 100vh; padding: 24px 16px 16px; background: #f3f4f6; }
|
||||
|
||||
/* ================= 你的原版主页样式 ================= */
|
||||
.dashboard { padding: 24px 16px 16px; }
|
||||
.scan-btn { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 180px; border-radius: 20px; color: #fff; box-shadow: 0 4px 20px rgba(0,0,0,0.12); transition: transform 0.15s; margin-bottom: 24px; }
|
||||
/* 扫码区域 */
|
||||
.scan-btn { display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
height: 180px; border-radius: 20px; color: #fff; box-shadow: 0 4px 20px rgba(0,0,0,0.12);
|
||||
transition: transform 0.15s; margin-bottom: 24px; }
|
||||
.scan-btn:active { transform: scale(0.96); }
|
||||
.camera-btn { background: linear-gradient(135deg, #2563EB, #4F46E5); }
|
||||
.scan-icon { font-size: 44px; margin-bottom: 6px; }
|
||||
.scan-text { font-size: 17px; font-weight: 700; }
|
||||
.scan-hint { font-size: 11px; opacity: 0.75; margin-top: 4px; }
|
||||
|
||||
/* 手动输入 */
|
||||
.manual-section { margin-top: 24px; }
|
||||
.section-label { font-size: 12px; color: #9ca3af; margin-bottom: 8px; display: block; }
|
||||
.manual-input { display: flex; gap: 8px; }
|
||||
.input { flex: 1; height: 48px; padding: 0 14px; border: 1px solid #e5e7eb; border-radius: 12px; font-size: 15px; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
|
||||
.search-btn { height: 48px; padding: 0 22px; background: #2563EB; color: #fff; border: none; border-radius: 12px; font-size: 15px; font-weight: 600; line-height: 48px; }
|
||||
.input { flex: 1; height: 48px; padding: 0 14px; border: 1px solid #e5e7eb; border-radius: 12px;
|
||||
font-size: 15px; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
|
||||
.search-btn { height: 48px; padding: 0 22px; background: #2563EB; color: #fff; border: none;
|
||||
border-radius: 12px; font-size: 15px; font-weight: 600; line-height: 48px; }
|
||||
.search-btn[disabled] { opacity: 0.5; }
|
||||
.last-scan { display: flex; align-items: center; gap: 8px; margin-top: 20px; padding: 12px 16px; background: #fff; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.04); }
|
||||
|
||||
/* 状态 */
|
||||
.last-scan { display: flex; align-items: center; gap: 8px; margin-top: 20px; padding: 12px 16px;
|
||||
background: #fff; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.04); }
|
||||
.last-label { font-size: 12px; color: #9ca3af; }
|
||||
.sn-text { font-family: monospace; font-size: 14px; color: #2563EB; font-weight: 600; flex: 1; }
|
||||
.loading { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 24px 0; color: #6b7280; font-size: 14px; }
|
||||
.loading { display: flex; align-items: center; justify-content: center; gap: 8px;
|
||||
padding: 24px 0; color: #6b7280; font-size: 14px; }
|
||||
.loading-icon { font-size: 20px; }
|
||||
.error-box { display: flex; align-items: center; gap: 8px; margin-top: 16px; padding: 14px; border-radius: 12px; background: #fef2f2; color: #dc2626; font-size: 13px; border: 1px solid #fecaca; }
|
||||
.error-box { display: flex; align-items: center; gap: 8px; margin-top: 16px; padding: 14px;
|
||||
border-radius: 12px; background: #fef2f2; color: #dc2626; font-size: 13px; border: 1px solid #fecaca; }
|
||||
.error-icon { font-size: 16px; }
|
||||
.empty { display: flex; flex-direction: column; align-items: center; padding-top: 60px; color: #9ca3af; }
|
||||
.empty-icon { font-size: 56px; margin-bottom: 12px; }
|
||||
.empty-text { font-size: 15px; font-weight: 500; }
|
||||
.empty-sub { font-size: 12px; margin-top: 4px; }
|
||||
|
||||
/* ================= 扫码层样式 ================= */
|
||||
.scan-layer { position: fixed; inset: 0; z-index: 100; }
|
||||
.top-bar { position: absolute; top: 0; left: 0; right: 0; z-index: 30; padding: 40px 16px 20px; background: linear-gradient(180deg, rgba(0,0,0,0.5), transparent); display: flex; flex-direction: column; align-items: center; gap: 4px; }
|
||||
.top-back { position: absolute; left: 16px; top: 44px; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; }
|
||||
.top-back-icon { font-size: 22px; color: #fff; }
|
||||
.top-title { font-size: 17px; font-weight: 700; color: #fff; }
|
||||
.top-desc { font-size: 12px; color: rgba(255,255,255,0.7); }
|
||||
|
||||
/* 扫描框 */
|
||||
.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-tl { top: 0; left: 0; border-width: 3px 0 0 3px; }
|
||||
.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-br { bottom: 0; right: 0; border-width: 0 3px 3px 0; }
|
||||
.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: 20%; } 50% { top: 58%; } 100% { top: 20%; } }
|
||||
|
||||
/* 多码标记 */
|
||||
.multi-marks { position: absolute; inset: 0; z-index: 50; background: rgba(0,0,0,0.35); }
|
||||
.mark-dot { position: absolute; display: flex; flex-direction: column; align-items: center; transform: translate(-50%, -50%); }
|
||||
.mark-pulse { width: 40px; height: 40px; border-radius: 50%; border: 3px solid #00ff88; animation: pulse 1s ease-out infinite; }
|
||||
@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-arrow { font-size: 10px; color: #00ff88; margin-bottom: -14px; }
|
||||
.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; }
|
||||
|
||||
/* 右下角相册按钮 */
|
||||
.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; }
|
||||
.album-icon { font-size: 20px; margin-bottom: 2px; }
|
||||
.album-text { color: #fff; font-size: 11px; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user