feat: 移动端工序选项隔离与售后标签
This commit is contained in:
@ -10,6 +10,8 @@
|
||||
<view class="overall-bar" @tap="handleOverallBarClick">
|
||||
<text class="overall-label">宏观状态</text>
|
||||
<text :class="['overall-val', overallStatusClass(product.overall_status)]">{{ product.overall_status || '未激活 — 点击发起首道工序' }}</text>
|
||||
<!-- 🔧 生命周期标识:生产阶段「发货测试」 vs 出库回流后「售后维修」 -->
|
||||
<text v-if="lifeBadge" :class="['life-badge', lifeBadge.cls]">{{ lifeBadge.label }}</text>
|
||||
<text v-if="product.task_tree && product.task_tree.length && canEditOverallStatus" class="overall-arrow">▾</text>
|
||||
</view>
|
||||
|
||||
@ -70,7 +72,7 @@
|
||||
<text class="sheet-title">{{ product && product.overall_status ? '修改宏观状态' : '🔔 请设定产品宏观状态' }}</text>
|
||||
<text class="sheet-hint">首次扫码,请选择一个状态以开启流转</text>
|
||||
<view class="sheet-options">
|
||||
<view v-for="opt in OVERALL_OPTIONS" :key="opt" :class="['sheet-opt', product && product.overall_status === opt ? 'sheet-opt-active' : '']" @tap="handleSetOverallStatus(opt)"><text>{{ opt }}</text></view>
|
||||
<view v-for="opt in availableOverallOptions" :key="opt" :class="['sheet-opt', product && product.overall_status === opt ? 'sheet-opt-active' : '']" @tap="handleSetOverallStatus(opt)"><text>{{ opt }}</text></view>
|
||||
</view>
|
||||
<button v-if="product && product.overall_status" class="sheet-close" @tap="showStatusPicker = false">关闭</button>
|
||||
</view>
|
||||
@ -124,7 +126,7 @@
|
||||
<text class="popup-hint">状态: {{ statusLabel(actionPopup.task && actionPopup.task.status) }} → 进行中</text>
|
||||
<view class="field-label">选择工序 <text class="required">*</text></view>
|
||||
<view class="user-grid">
|
||||
<view v-for="opt in TASK_NAME_OPTIONS" :key="opt"
|
||||
<view v-for="opt in availableTaskOptions" :key="opt"
|
||||
:class="['user-grid-item', receiveTaskName === opt ? 'user-grid-active' : '']"
|
||||
@tap="receiveTaskName = opt">{{ opt }}</view>
|
||||
</view>
|
||||
@ -217,21 +219,22 @@
|
||||
<script>
|
||||
import request, { get, post, patch, put, getBaseUrl } from "../../utils/request";
|
||||
import { setUserNameMap, formatUserName, formatUserAvatar } from "../../utils/format";
|
||||
import { taskOptionsFor, overallOptionsFor, lifecycleBadge } from "../../utils/lifecycle";
|
||||
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 = ["备货", "生产", "测试", "维修", "在库"];
|
||||
// 🔧 工序可选项不再写死 —— 由 computed availableOverallOptions / availableTaskOptions
|
||||
// 按生命周期阶段(生产制造 / 售后回流)动态收窄,词表见 utils/lifecycle.js
|
||||
const STATUS_MAP = { PENDING: "待接收", WIP: "进行中", COMPLETED: "已完成", REJECTED: "已驳回", ARCHIVED: "已入库", OUTBOUND: "已出库", CANCELED: "已撤回" };
|
||||
|
||||
export default {
|
||||
components: { WorkspaceArea, TreeCanvas, TaskSwipeCards },
|
||||
data() {
|
||||
return {
|
||||
OVERALL_OPTIONS, loading: true, error: "", product: null,
|
||||
loading: true, error: "", product: null,
|
||||
showStatusPicker: false, editProductVisible: false, editForm: { order_no: "", external_serial: "" }, editSaving: false,
|
||||
users: [], TASK_NAME_OPTIONS,
|
||||
users: [],
|
||||
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: "", currentUserRole: "", currentMode: "workspace", autoLockTaskId: "",
|
||||
@ -287,6 +290,42 @@ export default {
|
||||
checkTask(this.product.task_tree);
|
||||
return hasPermission;
|
||||
},
|
||||
// 🔧 生命周期标签:只在售后回流环节(发货测试 / 售后维修)打红色标签
|
||||
lifeBadge() {
|
||||
return lifecycleBadge(
|
||||
this.product && this.product.overall_status,
|
||||
this.product && this.product.lifecycle_phase,
|
||||
);
|
||||
},
|
||||
// ── 🔧 选项隔离:按生命周期阶段收窄可选工序 ──
|
||||
// 生产制造设备只能排「备货/生产/测试/维修/在库」;
|
||||
// 售后回流设备只能选「发货测试/售后维修/在库」,排不回前期环节。
|
||||
isAfterSales() {
|
||||
return !!(this.product && this.product.lifecycle_phase === "AFTER_SALES");
|
||||
},
|
||||
// 是否已有"真实工序"历史 —— 仅判断 task_tree 非空是不够的:
|
||||
// 老设备首次「发起首道工序」建出来的任务名是占位符「待确认」,
|
||||
// 此时产品已有一条任务,但接收人还没机会声明工序。若按 task_tree 非空
|
||||
// 就判定"有历史",接收下拉将只给生产工序,老设备永远选不到「售后维修」,
|
||||
// 售后通路直接断掉。故这里必须排除占位符。
|
||||
hasHistory() {
|
||||
const walk = (tasks) => {
|
||||
if (!tasks) return false;
|
||||
for (const t of tasks) {
|
||||
const name = String(t.task_name || "").trim();
|
||||
if (name && name !== "待确认" && !name.includes("virtual_warehouse")) return true;
|
||||
if (walk(t.child_tasks)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return walk(this.product && this.product.task_tree);
|
||||
},
|
||||
availableOverallOptions() {
|
||||
return overallOptionsFor(this.product && this.product.lifecycle_phase, this.hasHistory);
|
||||
},
|
||||
availableTaskOptions() {
|
||||
return taskOptionsFor(this.product && this.product.lifecycle_phase, this.hasHistory);
|
||||
},
|
||||
},
|
||||
onLoad(options) { this.loadUsers(); this.loadCurrentUser(); const sn = options.serial || ""; if (sn) { this.doQuery(sn); return; } /* 🚀 兜底: 从 taskId 反查 product */ const tid = options.taskId || ""; if (tid) this.doQueryByTask(tid); },
|
||||
// 🚀 onShow 生命周期:每次页面显示时刷新留言板(解决从聊天室退回不更新问题)
|
||||
@ -335,7 +374,7 @@ export default {
|
||||
async loadUsers() { try { const res = await get("/users/", { limit: 200, dept: "IRIS" }); this.users = (res || []).filter(u => u.department === "IRIS"); this.userOptions = this.users.map(u => ({ id: u.username || u.id, name: u.full_name || u.username })); setUserNameMap(this.users); this.dictVersion += 1; } catch (e) { console.error('[loadUsers] 获取用户列表失败:', e); } },
|
||||
loadCurrentUser() { try { let user = uni.getStorageSync("user"); if (typeof user === "string" && user) { try { user = JSON.parse(user); } catch (e) { user = null; } } if (user && typeof user === "object") { this.currentUser = user; this.currentUserId = String(user.id || ""); this.currentUsername = user.username || ""; this.currentUserRole = user.role || ""; } } catch {} },
|
||||
|
||||
onTaskNameChange(e) { const idx = e.detail.value; this.firstForm.taskNameIdx = idx; this.firstForm.task_name = TASK_NAME_OPTIONS[idx]; },
|
||||
onTaskNameChange(e) { const idx = e.detail.value; this.firstForm.taskNameIdx = idx; this.firstForm.task_name = this.availableTaskOptions[idx]; },
|
||||
onAssigneeChange(e) { const idx = e.detail.value; const u = this.users[idx]; if (u) { this.firstForm.assigneeIdx = idx; this.firstForm.assignee_id = u.username; this.firstForm.assigneeLabel = `${u.full_name} (${u.username})`; } },
|
||||
openCreateFirstTask() { this.isWarehouseTransfer = false; if (this.currentMode === 'tree') this.currentMode = 'workspace'; this.firstForm = { assignee_id: "", assigneeLabel: "", note: "" }; this.createFirstVisible = true; },
|
||||
handleOverallBarClick() { if (!this.product.task_tree || !this.product.task_tree.length) { this.openCreateFirstTask(); } else if (this.canEditOverallStatus) { this.showStatusPicker = true; } else { uni.showToast({ title: '仅超级管理员或当前主线负责人可修改状态', icon: 'none', duration: 2500 }); } },
|
||||
@ -356,7 +395,7 @@ export default {
|
||||
if (type === "deleteRecord") { this.doDeleteRecord(record); return; }
|
||||
if (type === "end") { this.confirmEndBranch(task); return; }
|
||||
if (type === "recall") { this.confirmRecall(task); return; }
|
||||
if (type === "transfer" || type === "spawn") { uni.showLoading({ title: "加载数据..." }); try { if (!this.userOptions.length) await this.loadUsers(); if (!this.processOptions.length) this.processOptions = ["🏭 入库 (virtual_warehouse)", ...TASK_NAME_OPTIONS]; } finally { uni.hideLoading(); } }
|
||||
if (type === "transfer" || type === "spawn") { uni.showLoading({ title: "加载数据..." }); try { if (!this.userOptions.length) await this.loadUsers(); this.processOptions = ["🏭 入库 (virtual_warehouse)", ...this.availableTaskOptions]; } finally { uni.hideLoading(); } }
|
||||
this.actionPopup = { visible: true, type, task }; this.rejectReason = ""; this.receiveRemark = ""; this.receiveTaskName = "";
|
||||
this.transferForm = { selectedUserId: "", isWarehouse: false, note: "" };
|
||||
this.spawnForm = { assignee_id: "", remark: "" };
|
||||
@ -490,6 +529,9 @@ export default {
|
||||
.overall-outbound { color: #4f46e5; } /* 已出库:靛蓝 */
|
||||
.overall-warehouse { color: #ea580c; } /* 待仓库收货:橙 */
|
||||
.overall-arrow { font-size: 12px; color: #9ca3af; }
|
||||
/* 🔧 售后回流标识:红底白字,回流设备一眼可辨(生产阶段不打标) */
|
||||
.life-badge { font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 20px; flex-shrink: 0; }
|
||||
.life-badge-after { background: #dc2626; color: #ffffff; }
|
||||
.card { background: #fff; border-radius: 12px; padding: 14px; margin-bottom: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); flex-shrink: 0; min-height: 120px; }
|
||||
.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; gap: 6px; }
|
||||
.card-header-right { display: flex; align-items: center; gap: 4px; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
|
||||
|
||||
Reference in New Issue
Block a user