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

122 lines
4.0 KiB
Vue
Raw Normal View History

<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>