防呆重构: 废除swiper→任务列表+锁定工作区 + remark醒目显示 + 树视图减少缩进+分支闭环标记

This commit is contained in:
2026-08-05 16:56:52 +08:00
parent 78559db1fa
commit 9aa0687d8c
2 changed files with 110 additions and 90 deletions

View File

@ -22,8 +22,12 @@
<text class="remark-text">{{ task.remark || task.description }}</text>
</view>
</view>
<!-- 状态标签 -->
<text :class="['badge', statusColor(task.status)]">{{ statusLabel(task.status) }}</text>
<!-- 状态标签 + 分支闭环标记 -->
<view style="display:flex;flex-direction:column;align-items:flex-end;gap:4px;flex-shrink:0;margin-left:8px;">
<text :class="['badge', statusColor(task.status)]">{{ statusLabel(task.status) }}</text>
<text v-if="task.status === 'COMPLETED' && (!task.child_tasks || !task.child_tasks.length)" class="end-marker">🏁 已终止</text>
<text v-if="task.status === 'ARCHIVED'" class="end-marker end-warehouse">📦 已入库</text>
</view>
</view>
<!-- 第二层递归子任务树 -->
@ -154,7 +158,7 @@ export default {
</script>
<style scoped>
.tnode { position: relative; padding-left: 24px; margin-bottom: 4px; }
.tnode { position: relative; padding-left: 12px; margin-bottom: 6px; }
.tree-line-vertical { position: absolute; left: 8px; top: 28px; bottom: 0; width: 2px; background: #e5e7eb; }
.tnode-card {
@ -211,9 +215,11 @@ export default {
.badge {
font-size: 10px; padding: 2px 8px; border-radius: 20px; font-weight: 600;
white-space: nowrap; flex-shrink: 0; margin-left: 8px;
white-space: nowrap; flex-shrink: 0;
background: #f3f4f6; color: #6b7280;
}
.end-marker { font-size: 9px; padding: 1px 6px; border-radius: 8px; font-weight: 600; white-space: nowrap; background: #dcfce7; color: #16a34a; }
.end-warehouse { background: #ede9fe; color: #7c3aed; }
.s-yellow .badge { background: #fef3c7; color: #b45309; }
.s-blue .badge { background: #dbeafe; color: #1d4ed8; }
.s-green .badge { background: #dcfce7; color: #15803d; }

View File

@ -59,7 +59,7 @@
<!-- ================================================================ -->
<!-- 🛠 个人工作区 (workspace) -->
<!-- ================================================================ -->
<view v-if="currentMode === 'workspace'" class="swiper-area">
<view v-if="currentMode === 'workspace'" class="workspace-area">
<!-- 0任务 -->
<view v-if="!product.task_tree || !product.task_tree.length" class="zero-task">
<text class="zero-icon">📋</text>
@ -67,91 +67,87 @@
<button class="btn-start" @tap="openCreateFirstTask">🚀 发起首道工序</button>
</view>
<!-- 无本人任务但有其他任务 -->
<!-- 无本人任务 -->
<view v-else-if="!focusTasks.length" class="zero-task">
<text class="zero-icon">🔒</text>
<text class="zero-text">当前没有需要你处理的任务</text>
<text class="task-count"> {{ countTasks(product.task_tree) }} 个任务</text>
</view>
<!-- Swiper 卡片 -->
<!-- ====== 任务列表未锁定 ====== -->
<template v-else-if="!lockedTaskId">
<view class="list-header">
<text class="list-title">📋 待处理任务 ({{ focusTasks.length }})</text>
<text class="list-hint">点击任务进入工作区</text>
</view>
<view class="task-list">
<view v-for="t in focusTasks" :key="t.id" class="task-list-item"
:class="statusColor(t.status)" @tap="lockTask(t.id)">
<view class="tli-left">
<text class="tli-name">{{ t.task_name }}</text>
<text v-if="t.remark" class="tli-remark">{{ t.remark }}</text>
</view>
<view class="tli-right">
<text :class="['tli-badge', statusColor(t.status)]">{{ statusLabel(t.status) }}</text>
<text v-if="t.is_rework" class="tag tag-rework-sm" style="margin-top:2px;">返工</text>
<text class="tli-arrow"></text>
</view>
</view>
</view>
</template>
<!-- ====== 锁定工作区单任务详情 ====== -->
<template v-else>
<view class="swipe-nav">
<text class="swipe-info">{{ swiperCurrent + 1 }} / {{ focusTasks.length }}</text>
<text class="swipe-hint"> 左右滑动切换任务 </text>
<view class="lock-bar">
<text class="lock-back" @tap="lockedTaskId = null"> 返回列表</text>
<text class="lock-title">🔒 {{ lockedTask.task_name }}</text>
</view>
<swiper
class="task-swiper"
:current="swiperCurrent"
@change="onSwiperChange"
:indicator-dots="false"
:circular="false"
>
<swiper-item v-for="(t, i) in focusTasks" :key="t.id">
<view class="focus-card" :class="statusColor(t.status)">
<!-- 卡片头 -->
<view class="fc-header">
<text class="fc-status">{{ statusLabel(t.status) }}</text>
<text v-if="t.is_rework" class="tag tag-rework-sm">返工</text>
</view>
<view class="focus-card" :class="statusColor(lockedTask.status)">
<view class="fc-header">
<text class="fc-status">{{ statusLabel(lockedTask.status) }}</text>
<text v-if="lockedTask.is_rework" class="tag tag-rework-sm">返工</text>
</view>
<text class="fc-name">{{ lockedTask.task_name }}</text>
<!-- 任务名 -->
<text class="fc-name">{{ t.task_name }}</text>
<!-- 📌 交接/接收备注 -->
<view v-if="lockedTask.remark" class="fc-remark">
<text class="fc-remark-label">📌 交接/接收备注</text>
<text class="fc-remark-text">{{ lockedTask.remark }}</text>
</view>
<!-- remark初始说明 -->
<view v-if="t.remark" class="fc-remark">
<text class="fc-remark-label">📌</text>
<text class="fc-remark-text">{{ t.remark }}</text>
</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>
</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 || '' }}
<text v-if="c.status==='COMPLETED'" class="branch-done">已完成</text>
<text v-else-if="c.status==='ARCHIVED'" class="branch-done">📦已入库</text>
</text>
</view>
<!-- 来源/去向 -->
<view class="fc-chain">
<view v-if="t.parent_task_id && parentTaskOf(t)" class="fc-link fc-up">
<text class="fc-link-label"> 上一步</text>
<text class="fc-link-name">{{ parentTaskOf(t).task_name }}</text>
<text class="fc-link-who"> {{ parentTaskOf(t).assignee_id || '—' }}</text>
</view>
<view v-if="t.child_tasks && t.child_tasks.length" class="fc-link fc-down">
<text class="fc-link-label"> 下一步 ({{ t.child_tasks.length }})</text>
<text v-for="c in t.child_tasks" :key="c.id" class="fc-link-name">· {{ c.task_name }} {{ c.assignee_id || '' }}</text>
</view>
</view>
<view v-if="lockedTask.records && lockedTask.records.length" class="fc-records-bar" @tap="handleViewRecords(lockedTask)">
📋 {{ lockedTask.records.length }} 条干活记录
</view>
<view class="fc-time">{{ formatTaskTime(lockedTask) }}</view>
</view>
<!-- 最新记录 -->
<view v-if="t.records && t.records.length" class="fc-records-bar" @tap="handleViewRecords(t)">
📋 {{ t.records.length }} 条记录
</view>
<!-- 时间 -->
<view class="fc-time">{{ formatTaskTime(t) }}</view>
</view>
</swiper-item>
</swiper>
<view class="footer-actions">
<button v-if="lockedTask.status === 'WIP'" class="footer-btn footer-transfer"
@tap="handleTaskAction({ task: lockedTask, type: 'transfer' })">🔄 完工转交</button>
<button v-if="lockedTask.status === 'WIP'" class="footer-btn footer-record"
@tap="handleTaskAction({ task: lockedTask, type: 'record' })">📝 记录/拍照</button>
<button v-if="lockedTask.status === 'WIP'" class="footer-btn footer-end"
@tap="handleTaskAction({ task: lockedTask, type: 'end' })">🏁 结束分支</button>
<button v-if="lockedTask.status === 'PENDING'" class="footer-btn footer-receive"
@tap="handleTaskAction({ task: lockedTask, type: 'receive' })"> 接收任务</button>
<button v-if="lockedTask.status === 'PENDING'" class="footer-btn footer-reject"
@tap="handleTaskAction({ task: lockedTask, type: 'reject' })"> 驳回任务</button>
</view>
</template>
<!-- 底部操作栏 workspace 模式 -->
<view v-if="currentTask" class="footer-actions">
<button v-if="currentTask.status === 'WIP'" class="footer-btn footer-transfer"
@tap="handleTaskAction({ task: currentTask, type: 'transfer' })">
🔄 完工转交
</button>
<button v-if="currentTask.status === 'WIP'" class="footer-btn footer-record"
@tap="handleTaskAction({ task: currentTask, type: 'record' })">
📝 记录/拍照
</button>
<button v-if="currentTask.status === 'WIP'" class="footer-btn footer-end"
@tap="handleTaskAction({ task: currentTask, type: 'end' })">
🏁 结束分支
</button>
<button v-if="currentTask.status === 'PENDING'" class="footer-btn footer-receive"
@tap="handleTaskAction({ task: currentTask, type: 'receive' })">
接收任务
</button>
<button v-if="currentTask.status === 'PENDING'" class="footer-btn footer-reject"
@tap="handleTaskAction({ task: currentTask, type: 'reject' })">
驳回任务
</button>
</view>
</view>
<!-- ================================================================ -->
@ -418,8 +414,8 @@ export default {
currentUser: null,
currentUserId: "",
currentUsername: "",
swiperCurrent: 0,
currentMode: "tree",
lockedTaskId: null,
// Picker 数据源
processOptions: [],
userOptions: [],
@ -473,8 +469,8 @@ export default {
if (this.product) walk(this.product.task_tree);
return result;
},
currentTask() {
return this.focusTasks[this.swiperCurrent] || null;
lockedTask() {
return this.focusTasks.find(t => t.id === this.lockedTaskId) || null;
},
hasMyActiveTask() {
return this.focusTasks.length > 0;
@ -508,14 +504,17 @@ export default {
// 智能分流:有本人活跃任务 → workspace否则 → tree
this.$nextTick(() => {
this.currentMode = this.hasMyActiveTask ? 'workspace' : 'tree';
this.swiperCurrent = 0;
this.lockedTaskId = null;
});
} catch (e) { this.error = e?.data?.detail || "查询失败"; }
finally { this.loading = false; }
},
toggleMode() {
this.currentMode = this.currentMode === 'workspace' ? 'tree' : 'workspace';
if (this.currentMode === 'workspace') this.swiperCurrent = 0;
if (this.currentMode === 'workspace') this.lockedTaskId = null;
},
lockTask(taskId) {
this.lockedTaskId = taskId;
},
// ---- 状态定调 ----
@ -804,9 +803,6 @@ export default {
this.doQuery(this.product.serial_number);
} catch {}
},
onSwiperChange(e) {
this.swiperCurrent = e.detail.current;
},
parentTaskOf(t) {
if (!t || !t.parent_task_id) return null;
return this.taskMap[t.parent_task_id] || null;
@ -910,12 +906,30 @@ export default {
/* 卡片 */
.card { background: #fff; border-radius: 12px; padding: 14px; margin-bottom: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); flex-shrink: 0; }
/* Swiper 卡片看板 */
.swiper-area { flex: 1; display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
.swipe-nav { display: flex; align-items: center; justify-content: space-between; padding: 8px 4px; flex-shrink: 0; }
.swipe-info { font-size: 12px; font-weight: 700; color: #2563eb; }
.swipe-hint { font-size: 11px; color: #9ca3af; }
.task-swiper { flex: 1; min-height: 0; }
/* 工作区 */
.workspace-area { flex: 1; display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
.list-header { display: flex; align-items: baseline; justify-content: space-between; padding: 8px 4px; flex-shrink: 0; }
.list-title { font-size: 14px; font-weight: 700; color: #1f2937; }
.list-hint { font-size: 11px; color: #9ca3af; }
.task-list { flex: 1; overflow-y: auto; min-height: 0; }
.task-list-item { display: flex; align-items: flex-start; justify-content: space-between; background: #fff; border-radius: 12px; padding: 14px; margin-bottom: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); border-left: 4px solid transparent; }
.task-list-item.s-yellow { border-left-color: #f59e0b; }
.task-list-item.s-blue { border-left-color: #3b82f6; }
.tli-left { flex: 1; min-width: 0; }
.tli-name { font-size: 15px; font-weight: 700; color: #1f2937; display: block; }
.tli-remark { font-size: 12px; color: #6b7280; margin-top: 4px; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tli-right { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex-shrink: 0; margin-left: 8px; }
.tli-badge { font-size: 11px; padding: 2px 10px; border-radius: 12px; font-weight: 600; }
.tli-badge.s-yellow { background: #fef3c7; color: #b45309; }
.tli-badge.s-blue { background: #dbeafe; color: #1d4ed8; }
.tli-arrow { font-size: 18px; color: #d1d5db; }
.lock-bar { display: flex; align-items: center; gap: 12px; padding: 8px 0; flex-shrink: 0; }
.lock-back { font-size: 13px; color: #2563eb; font-weight: 600; }
.lock-title { font-size: 14px; font-weight: 700; color: #1f2937; }
.branch-done { font-size: 11px; font-weight: 600; color: #16a34a; }
.branch-active { font-size: 11px; font-weight: 600; color: #2563eb; }
.task-count { font-size: 12px; color: #9ca3af; display: block; margin-top: 4px; }
.focus-card {