feat: 移动端全局人名映射 + 协同留言板 UI
- detail.vue: 留言板聊天室 UI + 乐观更新 + loadUsers 填充全局字典 - WorkspaceArea.vue: 任务列表/上游/下游负责人全部走 formatUserName - TreeCanvas.vue: 节点负责人 + 嵌套协助提示走 formatUserName - TaskSwipeCards.vue: 卡片负责人走 formatUserName - 留言板头像取中文名末字(formatUserAvatar)
This commit is contained in:
@ -25,7 +25,7 @@
|
||||
<view class="tli-left">
|
||||
<text class="tag-branch">{{ branchLabelsMap[t.id] || '' }}</text>
|
||||
<text class="tli-name">{{ t.task_name }}</text>
|
||||
<text class="tli-assignee">→ {{ t.assignee_id || '未分配' }}</text>
|
||||
<text class="tli-assignee">→ {{ formatUserName(t.assignee_id) || '未分配' }}</text>
|
||||
<text v-if="getTaskRemark(t)" class="tli-remark">{{ getTaskRemark(t) }}</text>
|
||||
</view>
|
||||
<view class="tli-right">
|
||||
@ -60,13 +60,13 @@
|
||||
</view>
|
||||
|
||||
<view v-if="lockedTask.parent_task_id && parentTaskOf(lockedTask)" class="fc-link fc-up">
|
||||
<text class="fc-link-label">⬆ 上游工序</text>
|
||||
<text class="fc-link-name">{{ parentTaskOf(lockedTask).task_name }} → {{ parentTaskOf(lockedTask).assignee_id || '—' }}</text>
|
||||
<text class="fc-link-label">{{ isNestedSpawn ? '⬆ 上游协助 (嵌套)' : '⬆ 上游工序' }}</text>
|
||||
<text class="fc-link-name">{{ parentTaskOf(lockedTask).task_name }} → {{ formatUserName(parentTaskOf(lockedTask).assignee_id) || '—' }}</text>
|
||||
</view>
|
||||
<view v-if="lockedTask.child_tasks && lockedTask.child_tasks.length" class="fc-link fc-down">
|
||||
<text class="fc-link-label">⬇ 下游分支 ({{ lockedTask.child_tasks.length }})</text>
|
||||
<text v-for="c in lockedTask.child_tasks" :key="c.id" class="fc-link-name">
|
||||
· {{ c.task_name }} → {{ c.assignee_id || '—' }}
|
||||
· {{ c.task_name }} → {{ formatUserName(c.assignee_id) || '—' }}
|
||||
<text v-if="c.status==='COMPLETED'" class="branch-done">✓已完成</text>
|
||||
<text v-else-if="c.status==='ARCHIVED'" class="branch-done">📦已入库</text>
|
||||
</text>
|
||||
@ -102,6 +102,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatUserName } from "../../../utils/format";
|
||||
const STATUS_MAP = { PENDING: "待接收", WIP: "进行中", COMPLETED: "已完成", REJECTED: "已驳回", ARCHIVED: "已入库", CANCELED: "已撤回" };
|
||||
|
||||
export default {
|
||||
@ -142,6 +143,7 @@ export default {
|
||||
},
|
||||
focusTasks() {
|
||||
const result = [];
|
||||
const isMainFn = (t) => !t.parent_task_id || t.task_type === 'TRANSFER' || t.task_type === 'RECOVERY';
|
||||
const walk = (tasks) => {
|
||||
if (!tasks) return;
|
||||
for (const t of tasks) {
|
||||
@ -151,8 +153,8 @@ export default {
|
||||
};
|
||||
if (this.product) walk(this.product.task_tree);
|
||||
result.sort((a, b) => {
|
||||
const aIsMain = !a.parent_task_id || a.task_type === 'TRANSFER' || a.task_type === 'RECOVERY';
|
||||
const bIsMain = !b.parent_task_id || b.task_type === 'TRANSFER' || b.task_type === 'RECOVERY';
|
||||
const aIsMain = isMainFn(a);
|
||||
const bIsMain = isMainFn(b);
|
||||
if (aIsMain && !bIsMain) return -1;
|
||||
if (!aIsMain && bIsMain) return 1;
|
||||
return new Date(a.created_at) - new Date(b.created_at);
|
||||
@ -169,11 +171,17 @@ export default {
|
||||
if (!parent) return false;
|
||||
return parent.assignee_id == this.currentUserId || parent.assignee_id == this.currentUsername;
|
||||
},
|
||||
// 🚀 嵌套协助判定:直接父任务不是主线 → 孙子/曾孙协助
|
||||
isNestedSpawn() {
|
||||
if (!this.lockedTask || !this.lockedTask.parent_task_id) return false;
|
||||
const parent = this.taskMap[this.lockedTask.parent_task_id];
|
||||
if (!parent) return false;
|
||||
return parent.parent_task_id && parent.task_type !== 'TRANSFER' && parent.task_type !== 'RECOVERY';
|
||||
},
|
||||
branchLabelsMap() {
|
||||
const map = {};
|
||||
if (!this.product || !this.product.task_tree) return map;
|
||||
|
||||
// 拍平树结构,方便随时查父节点状态
|
||||
const flatMap = {};
|
||||
const flatten = (tasks) => {
|
||||
if (!tasks) return;
|
||||
@ -181,22 +189,14 @@ export default {
|
||||
};
|
||||
flatten(this.product.task_tree);
|
||||
|
||||
// 🚀 仅凭基因字段判定,移除危险的状态兜底
|
||||
const isMainFn = (t) => !t.parent_task_id || t.task_type === 'TRANSFER' || t.task_type === 'RECOVERY';
|
||||
|
||||
const traverse = (tasks, prefix) => {
|
||||
if (!tasks) return;
|
||||
let spawnIndex = 0;
|
||||
tasks.forEach((t) => {
|
||||
const parent = t.parent_task_id ? flatMap[t.parent_task_id] : null;
|
||||
|
||||
// 🚀 终极防御判定:
|
||||
// 1. 没有父节点(打底任务) -> 主分支
|
||||
// 2. 明确的基因 (TRANSFER/RECOVERY) -> 主分支
|
||||
// 3. 兜底:如果基因丢失,但它的父节点已经完工(COMPLETED),那它必然是接力的转交任务 -> 主分支
|
||||
const isMain = !t.parent_task_id
|
||||
|| t.task_type === 'TRANSFER'
|
||||
|| t.task_type === 'RECOVERY'
|
||||
|| (!t.task_type && parent && (parent.status === 'COMPLETED' || parent.status === 'CANCELED'));
|
||||
|
||||
if (isMain) {
|
||||
if (isMainFn(t)) {
|
||||
map[t.id] = '主分支';
|
||||
traverse(t.child_tasks, prefix);
|
||||
} else {
|
||||
@ -215,6 +215,7 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatUserName,
|
||||
statusLabel(s) { return STATUS_MAP[s] || s; },
|
||||
statusColor(s) { switch (s) { case "PENDING": return "s-yellow"; case "WIP": return "s-blue"; case "COMPLETED": return "s-green"; case "REJECTED": return "s-red"; default: return "s-gray"; } },
|
||||
countTasks(tree) { return tree ? tree.reduce((s, t) => s + 1 + this.countTasks(t.child_tasks), 0) : 0; },
|
||||
|
||||
Reference in New Issue
Block a user