全局重命名: 序列号→身份证 (product.serial_number)

This commit is contained in:
2026-08-06 09:00:15 +08:00
parent 3b29bb0bd1
commit ae720656b1
3 changed files with 136 additions and 15 deletions

View File

@ -20,7 +20,7 @@
</view>
</view>
<view class="info-grid">
<view class="info-item"><text class="label">序列号</text><text class="value sn">{{ product.serial_number }}</text></view>
<view class="info-item"><text class="label">身份证</text><text class="value sn">{{ product.serial_number }}</text></view>
<view class="info-item"><text class="label">物料名称</text><text class="value">{{ product.material_name || product.material_id || '—' }}</text></view>
<view class="info-item"><text class="label">规格型号</text><text class="value">{{ product.spec_model || '—' }}</text></view>
<view class="info-item"><text class="label">订单编号</text><text class="value">{{ product.order_no || '—' }}</text></view>
@ -62,8 +62,8 @@
<text class="popup-title">编辑产品</text>
<view class="field-label">订单编号</view>
<input v-model="editForm.order_no" class="popup-input" placeholder="请输入订单编号" />
<view class="field-label" style="margin-top:10px;">外部序列号</view>
<input v-model="editForm.external_serial" class="popup-input" placeholder="请输入外部序列号" />
<view class="field-label" style="margin-top:10px;">外部身份证</view>
<input v-model="editForm.external_serial" class="popup-input" placeholder="请输入外部身份证" />
<view class="popup-btns"><button class="btn-cancel" @tap="editProductVisible = false">取消</button><button class="btn-primary" :disabled="editSaving" @tap="doEditProduct">{{ editSaving ? '保存中...' : '保存' }}</button></view>
</view>
</view>

View File

