Files
track/track-uniapp/src/pages/scan/components/FlowCard.vue

176 lines
7.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="fc-card" :class="[statusColorClass(node.status), { 'is-completed': isCompleted, 'is-active': isActive }]">
<view class="fc-head">
<!-- 主线/分支 标签(仅中央主线显示) -->
<text v-if="showBadge" class="fc-badge" :class="isMainNode ? 'fc-badge-main' : 'fc-badge-sub'">{{ isMainNode ? '主线' : '分支' }}</text>
<text :class="['fc-status', statusColorClass(node.status)]">{{ statusLabel(node.status) }}</text>
<text v-if="node.is_rework" class="fc-rework">⚠返工</text>
</view>
<text class="fc-name">{{ node.task_name }}</text>
<view class="fc-meta">
<!-- 仓储系统任务:系统图标 + 固定名(assignee 为 null 不报错) -->
<text v-if="isSystemTask" class="fc-assignee">📦 MOM 仓储系统</text>
<!-- 在库任务显示"转入在库",其余显示负责人 -->
<text v-else-if="isWarehouseTask" class="fc-warehouse-in">📥 转入在库: {{ formatUserName(node.assignee_id) }}</text>
<text v-else class="fc-assignee">👤 {{ formatUserName(node.assignee_id) || '未分配' }}</text>
</view>
<!-- 时间独占一行 -->
<text class="fc-time">⏰ {{ fmtDate(node.created_at) }}{{ node.completed_at ? '→' + fmtDate(node.completed_at) : '→至今' }}</text>
<!-- 驳回/返工原因:原 REJECTED 取 reject_reason;返工节点取含“驳回/返工”的备注 -->
<text v-if="reasonText" style="display:block;margin:8rpx 0 0;font-size:22rpx;line-height:1.4;color:#dc2626;font-weight:600;">❌ {{ reasonText }}</text>
<!-- 卡片底部:全宽蓝色日志条(对齐 Web 端) -->
<view v-if="node.records && node.records.length" class="ft-log-bar" @tap.stop="$emit('viewRecords', node)">
查看操作日志 ({{ node.records.length }}条) ›
</view>
</view>
</template>
<script>
import { formatUserName as fmtUserName } from "../../../utils/format";
export default {
name: "FlowCard",
props: {
node: { type: Object, required: true },
showBadge: { type: Boolean, default: false },
},
emits: ["viewRecords"],
computed: {
// 驳回/返工原因:原 REJECTED 有 reject_reason;返工节点原因在 remark(形如“返工任务(驳回自…原因…)”)
reasonText() {
const n = this.node || {};
if (n.reject_reason) return `驳回: ${n.reject_reason}`;
const rm = n.remark;
return rm && (rm.indexOf("驳回") >= 0 || rm.indexOf("返工") >= 0) ? rm : "";
},
isMainNode() {
return !this.node.parent_task_id
|| this.node.task_type === "TRANSFER"
|| this.node.task_type === "RECOVERY"
|| this.node.task_type === "WAREHOUSE";
},
// ★ 仓储系统任务(扫码入库/扫码出库):防御性渲染,不请求用户数据
isSystemTask() {
const name = String(this.node.task_name || "");
return name.includes("扫码") || this.node.task_type === "WAREHOUSE";
},
isWarehouseTask() {
const name = String(this.node.task_name || "");
return (name.includes("在库") || name.includes("入库")) || this.node.assignee_id === "virtual_warehouse";
},
// 进行中/待接收:WIP / PENDING / IN_PROGRESS(待收货)
isActive() {
return ["WIP", "PENDING", "IN_PROGRESS"].includes(this.node.status);
},
// 已完成 / 历史状态:非进行中
isCompleted() {
return !this.isActive;
},
},
methods: {
formatUserName(userId) {
return fmtUserName(userId);
},
statusLabel(s) {
const map = { PENDING: "待接收", WIP: "进行中", COMPLETED: "已完成", REJECTED: "已驳回", ARCHIVED: "已入库", OUTBOUND: "已出库", IN_PROGRESS: "待收货" };
return map[s] || s;
},
statusColorClass(s) {
switch (s) {
case "PENDING": return "s-yellow";
case "WIP": return "s-blue";
case "COMPLETED": return "s-green";
case "REJECTED": return "s-red";
case "ARCHIVED": return "s-archived";
case "OUTBOUND": return "s-outbound";
case "IN_PROGRESS": return "s-orange";
default: return "s-gray";
}
},
fmtDate(t) {
if (!t) return "";
const d = new Date(t);
const p = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
},
},
};
</script>
<style scoped>
.fc-card {
background: #fff;
border-radius: 10px;
border: 1px solid #e5e7eb;
border-left-width: 4px;
padding: 10px 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
width: 168px;
flex-shrink: 0;
}
.fc-card.s-yellow { border-left-color: #f59e0b; }
.fc-card.s-blue { border-left-color: #3b82f6; }
.fc-card.s-green { border-left-color: #22c55e; }
.fc-card.s-red { border-left-color: #ef4444; }
.fc-card.s-archived { border-left-color: #8b5cf6; }
.fc-card.s-outbound { border-left-color: #4f46e5; }
.fc-card.s-orange { border-left-color: #f97316; }
.fc-card.s-gray { border-left-color: #9ca3af; }
/* 已完成/历史状态:降透明度 + 浅灰底 + 深灰文字(视觉降噪) */
.fc-card.is-completed {
opacity: 0.85;
background-color: #fafafa;
}
.fc-card.is-completed .fc-name,
.fc-card.is-completed .fc-assignee,
.fc-card.is-completed .fc-warehouse-in {
color: #6b7280;
}
/* 进行中/待接收:纯白底 + 完整蓝色边框 + 外发光,浮起来 */
.fc-card.is-active {
background-color: #ffffff;
border: 1px solid #93c5fd;
box-shadow: 0 4px 16px rgba(59, 130, 246, 0.12);
}
.fc-head { display: flex; align-items: center; gap: 4px; margin-bottom: 4px; flex-wrap: wrap; }
.fc-badge { font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 4px; color: #fff; }
.fc-badge-main { background: #2563eb; }
.fc-badge-sub { background: #7c3aed; }
.fc-status { font-size: 11px; font-weight: 700; padding: 1px 8px; border-radius: 10px; }
.s-yellow .fc-status { background: #fef3c7; color: #b45309; }
.s-blue .fc-status { background: #dbeafe; color: #1d4ed8; }
.s-green .fc-status { background: #dcfce7; color: #15803d; }
.s-red .fc-status { background: #fce4ec; color: #be123c; }
.s-archived .fc-status { background: #ede9fe; color: #7c3aed; }
.s-outbound .fc-status { background: #e0e7ff; color: #4338ca; }
.s-orange .fc-status { background: #ffedd5; color: #ea580c; }
.s-gray .fc-status { background: #f3f4f6; color: #6b7280; }
.fc-rework { font-size: 10px; background: #ef4444; color: #fff; padding: 1px 5px; border-radius: 4px; font-weight: 700; }
.fc-name { font-size: 13px; font-weight: 700; color: #1f2937; }
.fc-meta { margin-top: 4px; font-size: 11px; }
.fc-assignee { color: #4b5563; }
.fc-warehouse-in { color: #059669; font-weight: 700; }
/* 时间独占一行 */
.fc-time { display: block; font-size: 10px; color: #9ca3af; margin-top: 6px; }
/* 操作日志:底部全宽蓝色条块(对齐 Web 端) */
.ft-log-bar {
width: calc(100% + 24px);
margin-left: -12px;
margin-right: -12px;
margin-bottom: -10px;
margin-top: 10px;
background-color: #eff6ff;
color: #2563eb;
text-align: center;
padding: 8px 0;
font-size: 13px;
border-top: 1px solid #dbeafe;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
.ft-log-bar:active { background-color: #dbeafe; }
</style>