feat: 移动端双Token队列拦截器 + 通知列表 + 登录页 + TabBar红点 + v1.0.1
This commit is contained in:
@ -4,45 +4,55 @@
|
||||
<view v-if="error" class="error-box">{{ error }}</view>
|
||||
|
||||
<template v-if="product && !loading">
|
||||
<view class="overall-bar" @tap="handleOverallBarClick">
|
||||
<text class="overall-label">宏观状态</text>
|
||||
<text :class="['overall-val', product.overall_status ? '' : 'overall-empty']">{{ product.overall_status || '未激活 — 点击发起首道工序' }}</text>
|
||||
<text v-if="product.task_tree && product.task_tree.length" class="overall-arrow">▾</text>
|
||||
</view>
|
||||
<!-- 工作区模式:显示产品信息栏 -->
|
||||
<template v-if="currentMode === 'workspace'">
|
||||
<view class="overall-bar" @tap="handleOverallBarClick">
|
||||
<text class="overall-label">宏观状态</text>
|
||||
<text :class="['overall-val', product.overall_status ? '' : 'overall-empty']">{{ product.overall_status || '未激活 — 点击发起首道工序' }}</text>
|
||||
<text v-if="product.task_tree && product.task_tree.length" class="overall-arrow">▾</text>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">📦 产品信息</text>
|
||||
<view class="card-header-right">
|
||||
<text :class="['badge', statusColor(product.status)]">{{ statusLabel(product.status) }}</text>
|
||||
<text class="mode-toggle" @tap="toggleMode">{{ currentMode === 'workspace' ? '🌳 流转树' : '🛠️ 工作区' }}</text>
|
||||
<text class="edit-btn" @tap="openEditProduct">✏️</text>
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">📦 产品信息</text>
|
||||
<view class="card-header-right">
|
||||
<text class="mode-toggle" @tap="toggleMode">{{ modeToggleLabel }}</text>
|
||||
<text class="edit-btn" @tap="openEditProduct">✏️</text>
|
||||
</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" v-if="product.external_serial"><text class="label">产品序列号</text><text class="value sn">{{ product.external_serial }}</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>
|
||||
<view class="info-item" v-if="product.current_location_id">
|
||||
<text class="label">当前位置</text>
|
||||
<text :class="['value', product.current_location_id === 'virtual_warehouse' ? 'warehouse' : '']">{{ product.current_location_id === 'virtual_warehouse' ? '🏭 仓库' : product.current_location_id }}</text>
|
||||
</view>
|
||||
</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">{{ 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>
|
||||
<view class="info-item" v-if="product.current_location_id">
|
||||
<text class="label">当前位置</text>
|
||||
<text :class="['value', product.current_location_id === 'virtual_warehouse' ? 'warehouse' : '']">{{ product.current_location_id === 'virtual_warehouse' ? '🏭 仓库' : product.current_location_id }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="product && product.current_location_id === 'virtual_warehouse' && !hasMyActiveTask"
|
||||
class="warehouse-transfer-banner" @tap="openWarehouseTransfer">
|
||||
<text class="wt-icon">📤</text>
|
||||
<text class="wt-text">该产品在仓库中 — 点击此处转出并派发给指定人员</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 📤 仓库转出横幅(当前用户有活跃任务时隐藏) -->
|
||||
<view v-if="product && product.current_location_id === 'virtual_warehouse' && !hasMyActiveTask"
|
||||
class="warehouse-transfer-banner" @tap="openWarehouseTransfer">
|
||||
<text class="wt-icon">📤</text>
|
||||
<text class="wt-text">该产品在仓库中 — 点击此处转出并派发给指定人员</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 工作区视图 -->
|
||||
<WorkspaceArea v-if="currentMode === 'workspace'" :product="product"
|
||||
:currentUserId="currentUserId" :currentUsername="currentUsername"
|
||||
:initialLockTaskId="autoLockTaskId"
|
||||
@action="handleTaskAction" @viewRecords="handleViewRecords" />
|
||||
|
||||
<TreeCanvas v-if="currentMode === 'tree'" :product="product" @viewRecords="handleViewRecords" />
|
||||
<!-- 📇 流转卡片:探探式单张滑动 -->
|
||||
<TaskSwipeCards v-if="currentMode === 'swipe'" :product="product"
|
||||
@back="currentMode = 'workspace'" @overview="currentMode = 'tree'"
|
||||
@viewRecords="handleViewRecords" />
|
||||
|
||||
<!-- 🌳 流转树:全屏独立视图 -->
|
||||
<TreeCanvas v-if="currentMode === 'tree'" :product="product" @viewRecords="handleViewRecords" @back="currentMode = 'workspace'" @swipe="currentMode = 'swipe'" />
|
||||
</template>
|
||||
|
||||
<!-- 状态定调 -->
|
||||
@ -155,13 +165,14 @@
|
||||
import request, { get, post, patch, put } from "../../utils/request";
|
||||
import WorkspaceArea from "./components/WorkspaceArea.vue";
|
||||
import TreeCanvas from "./components/TreeCanvas.vue";
|
||||
import TaskSwipeCards from "./components/TaskSwipeCards.vue";
|
||||
|
||||
const OVERALL_OPTIONS = ["备货", "生产", "测试", "维修", "在库"];
|
||||
const TASK_NAME_OPTIONS = ["备货", "生产", "测试", "维修", "在库"];
|
||||
const STATUS_MAP = { PENDING: "待接收", WIP: "进行中", COMPLETED: "已完成", REJECTED: "已驳回", ARCHIVED: "已入库", CANCELED: "已撤回" };
|
||||
|
||||
export default {
|
||||
components: { WorkspaceArea, TreeCanvas },
|
||||
components: { WorkspaceArea, TreeCanvas, TaskSwipeCards },
|
||||
data() {
|
||||
return {
|
||||
OVERALL_OPTIONS, loading: true, error: "", product: null,
|
||||
@ -169,7 +180,7 @@ export default {
|
||||
users: [], TASK_NAME_OPTIONS,
|
||||
createFirstVisible: false, isWarehouseTransfer: false, firstForm: { task_name: "", taskNameIdx: 0, assignee_id: "", assigneeLabel: "", assigneeIdx: 0, note: "" }, firstSaving: false,
|
||||
recordPopup: { visible: false, task: null }, recordForm: { recordId: null, remark: "", images: [], pendingCount: 0 }, recordSaving: false, isUploading: false,
|
||||
currentUser: null, currentUserId: "", currentUsername: "", currentMode: "tree",
|
||||
currentUser: null, currentUserId: "", currentUsername: "", currentMode: "workspace", autoLockTaskId: "",
|
||||
processOptions: [], userOptions: [],
|
||||
actionPopup: { visible: false, type: "", task: null }, actionLoading: false, rejectReason: "", receiveRemark: "", receiveTaskName: "",
|
||||
transferForm: { selectedUserId: "", isWarehouse: false, note: "" },
|
||||
@ -182,6 +193,7 @@ export default {
|
||||
transferUserName() { const u = this.userOptions.find(u => u.id === this.transferForm.selectedUserId); return u ? u.name : ""; },
|
||||
spawnUserName() { const u = this.userOptions.find(u => u.id === this.spawnForm.assignee_id); return u ? u.name : ""; },
|
||||
userGridOptions() { return (this.userOptions || []).map(u => ({ id: u.id, name: u.name })); },
|
||||
modeToggleLabel() { if (this.currentMode === 'workspace') return '📇 流转卡片'; if (this.currentMode === 'swipe') return '🌳 流转树'; return '🛠️ 工作区'; },
|
||||
hasMyActiveTask() {
|
||||
const find = (tasks) => { if (!tasks) return false; for (const t of tasks) { if ((t.status === 'WIP' || t.status === 'PENDING') && (t.assignee_id == this.currentUserId || t.assignee_id == this.currentUsername)) return true; if (find(t.child_tasks)) return true; } return false; };
|
||||
return this.product ? find(this.product.task_tree) : false;
|
||||
@ -193,8 +205,24 @@ export default {
|
||||
statusLabel(s) { return STATUS_MAP[s] || s; },
|
||||
statusColor(s) { switch (s) { case "PENDING": return "s-yellow"; case "WIP": return "s-blue"; case "COMPLETED": return "s-green"; case "REJECTED": return "s-red"; default: return "s-gray"; } },
|
||||
|
||||
async doQuery(sn) { this.loading = true; this.error = ""; try { this.product = await get(`/products/scan/${sn}`); this.$nextTick(() => { this.currentMode = this.hasMyActiveTask ? 'workspace' : 'tree'; if (!this.product.task_tree || !this.product.task_tree.length) { this.openCreateFirstTask(); } }); } catch (e) { this.error = e?.data?.detail || "查询失败"; } finally { this.loading = false; } },
|
||||
toggleMode() { this.currentMode = this.currentMode === 'workspace' ? 'tree' : 'workspace'; },
|
||||
async doQuery(sn) { this.loading = true; this.error = ""; try { this.product = await get(`/products/scan/${sn}`); this.$nextTick(() => { this.currentMode = 'workspace'; this.autoLockTaskId = this.findMyImmersiveTask() || ''; if (!this.product.task_tree || !this.product.task_tree.length) { this.openCreateFirstTask(); } }); } catch (e) { this.error = e?.data?.detail || "查询失败"; } finally { this.loading = false; } },
|
||||
findMyImmersiveTask() {
|
||||
// 🚀 扫描用户的 WIP/PENDING 任务,自动沉浸锁定
|
||||
if (!this.product || !this.product.task_tree) return null;
|
||||
let wipTask = null, pendingTask = null;
|
||||
const walk = (tasks) => {
|
||||
if (!tasks) return;
|
||||
for (const t of tasks) {
|
||||
const isMine = t.assignee_id == this.currentUserId || t.assignee_id == this.currentUsername;
|
||||
if (isMine && t.status === 'WIP') wipTask = t;
|
||||
if (isMine && t.status === 'PENDING' && !pendingTask) pendingTask = t;
|
||||
walk(t.child_tasks);
|
||||
}
|
||||
};
|
||||
walk(this.product.task_tree);
|
||||
return (wipTask || pendingTask) ? (wipTask || pendingTask).id : null;
|
||||
},
|
||||
toggleMode() { if (this.currentMode === 'workspace') this.currentMode = 'swipe'; else if (this.currentMode === 'swipe') this.currentMode = 'tree'; else this.currentMode = 'workspace'; },
|
||||
|
||||
async handleSetOverallStatus(status) { try { this.product = await patch(`/products/scan/${this.product.serial_number}/status`, { status }); uni.showToast({ title: `状态已更新: ${status}`, icon: "success" }); this.showStatusPicker = false; } catch {} },
|
||||
openEditProduct() { this.editForm = { order_no: this.product.order_no || "", external_serial: this.product.external_serial || "" }; this.editProductVisible = true; },
|
||||
|
||||
Reference in New Issue
Block a user