防呆重构: 废除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> <text class="remark-text">{{ task.remark || task.description }}</text>
</view> </view>
</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> </view>
<!-- 第二层递归子任务树 --> <!-- 第二层递归子任务树 -->
@ -154,7 +158,7 @@ export default {
</script> </script>
<style scoped> <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; } .tree-line-vertical { position: absolute; left: 8px; top: 28px; bottom: 0; width: 2px; background: #e5e7eb; }
.tnode-card { .tnode-card {
@ -211,9 +215,11 @@ export default {
.badge { .badge {
font-size: 10px; padding: 2px 8px; border-radius: 20px; font-weight: 600; 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; 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-yellow .badge { background: #fef3c7; color: #b45309; }
.s-blue .badge { background: #dbeafe; color: #1d4ed8; } .s-blue .badge { background: #dbeafe; color: #1d4ed8; }
.s-green .badge { background: #dcfce7; color: #15803d; } .s-green .badge { background: #dcfce7; color: #15803d; }

View File

@ -59,7 +59,7 @@
<!-- ================================================================ --> <!-- ================================================================ -->
<!-- 🛠 个人工作区 (workspace) --> <!-- 🛠 个人工作区 (workspace) -->
<!-- ================================================================ --> <!-- ================================================================ -->
<view v-if="currentMode === 'workspace'" class="swiper-area"> <view v-if="currentMode === 'workspace'" class="workspace-area">
<!-- 0任务 --> <!-- 0任务 -->
<view v-if="!product.task_tree || !product.task_tree.length" class="zero-task"> <view v-if="!product.task_tree || !product.task_tree.length" class="zero-task">
<text class="zero-icon">📋</text> <text class="zero-icon">📋</text>
@ -67,91 +67,87 @@
<button class="btn-start" @tap="openCreateFirstTask">🚀 发起首道工序</button> <button class="btn-start" @tap="openCreateFirstTask">🚀 发起首道工序</button>
</view> </view>
<!-- 无本人任务但有其他任务 --> <!-- 无本人任务 -->
<view v-else-if="!focusTasks.length" class="zero-task"> <view v-else-if="!focusTasks.length" class="zero-task">
<text class="zero-icon">🔒</text> <text class="zero-icon">🔒</text>
<text class="zero-text">当前没有需要你处理的任务</text> <text class="zero-text">当前没有需要你处理的任务</text>
<text class="task-count"> {{ countTasks(product.task_tree) }} 个任务</text> <text class="task-count"> {{ countTasks(product.task_tree) }} 个任务</text>
</view> </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> <template v-else>
<view class="swipe-nav"> <view class="lock-bar">
<text class="swipe-info">{{ swiperCurrent + 1 }} / {{ focusTasks.length }}</text> <text class="lock-back" @tap="lockedTaskId = null"> 返回列表</text>
<text class="swipe-hint"> 左右滑动切换任务 </text> <text class="lock-title">🔒 {{ lockedTask.task_name }}</text>
</view> </view>
<swiper <view class="focus-card" :class="statusColor(lockedTask.status)">
class="task-swiper" <view class="fc-header">
:current="swiperCurrent" <text class="fc-status">{{ statusLabel(lockedTask.status) }}</text>
@change="onSwiperChange" <text v-if="lockedTask.is_rework" class="tag tag-rework-sm">返工</text>
:indicator-dots="false" </view>
:circular="false" <text class="fc-name">{{ lockedTask.task_name }}</text>
>
<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>
<!-- 任务名 --> <!-- 📌 交接/接收备注 -->
<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="lockedTask.parent_task_id && parentTaskOf(lockedTask)" class="fc-link fc-up">
<view v-if="t.remark" class="fc-remark"> <text class="fc-link-label"> 上游工序</text>
<text class="fc-remark-label">📌</text> <text class="fc-link-name">{{ parentTaskOf(lockedTask).task_name }} {{ parentTaskOf(lockedTask).assignee_id || '—' }}</text>
<text class="fc-remark-text">{{ t.remark }}</text> </view>
</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 v-if="lockedTask.records && lockedTask.records.length" class="fc-records-bar" @tap="handleViewRecords(lockedTask)">
<view class="fc-chain"> 📋 {{ lockedTask.records.length }} 条干活记录
<view v-if="t.parent_task_id && parentTaskOf(t)" class="fc-link fc-up"> </view>
<text class="fc-link-label"> 上一步</text> <view class="fc-time">{{ formatTaskTime(lockedTask) }}</view>
<text class="fc-link-name">{{ parentTaskOf(t).task_name }}</text> </view>
<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 class="footer-actions">
<view v-if="t.records && t.records.length" class="fc-records-bar" @tap="handleViewRecords(t)"> <button v-if="lockedTask.status === 'WIP'" class="footer-btn footer-transfer"
📋 {{ t.records.length }} 条记录 @tap="handleTaskAction({ task: lockedTask, type: 'transfer' })">🔄 完工转交</button>
</view> <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"
<view class="fc-time">{{ formatTaskTime(t) }}</view> @tap="handleTaskAction({ task: lockedTask, type: 'end' })">🏁 结束分支</button>
</view> <button v-if="lockedTask.status === 'PENDING'" class="footer-btn footer-receive"
</swiper-item> @tap="handleTaskAction({ task: lockedTask, type: 'receive' })"> 接收任务</button>
</swiper> <button v-if="lockedTask.status === 'PENDING'" class="footer-btn footer-reject"
@tap="handleTaskAction({ task: lockedTask, type: 'reject' })"> 驳回任务</button>
</view>
</template> </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> </view>
<!-- ================================================================ --> <!-- ================================================================ -->
@ -418,8 +414,8 @@ export default {
currentUser: null, currentUser: null,
currentUserId: "", currentUserId: "",
currentUsername: "", currentUsername: "",
swiperCurrent: 0,
currentMode: "tree", currentMode: "tree",
lockedTaskId: null,
// Picker 数据源 // Picker 数据源
processOptions: [], processOptions: [],
userOptions: [], userOptions: [],
@ -473,8 +469,8 @@ export default {
if (this.product) walk(this.product.task_tree); if (this.product) walk(this.product.task_tree);
return result; return result;
}, },
currentTask() { lockedTask() {
return this.focusTasks[this.swiperCurrent] || null; return this.focusTasks.find(t => t.id === this.lockedTaskId) || null;
}, },
hasMyActiveTask() { hasMyActiveTask() {
return this.focusTasks.length > 0; return this.focusTasks.length > 0;
@ -508,14 +504,17 @@ export default {
// 智能分流:有本人活跃任务 → workspace否则 → tree // 智能分流:有本人活跃任务 → workspace否则 → tree
this.$nextTick(() => { this.$nextTick(() => {
this.currentMode = this.hasMyActiveTask ? 'workspace' : 'tree'; this.currentMode = this.hasMyActiveTask ? 'workspace' : 'tree';
this.swiperCurrent = 0; this.lockedTaskId = null;
}); });
} catch (e) { this.error = e?.data?.detail || "查询失败"; } } catch (e) { this.error = e?.data?.detail || "查询失败"; }
finally { this.loading = false; } finally { this.loading = false; }
}, },
toggleMode() { toggleMode() {
this.currentMode = this.currentMode === 'workspace' ? 'tree' : 'workspace'; 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); this.doQuery(this.product.serial_number);
} catch {} } catch {}
}, },
onSwiperChange(e) {
this.swiperCurrent = e.detail.current;
},
parentTaskOf(t) { parentTaskOf(t) {
if (!t || !t.parent_task_id) return null; if (!t || !t.parent_task_id) return null;
return this.taskMap[t.parent_task_id] || 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; } .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; } .workspace-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; } .list-header { display: flex; align-items: baseline; justify-content: space-between; padding: 8px 4px; flex-shrink: 0; }
.swipe-info { font-size: 12px; font-weight: 700; color: #2563eb; } .list-title { font-size: 14px; font-weight: 700; color: #1f2937; }
.swipe-hint { font-size: 11px; color: #9ca3af; } .list-hint { font-size: 11px; color: #9ca3af; }
.task-swiper { flex: 1; min-height: 0; } .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; } .task-count { font-size: 12px; color: #9ca3af; display: block; margin-top: 4px; }
.focus-card { .focus-card {