Files
track/track-uniapp/src/pages/scan/index.vue

140 lines
5.3 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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