176 lines
7.1 KiB
Vue
176 lines
7.1 KiB
Vue
<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>
|