Files
track/track-uniapp/src/pages/scan/components/FlowTree.vue
duxingchen 25ad5156f8 style: 移动端流转树主干道对齐与连线箭头
- 主线(含 TRANSFER 子任务)左边缘对齐,消除缩进形成笔直主干道
- 主线卡片之间加向下连线箭头(↓)
- 分支(SPAWN 协助)保留缩进 + 左边线
2026-09-01 14:10:40 +08:00

175 lines
6.8 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="ft-node">
<!-- 节点卡片 -->
<view class="ft-card" :class="statusColorClass(node.status)">
<view class="ft-head">
<text class="ft-badge" :class="isMainNode ? 'ft-badge-main' : 'ft-badge-sub'">{{ isMainNode ? '主线' : '分支' }}</text>
<text :class="['ft-status', statusColorClass(node.status)]">{{ statusLabel(node.status) }}</text>
<text v-if="node.is_rework" class="ft-rework">返工</text>
</view>
<text class="ft-name">{{ node.task_name }}</text>
<view class="ft-meta">
<!-- 在库任务显示"转入在库"其余显示负责人 -->
<text v-if="isWarehouseTask" class="ft-warehouse-in">📥 转入在库: {{ formatUserName(node.assignee_id) }}</text>
<text v-else class="ft-assignee">👤 {{ formatUserName(node.assignee_id) || '未分配' }}</text>
</view>
<view class="ft-bottom">
<text class="ft-time"> {{ fmtDate(node.created_at) }}{{ node.completed_at ? ' → ' + fmtDate(node.completed_at) : ' → 至今' }}</text>
<text v-if="node.records && node.records.length" class="ft-records" @tap.stop="$emit('viewRecords', node)">📋 {{ node.records.length }} </text>
</view>
</view>
<!-- 子任务主线继续主干道(左对齐 + 连线)分支(SPAWN 协助)缩进 -->
<view v-if="node.child_tasks && node.child_tasks.length">
<!-- 主线子任务左边缘对齐形成笔直主干道之间用 连线 -->
<template v-for="(child, i) in mainChildren">
<view :key="'arrow-' + child.id" v-if="i > 0" class="ft-flow-arrow"></view>
<FlowTree
:key="child.id"
:node="child"
:current-user="currentUser"
:current-user-id="currentUserId"
:current-username="currentUsername"
@view-records="$emit('viewRecords', $event)"
/>
</template>
<!-- 分支子任务缩进 + 左边线 -->
<view v-if="branchChildren.length" class="ft-branch-children">
<FlowTree
v-for="child in branchChildren"
:key="child.id"
:node="child"
:current-user="currentUser"
:current-user-id="currentUserId"
:current-username="currentUsername"
@view-records="$emit('viewRecords', $event)"
/>
</view>
</view>
</view>
</template>
<script>
import { formatUserName as fmtUserName } from "../../../utils/format";
export default {
name: "FlowTree",
props: {
node: { type: Object, required: true },
currentUser: { type: Object, default: null },
currentUserId: { type: [String, Number], default: "" },
currentUsername: { type: String, default: "" },
},
emits: ["viewRecords"],
computed: {
isMainNode() {
return !this.node.parent_task_id
|| this.node.task_type === "TRANSFER"
|| this.node.task_type === "RECOVERY";
},
// 主线子任务TRANSFER/RECOVERY继续主干道左对齐
mainChildren() {
return (this.node.child_tasks || []).filter((c) =>
!c.parent_task_id || c.task_type === "TRANSFER" || c.task_type === "RECOVERY"
);
},
// 分支子任务:其余(SPAWN 协助等),缩进显示
branchChildren() {
return (this.node.child_tasks || []).filter((c) =>
c.parent_task_id && c.task_type !== "TRANSFER" && c.task_type !== "RECOVERY"
);
},
isWarehouseTask() {
const name = String(this.node.task_name || "");
return (name.includes("在库") || name.includes("入库")) || this.node.assignee_id === "virtual_warehouse";
},
},
methods: {
// Vue2 Options API 中 import 的函数需挂到 methods 才能被模板访问
formatUserName(userId) {
return fmtUserName(userId);
},
statusLabel(s) {
const map = { PENDING: "待接收", WIP: "进行中", COMPLETED: "已完成", REJECTED: "已驳回", ARCHIVED: "已入库", OUTBOUND: "已出库" };
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";
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>
.ft-node { margin-bottom: 10px; }
.ft-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);
}
/* 状态左色条 */
.ft-card.s-yellow { border-left-color: #f59e0b; }
.ft-card.s-blue { border-left-color: #3b82f6; }
.ft-card.s-green { border-left-color: #22c55e; }
.ft-card.s-red { border-left-color: #ef4444; }
.ft-card.s-archived { border-left-color: #8b5cf6; }
.ft-card.s-outbound { border-left-color: #4f46e5; }
.ft-card.s-gray { border-left-color: #9ca3af; }
.ft-head { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
.ft-badge { font-size: 10px; font-weight: 700; padding: 1px 6px; border-radius: 4px; color: #fff; }
.ft-badge-main { background: #2563eb; }
.ft-badge-sub { background: #7c3aed; }
.ft-status { font-size: 11px; font-weight: 700; padding: 1px 8px; border-radius: 10px; }
.s-yellow .ft-status { background: #fef3c7; color: #b45309; }
.s-blue .ft-status { background: #dbeafe; color: #1d4ed8; }
.s-green .ft-status { background: #dcfce7; color: #15803d; }
.s-red .ft-status { background: #fce4ec; color: #be123c; }
.s-archived .ft-status { background: #ede9fe; color: #7c3aed; }
.s-outbound .ft-status { background: #e0e7ff; color: #4338ca; }
.s-gray .ft-status { background: #f3f4f6; color: #6b7280; }
.ft-rework { font-size: 10px; background: #ef4444; color: #fff; padding: 1px 5px; border-radius: 4px; font-weight: 700; }
.ft-name { font-size: 14px; font-weight: 700; color: #1f2937; }
.ft-meta { margin-top: 4px; font-size: 12px; }
.ft-assignee { color: #4b5563; }
.ft-warehouse-in { color: #059669; font-weight: 700; }
.ft-bottom { display: flex; align-items: center; justify-content: space-between; margin-top: 6px; }
.ft-time { font-size: 11px; color: #9ca3af; }
.ft-records { font-size: 11px; color: #2563eb; font-weight: 600; }
/* 主线子任务:无缩进(左边缘对齐成笔直主干道),之间用 ↓ 连线 */
.ft-flow-arrow {
text-align: center;
color: #9ca3af;
font-size: 16px;
font-weight: 700;
line-height: 1;
margin: 2px 0;
}
/* 分支子任务:缩进 + 左边线 */
.ft-branch-children {
margin-left: 16px;
padding-left: 12px;
border-left: 2px solid #e5e7eb;
margin-top: 8px;
}
</style>