- 主线(含 TRANSFER 子任务)左边缘对齐,消除缩进形成笔直主干道 - 主线卡片之间加向下连线箭头(↓) - 分支(SPAWN 协助)保留缩进 + 左边线
122 lines
4.0 KiB
Vue
122 lines
4.0 KiB
Vue
<template>
|
||
<view class="tree-fullscreen">
|
||
<view class="tc-toolbar">
|
||
<view class="tc-toolbar-left">
|
||
<view class="tc-back-btn" @tap="$emit('back')">← 返回</view>
|
||
<text class="tc-title">🌳 流转树</text>
|
||
</view>
|
||
<view class="tc-toolbar-right">
|
||
<view class="tc-zoom-btn tc-mode-btn" @tap="$emit('swipe')">📇 卡片</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="!product.task_tree || !product.task_tree.length" class="zero-task">
|
||
<text class="zero-icon">📋</text><text class="zero-text">该产品暂无流转记录</text>
|
||
</view>
|
||
|
||
<scroll-view v-else scroll-y class="tree-scroll" :style="{ height: scrollHeight + 'px' }">
|
||
<view class="tree-root">
|
||
<!-- 递归纵向流转树:根级主线之间用 ↓ 连线,分支由 FlowTree 内部缩进 -->
|
||
<template v-for="(t, i) in product.task_tree">
|
||
<view :key="'arrow-' + i" v-if="i > 0" class="ft-flow-arrow">↓</view>
|
||
<FlowTree
|
||
:key="t.id"
|
||
:node="t"
|
||
:current-user="currentUser"
|
||
:current-user-id="currentUserId"
|
||
:current-username="currentUsername"
|
||
@view-records="$emit('viewRecords', $event)"
|
||
/>
|
||
</template>
|
||
</view>
|
||
|
||
<!-- 图例 -->
|
||
<view class="tree-legend">
|
||
<text class="legend-item legend-main">■ 主线</text>
|
||
<text class="legend-item legend-sub">□ 分支</text>
|
||
<text class="legend-item">📥 在库</text>
|
||
<text class="legend-item">📦 已入库</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import FlowTree from "./FlowTree.vue";
|
||
|
||
export default {
|
||
name: "TreeCanvas",
|
||
components: { FlowTree },
|
||
props: {
|
||
product: { type: Object, required: true },
|
||
currentUser: { type: Object, default: null },
|
||
currentUserId: { type: [String, Number], default: "" },
|
||
currentUsername: { type: String, default: "" },
|
||
},
|
||
emits: ["viewRecords", "back", "swipe"],
|
||
data() {
|
||
return { scrollHeight: 500 };
|
||
},
|
||
mounted() {
|
||
try {
|
||
const info = uni.getSystemInfoSync();
|
||
// 工具条高度 + 底部安全区预留约 70px,剩余给滚动区
|
||
this.scrollHeight = Math.max((info.windowHeight || 600) - 70, 300);
|
||
} catch (e) {}
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.tree-fullscreen {
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: #f5f7fa;
|
||
}
|
||
.tc-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 10px 12px;
|
||
padding-top: calc(10px + env(safe-area-inset-top));
|
||
background: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(10px);
|
||
border-bottom: 1px solid #e5e7eb;
|
||
flex-shrink: 0;
|
||
z-index: 999;
|
||
}
|
||
.tc-toolbar-left { display: flex; align-items: center; gap: 12px; }
|
||
.tc-back-btn { font-size: 14px; font-weight: 700; color: #2563eb; padding: 6px 12px; background: #eff6ff; border-radius: 8px; }
|
||
.tc-title { font-size: 16px; font-weight: 800; color: #1f2937; }
|
||
.tc-toolbar-right { display: flex; align-items: center; gap: 8px; }
|
||
.tc-zoom-btn { display: flex; align-items: center; justify-content: center; }
|
||
.tc-mode-btn { width: auto; padding: 0 10px; font-size: 12px; background: #ede9fe; color: #7c3aed; border-radius: 8px; height: 30px; }
|
||
.zero-task { display: flex; flex-direction: column; align-items: center; padding: 60px 0; }
|
||
.zero-icon { font-size: 40px; margin-bottom: 10px; }
|
||
.zero-text { font-size: 14px; color: #9ca3af; }
|
||
|
||
.tree-scroll { flex-shrink: 0; }
|
||
.tree-root { padding: 16px 12px 20px; }
|
||
/* 根级主线之间的向下连线箭头 */
|
||
.ft-flow-arrow {
|
||
text-align: center;
|
||
color: #9ca3af;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
margin: 2px 0;
|
||
}
|
||
.tree-legend {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
padding: 10px 16px 20px;
|
||
font-size: 11px;
|
||
color: #6b7280;
|
||
}
|
||
.legend-item { padding: 2px 8px; background: #fff; border-radius: 6px; border: 1px solid #e5e7eb; }
|
||
.legend-main { color: #2563eb; font-weight: 700; }
|
||
.legend-sub { color: #7c3aed; font-weight: 700; }
|
||
</style>
|