@ -10,7 +10,7 @@
<!-- 手动输入 -->
<view class="manual-input">
<input v-model="serialNumber" class="input" type="text" maxlength="16"
placeholder="手动输入16位序列号" @confirm="handleSearch" />
placeholder="手动输入16位身份证" @confirm="handleSearch" />
<button class="search-btn" @tap="handleSearch" :disabled="loading">
{{ loading ? '查询中' : '查询' }}
</button>
@ -24,7 +24,7 @@
<!-- 空状态 -->
<view v-if="!loading && !error" class="empty">
<text class="empty-icon">📱</text>
<text class="empty-text">扫码或手动输入序列号查询产品进度</text>
<text class="empty-text">扫码或手动输入身份证查询产品进度</text>
</view>
</view>
</template>
@ -43,7 +43,7 @@ export default {
},
methods: {
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.lastScanned = sn;
this.loading = true;

View File

@ -2,24 +2,145 @@
<view class="page">
<view class="header">
<text class="title">我的任务</text>
<text class="subtitle">待处理和进行中的任务</text>
</view>
<view class="empty">
<text class="empty-icon">📋</text>
<text class="empty-text">暂无待办任务</text>
<!-- Tab 固定顶部 -->
<view class="tabs">
<view v-for="t in TABS" :key="t.key"
:class="['tab', tab === t.key ? 'tab-active' : '']"
@tap="tab = t.key">{{ t.label }} ({{ countBy(t.key) }})</view>
</view>
<!-- 内容区 -->
<view v-if="loading" class="center">加载中...</view>
<template v-else>
<view v-if="filtered.length === 0" class="empty">
<text class="empty-icon">📋</text>
<text class="empty-text">{{ tab === 'all' ? '暂无待办任务' : '无此状态任务' }}</text>
</view>
<view v-for="task in filtered" :key="task.id" class="card" @tap="goDetail(task)">
<view class="card-row">
<text class="card-name">{{ task.task_name }}</text>
<text :class="['card-status', statusColor(task.status)]">{{ statusLabel(task.status) }}</text>
</view>
<view class="card-meta">
<text>身份证: {{ task.product_sn || '—' }}</text>
<text>物料: {{ task.product_material || '—' }}</text>
</view>
<view class="card-time">创建: {{ formatTime(task.created_at) }}</view>
</view>
</template>
</view>
</template>
<script setup>
<script>
import { get } from "../../utils/request";
const TABS = [
{ key: "all", label: "全部" },
{ key: "PENDING", label: "待接收" },
{ key: "WIP", label: "进行中" },
{ key: "COMPLETED", label: "已完成" },
];
export default {
data() {
return {
TABS,
tab: "PENDING",
tasks: [],
loading: true,
currentUser: null,
};
},
computed: {
filtered() {
if (this.tab === "all") return this.tasks;
return this.tasks.filter(t => t.status === this.tab);
},
},
onShow() {
this.loadCurrentUser();
this.fetchTasks();
},
methods: {
statusLabel(s) {
const map = { PENDING: "待接收", WIP: "进行中", COMPLETED: "已完成", REJECTED: "已驳回", ARCHIVED: "已入库" };
return map[s] || s;
},
statusColor(s) {
switch (s) {
case "PENDING": return "s-yellow";
case "WIP": return "s-blue";
case "COMPLETED": return "s-green";
default: return "s-gray";
}
},
countBy(key) {
if (key === "all") return this.tasks.length;
return this.tasks.filter(t => t.status === key).length;
},
formatTime(t) {
if (!t) return "";
const d = new Date(t);
const pad = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
},
loadCurrentUser() {
try {
const u = uni.getStorageSync("user");
if (u) this.currentUser = typeof u === "string" ? JSON.parse(u) : u;
} catch {}
},
async fetchTasks() {
this.loading = true;
try {
const username = this.currentUser?.username || "";
const res = await get("/tasks/", { assignee_id: username, limit: 100 });
this.tasks = res.tasks || [];
} catch { this.tasks = []; }
finally { this.loading = false; }
},
goDetail(task) {
const sn = task.product_sn || task.product_id;
if (sn) uni.navigateTo({ url: `/pages/scan/detail?serial=${sn}` });
},
},
};
</script>
<style scoped>
.page { min-height: 100vh; padding: 16px; padding-bottom: 80px; }
.header { margin-bottom: 24px; }
.title { font-size: 20px; font-weight: 700; color: #1f2937; display: block; }
.subtitle { font-size: 13px; color: #9ca3af; margin-top: 4px; display: block; }
.page { min-height: 100vh; padding: 16px; padding-bottom: 100px; }
.header { margin-bottom: 12px; }
.title { font-size: 20px; font-weight: 700; color: #1f2937; }
.center { text-align: center; padding: 48px 0; color: #9ca3af; }
.empty { display: flex; flex-direction: column; align-items: center; padding-top: 80px; }
.empty-icon { font-size: 64px; margin-bottom: 12px; }
.empty-text { font-size: 14px; color: #9ca3af; }
/* tabs — sticky 不换行,溢出滚动 */
.tabs {
display: flex; gap: 8px; margin-bottom: 14px;
white-space: nowrap; overflow-x: auto;
position: sticky; top: 0; z-index: 999;
background: #f3f4f6; padding: 12px 0 8px;
}
.tab {
padding: 8px 16px; border-radius: 20px; font-size: 13px; font-weight: 600;
background: #fff; color: #6b7280; flex-shrink: 0;
}
.tab-active { background: #2563eb; color: #fff; }
/* card */
.card { background: #fff; border-radius: 12px; padding: 14px; margin-bottom: 10px;
box-shadow: 0 1px 3px rgba(0,0,0,0.06); }
.card-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.card-name { font-size: 15px; font-weight: 700; color: #1f2937; }
.card-status { font-size: 11px; padding: 2px 10px; border-radius: 20px; font-weight: 600; }
.s-yellow { background: #fef3c7; color: #b45309; }
.s-blue { background: #dbeafe; color: #1d4ed8; }
.s-green { background: #dcfce7; color: #15803d; }
.s-gray { background: #f3f4f6; color: #6b7280; }
.card-meta { font-size: 12px; color: #6b7280; display: flex; gap: 12px; }
.card-time { font-size: 11px; color: #9ca3af; margin-top: 4px; }
</style>