140 lines
5.3 KiB
Vue
140 lines
5.3 KiB
Vue
<template>
|
||
<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">
|
||
<view class="input-wrap">
|
||
<input v-model="serialNumber" class="input" type="text" maxlength="16"
|
||
placeholder="输入16位身份证" @confirm="handleSearch" />
|
||
<text v-if="serialNumber" class="input-clear" @tap="serialNumber=''">✕</text>
|
||
</view>
|
||
<button class="search-btn" @tap="handleSearch" :disabled="loading">
|
||
{{ loading ? '查询中' : '查询' }}
|
||
</button>
|
||
</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>
|
||
</template>
|
||
|
||
<script>
|
||
import { get } from "../../utils/request";
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
serialNumber: "",
|
||
lastScanned: "",
|
||
loading: false,
|
||
error: "",
|
||
};
|
||
},
|
||
methods: {
|
||
async doQuery(sn) {
|
||
if (!sn || sn.length < 8) { this.error = "身份证至少需要 8 位"; return; }
|
||
this.serialNumber = sn;
|
||
this.lastScanned = sn;
|
||
this.loading = true;
|
||
this.error = "";
|
||
try {
|
||
await get(`/products/scan/${sn}`);
|
||
uni.navigateTo({ url: `/pages/scan/detail?serial=${sn}` });
|
||
} catch (e) {
|
||
this.error = e?.data?.detail || "未找到该产品";
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
handleSearch() { this.doQuery(this.serialNumber.trim()); },
|
||
|
||
// 📷 全屏相机扫码
|
||
handleScanCamera() {
|
||
uni.scanCode({
|
||
onlyFromCamera: true,
|
||
scanType: ["qrCode", "barCode"],
|
||
success: (res) => {
|
||
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" });
|
||
}
|
||
},
|
||
});
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page { min-height: 100vh; padding: 24px 16px 16px; background: #f3f4f6; }
|
||
|
||
/* 扫码区域 */
|
||
.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-wrap { flex: 1; position: relative; }
|
||
.input { width: 100%; height: 48px; padding: 0 36px 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); box-sizing: border-box; }
|
||
.input-clear { position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
|
||
font-size: 16px; color: #9ca3af; padding: 4px; z-index: 2; }
|
||
.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-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-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-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; }
|
||
</style>
|