From e2d9590c48eed75bbf61596fd71f264ca234c64a Mon Sep 17 00:00:00 2001 From: duxingchen Date: Wed, 5 Aug 2026 16:35:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dremark=E4=B8=A2=E5=A4=B1(=5Ft?= =?UTF-8?q?o=5Fresponse=E6=BC=8F=E5=AD=97=E6=AE=B5)=20+=20UI=E9=87=8D?= =?UTF-8?q?=E6=9E=84swiper=E4=B8=93=E6=B3=A8=E5=8D=A1=E7=89=87=E7=9C=8B?= =?UTF-8?q?=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/task_service.py | 1 + track-uniapp/src/pages/scan/detail.vue | 226 +++++++++++++++++-------- 2 files changed, 161 insertions(+), 66 deletions(-) diff --git a/backend/app/services/task_service.py b/backend/app/services/task_service.py index f8c7304..55fdef8 100644 --- a/backend/app/services/task_service.py +++ b/backend/app/services/task_service.py @@ -104,6 +104,7 @@ def _to_response(task: Task) -> TaskResponse: status=task.status, notify_parent_on_complete=task.notify_parent_on_complete, is_rework=task.is_rework, + remark=task.remark, reject_reason=task.reject_reason, received_at=task.received_at, completed_at=task.completed_at, diff --git a/track-uniapp/src/pages/scan/detail.vue b/track-uniapp/src/pages/scan/detail.vue index 1eb99e9..f0afb19 100644 --- a/track-uniapp/src/pages/scan/detail.vue +++ b/track-uniapp/src/pages/scan/detail.vue @@ -54,57 +54,98 @@ - + - - - - 🌿 任务流转树 - {{ countTasks(product.task_tree) }} 个任务 - - - - - 📋 - 该产品暂无流转任务 - - - - - + + + + 📋 + 该产品暂无流转任务 + + + + + 🔒 + 当前没有需要你处理的任务 + 共 {{ countTasks(product.task_tree) }} 个任务 + + + + - - - - - @@ -351,6 +392,7 @@ export default { currentUser: null, currentUserId: "", currentUsername: "", + swiperCurrent: 0, // Picker 数据源 processOptions: [], userOptions: [], @@ -378,38 +420,34 @@ export default { return !!b.assignee_id; }); }, - activeTask() { - // 查找树中第一个 WIP 或 PENDING 任务(不限归属) - const findActive = (tasks) => { - if (!tasks) return null; - for (const t of tasks) { - if (t.status === 'WIP' || t.status === 'PENDING') return t; - if (t.child_tasks && t.child_tasks.length) { - const found = findActive(t.child_tasks); - if (found) return found; - } - } - return null; + // 构建 parent_task_id → task 的索引 + taskMap() { + const m = {}; + const walk = (tasks) => { + if (!tasks) return; + for (const t of tasks) { m[t.id] = t; walk(t.child_tasks); } }; - return this.product ? findActive(this.product.task_tree) : null; + if (this.product) walk(this.product.task_tree); + return m; }, - myActiveTask() { - // 只返回当前登录人自己的 WIP/PENDING 任务 - const findMine = (tasks) => { - if (!tasks) return null; + // 只返回当前用户需要处理的活跃任务 (PENDING/WIP) + focusTasks() { + const result = []; + const walk = (tasks) => { + if (!tasks) return; for (const t of tasks) { const isMine = t.status === 'WIP' || t.status === 'PENDING'; const matchId = t.assignee_id == this.currentUserId; const matchName = t.assignee_id == this.currentUsername; - if (isMine && (matchId || matchName)) return t; - if (t.child_tasks && t.child_tasks.length) { - const found = findMine(t.child_tasks); - if (found) return found; - } + if (isMine && (matchId || matchName)) result.push(t); + walk(t.child_tasks); } - return null; }; - return this.product ? findMine(this.product.task_tree) : null; + if (this.product) walk(this.product.task_tree); + return result; + }, + currentTask() { + return this.focusTasks[this.swiperCurrent] || null; }, }, onLoad(options) { @@ -709,6 +747,26 @@ export default { handleViewRecords(task) { uni.navigateTo({ url: `/pages/scan/records?taskId=${task.id}` }); }, + 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; + }, + formatTaskTime(t) { + if (!t) return ""; + const parts = []; + if (t.created_at) parts.push("创建: " + this.formatTime(t.created_at)); + if (t.received_at) parts.push("接收: " + this.formatTime(t.received_at)); + return parts.join(" | "); + }, + formatTime(d) { + if (!d) return ""; + const dt = new Date(d); + const pad = (n) => String(n).padStart(2, "0"); + return `${dt.getFullYear()}-${pad(dt.getMonth()+1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}`; + }, closeActionPopup() { this.actionPopup = { visible: false, type: "", task: null }; }, async doReceive() { @@ -795,10 +853,46 @@ 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; } -/* 任务树滚动区域 → 撑满剩余空间 */ -.tree-scroll-area { flex: 1; overflow-y: auto; min-height: 0; -webkit-overflow-scrolling: touch; } -.card-tree { margin-bottom: 0; } -.card-tree .card-header { 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; } +.task-count { font-size: 12px; color: #9ca3af; display: block; margin-top: 4px; } + +.focus-card { + height: 100%; margin: 0 4px; padding: 20px 16px; border-radius: 16px; + background: #fff; box-shadow: 0 2px 12px rgba(0,0,0,0.08); overflow-y: auto; + border-top: 5px solid #3b82f6; +} +.focus-card.s-yellow { border-top-color: #f59e0b; } +.focus-card.s-blue { border-top-color: #3b82f6; } +.focus-card.s-green { border-top-color: #22c55e; } +.focus-card.s-red { border-top-color: #ef4444; } + +.fc-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; } +.fc-status { font-size: 12px; padding: 2px 10px; border-radius: 12px; font-weight: 700; background: #f3f4f6; color: #6b7280; } +.s-yellow .fc-status { background: #fef3c7; color: #b45309; } +.s-blue .fc-status { background: #dbeafe; color: #1d4ed8; } +.tag-rework-sm { font-size: 10px; padding: 2px 6px; border-radius: 8px; background: #ef4444; color: #fff; } + +.fc-name { font-size: 22px; font-weight: 800; color: #1f2937; display: block; margin-bottom: 10px; } + +.fc-remark { display: flex; gap: 6px; padding: 10px 12px; background: #fefce8; border-radius: 10px; margin-bottom: 10px; border-left: 4px solid #eab308; } +.fc-remark-label { font-size: 14px; flex-shrink: 0; } +.fc-remark-text { font-size: 14px; color: #713f12; line-height: 1.5; word-break: break-all; } + +.fc-chain { margin-bottom: 8px; } +.fc-link { padding: 6px 10px; border-radius: 8px; margin-bottom: 4px; font-size: 12px; } +.fc-up { background: #f0fdf4; color: #16a34a; } +.fc-down { background: #eff6ff; color: #2563eb; } +.fc-link-label { font-weight: 700; display: block; } +.fc-link-name { display: block; margin-top: 2px; } +.fc-link-who { color: #9ca3af; font-size: 11px; } + +.fc-records-bar { padding: 8px 12px; background: linear-gradient(135deg,#eff6ff,#dbeafe); border-radius: 8px; font-size: 13px; font-weight: 700; color: #2563eb; margin-bottom: 8px; } +.fc-time { font-size: 11px; color: #9ca3af; margin-top: auto; padding-top: 8px; border-top: 1px solid #f3f4f6; } .card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; } .card-header-right { display: flex; align-items: center; gap: 8px; } .card-title { font-size: 15px; font-weight: 700; }