From 4dd94cde27dbc265b4eb8494da51b90c7749e94c Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 16 Aug 2026 13:52:45 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=89=93=E6=89=8B=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E4=B8=8E=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/repositories/worker-platform/types.ts | 1 + .../worker-platform/work-order-repo.ts | 24 ++- .../worker-platform/worker-service.ts | 1 + .../src/pages/worker/WorkerOrdersPage.tsx | 116 ++++++++++++-- apps/frontend/src/styles/worker.css | 149 ++++++++++++++++-- 5 files changed, 266 insertions(+), 25 deletions(-) diff --git a/apps/backend/src/repositories/worker-platform/types.ts b/apps/backend/src/repositories/worker-platform/types.ts index b667d9ba..bea91be1 100644 --- a/apps/backend/src/repositories/worker-platform/types.ts +++ b/apps/backend/src/repositories/worker-platform/types.ts @@ -215,6 +215,7 @@ export type ListInput = { categoryId?: number workerSharingId?: number visibleAfterIso?: string + sort?: 'id_desc' | 'worker_claimed_at_desc' } export type ProductRuleListInput = { diff --git a/apps/backend/src/repositories/worker-platform/work-order-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-repo.ts index 792063e0..76ab4c81 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-repo.ts @@ -743,6 +743,7 @@ export async function listWorkOrders({ categoryId = 0, workerSharingId = 0, visibleAfterIso = '', + sort = 'id_desc', }: ListInput = {}): Promise<{ items: WorkOrderRow[]; total: number }> { const { whereClause, params } = buildWorkOrderWhere({ status, @@ -761,11 +762,32 @@ export async function listWorkOrders({ params, ) const offset = (page - 1) * pageSize + const orderBy = + sort === 'worker_claimed_at_desc' && workerSharingId + ? (() => { + params.push(workerSharingId) + const workerIdPlaceholder = `$${params.length}` + return `COALESCE( + CASE + WHEN wo.assigned_worker_id = ${workerIdPlaceholder} THEN wo.assigned_at + END, + ( + SELECT wos.created_at + FROM work_order_shares wos + WHERE wos.work_order_id = wo.id + AND wos.worker_id = ${workerIdPlaceholder} + AND wos.status != 'cancelled' + ORDER BY wos.created_at DESC + LIMIT 1 + ) + ) DESC NULLS LAST, wo.id DESC` + })() + : 'wo.id DESC' params.push(pageSize, offset) const itemsResult = await query( `${WORK_ORDER_SELECT} ${whereClause} - ORDER BY wo.id DESC + ORDER BY ${orderBy} LIMIT $${params.length - 1} OFFSET $${params.length}`, params, ) diff --git a/apps/backend/src/services/worker-platform/worker-service.ts b/apps/backend/src/services/worker-platform/worker-service.ts index a852a9ad..723aa67f 100644 --- a/apps/backend/src/services/worker-platform/worker-service.ts +++ b/apps/backend/src/services/worker-platform/worker-service.ts @@ -994,6 +994,7 @@ export async function listWorkerMyOrders(query: JsonObject = {}, session: Worker status: String(query.status || '').trim(), keyword: String(query.keyword || '').trim(), workerSharingId: worker.id, + sort: 'worker_claimed_at_desc', }) const permissions = resolveWorkerPermissions(worker) const myShares = await listWorkerSharesByWorker(worker.id) diff --git a/apps/frontend/src/pages/worker/WorkerOrdersPage.tsx b/apps/frontend/src/pages/worker/WorkerOrdersPage.tsx index 1003ba10..a38e6df6 100644 --- a/apps/frontend/src/pages/worker/WorkerOrdersPage.tsx +++ b/apps/frontend/src/pages/worker/WorkerOrdersPage.tsx @@ -51,13 +51,13 @@ import { getImageIdentity } from '@/utils/image-identity' import { useIsMobile } from '@/utils/use-is-mobile' const STATUS_OPTIONS = [ - { value: '', label: '全部订单' }, { value: 'in_progress', label: '代练中' }, { value: 'pending_acceptance', label: '待验收' }, { value: 'problem', label: '问题单' }, { value: 'open', label: '已超时' }, { value: 'accepted', label: '已验收' }, { value: 'cancelled', label: '已取消' }, + { value: '', label: '全部订单' }, ] as const type AcceptanceFormValues = { @@ -68,6 +68,97 @@ type WorkerNoteFormValues = { note?: string } +type NoteTheme = 'success' | 'processing' | 'danger' | 'warning' | 'purple' | 'default' + +const PRESET_NOTE_TAGS: Array<{ label: string; theme: NoteTheme }> = [ + { label: '已安排', theme: 'success' }, + { label: '打单中', theme: 'processing' }, + { label: '账号问题', theme: 'danger' }, + { label: '排队中', theme: 'purple' }, + { label: '未安排', theme: 'warning' }, + { label: '暂缓做单', theme: 'warning' }, +] + +function resolveNoteTheme(note?: string): NoteTheme { + const text = String(note || '').trim().toLowerCase() + if (!text) return 'default' + + // 1. 危险 / 异常 / 问题类 (Red) + if ( + text.includes('问题') || + text.includes('异常') || + text.includes('密码') || + text.includes('封号') || + text.includes('错误') || + text.includes('无法') || + text.includes('投诉') || + text.includes('退') || + text.includes('失败') || + text.includes('被挤') + ) { + return 'danger' + } + + // 2. 暂缓 / 未安排 / 延后 / 暂停类 (Orange) + if ( + text.includes('未安排') || + text.includes('未') || + text.includes('暂缓') || + text.includes('暂停') || + text.includes('稍后') || + text.includes('延后') || + text.includes('挂起') || + text.includes('待定') + ) { + return 'warning' + } + + // 3. 排队 / 等待类 (Purple) + if ( + text.includes('排队') || + text.includes('等待') || + text.includes('等买家') || + text.includes('等扫码') || + text.includes('等验证') || + text.includes('待扫') + ) { + return 'purple' + } + + // 4. 进行中 / 打单中 / 上号 (Blue) + if ( + text.includes('打单') || + text.includes('做单') || + text.includes('进行') || + text.includes('处理') || + text.includes('上号') || + text.includes('练级') || + text.includes('正在') || + text.includes('开打') || + text.includes('肝') + ) { + return 'processing' + } + + // 5. 已安排 / 已就绪 / 完成 / 正常 (Green) + if ( + text.includes('已安排') || + text.includes('安排') || + text.includes('已就绪') || + text.includes('就绪') || + text.includes('已联系') || + text.includes('完成') || + text.includes('已好') || + text.includes('搞定') || + text.includes('正常') || + text.includes('ok') + ) { + return 'success' + } + + return 'default' +} + type DetailFieldItem = { key: string label: string @@ -79,7 +170,7 @@ export default function WorkerOrdersPage() { const isMobile = useIsMobile() const { message } = App.useApp() const queryClient = useQueryClient() - const [status, setStatus] = useState('') + const [status, setStatus] = useState('in_progress') const [keywordInput, setKeywordInput] = useState('') const [keyword, setKeyword] = useState('') const [page, setPage] = useState(1) @@ -401,10 +492,14 @@ export default function WorkerOrdersPage() { width: 180, render: (_, row) => { const note = String(row.workerNote || '').trim() + const theme = resolveNoteTheme(note) return (
{note ? ( -
openNoteModal(row)}> +
openNoteModal(row)} + > {note} @@ -690,7 +785,10 @@ export default function WorkerOrdersPage() {
{workerNote ? ( -
openNoteModal(order)}> +
openNoteModal(order)} + > {workerNote} @@ -1012,13 +1110,13 @@ export default function WorkerOrdersPage() {
- {['已安排', '未安排', '打单中', '账号问题', '排队中', '暂缓做单'].map((tag) => ( + {PRESET_NOTE_TAGS.map((item) => ( noteForm.setFieldsValue({ note: tag })} + key={item.label} + className={`worker-note-preset-tag theme-${item.theme}`} + onClick={() => noteForm.setFieldsValue({ note: item.label })} > - {tag} + {item.label} ))}
diff --git a/apps/frontend/src/styles/worker.css b/apps/frontend/src/styles/worker.css index e345155f..ad05063a 100644 --- a/apps/frontend/src/styles/worker.css +++ b/apps/frontend/src/styles/worker.css @@ -13,26 +13,25 @@ } .worker-page-head { - display: grid; - grid-template-areas: - 'main' - 'search'; - grid-template-columns: minmax(0, 1fr); + display: flex; align-items: center; + justify-content: space-between; gap: 12px 16px; + flex-wrap: wrap; } -.worker-page-head h3 { - margin: 0; +.worker-page-head h3, +.worker-page-head > .ant-typography { + margin: 0 !important; flex: 0 0 auto; white-space: nowrap; } .worker-page-head-title-row { - grid-area: main; display: flex; align-items: center; justify-content: space-between; + width: 100%; min-width: 0; gap: 16px; } @@ -223,6 +222,7 @@ cursor: pointer; transition: all 0.2s ease; font-size: 12px; + font-weight: 500; color: #24292f; } @@ -232,11 +232,96 @@ color: #0958d9; } +/* 绿色主题:已安排 / 已就绪 / 完成 / 正常 */ +.worker-note-badge.theme-success { + background: #f6ffed; + border-color: #b7eb8f; + color: #237804; +} + +.worker-note-badge.theme-success .worker-note-edit-icon { + color: #52c41a; +} + +.worker-note-badge.theme-success:hover { + background: #d9f7be; + border-color: #73d13d; + color: #135200; +} + +/* 蓝色主题:打单中 / 处理中 / 进行中 / 上号中 */ +.worker-note-badge.theme-processing { + background: #e6f4ff; + border-color: #91caff; + color: #0958d9; +} + +.worker-note-badge.theme-processing .worker-note-edit-icon { + color: #1677ff; +} + +.worker-note-badge.theme-processing:hover { + background: #bae0ff; + border-color: #69b1ff; + color: #003eb3; +} + +/* 红色主题:账号问题 / 密码错误 / 异常 / 封号 */ +.worker-note-badge.theme-danger { + background: #fff1f0; + border-color: #ffccc7; + color: #cf1322; +} + +.worker-note-badge.theme-danger .worker-note-edit-icon { + color: #ff4d4f; +} + +.worker-note-badge.theme-danger:hover { + background: #ffccc7; + border-color: #ff7875; + color: #a8071a; +} + +/* 紫色主题:排队中 / 等待中 / 等买家扫码 */ +.worker-note-badge.theme-purple { + background: #f9f0ff; + border-color: #d3adf7; + color: #531dab; +} + +.worker-note-badge.theme-purple .worker-note-edit-icon { + color: #722ed1; +} + +.worker-note-badge.theme-purple:hover { + background: #efdbff; + border-color: #b37feb; + color: #391085; +} + +/* 橙色主题:未安排 / 暂缓做单 / 暂停 / 待定 */ +.worker-note-badge.theme-warning { + background: #fff7e6; + border-color: #ffd591; + color: #d46b08; +} + +.worker-note-badge.theme-warning .worker-note-edit-icon { + color: #fa8c16; +} + +.worker-note-badge.theme-warning:hover { + background: #ffe7ba; + border-color: #ffc069; + color: #ad4e00; +} + .worker-note-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - max-width: 110px; + max-width: 120px; } .worker-note-edit-icon { @@ -246,7 +331,7 @@ } .worker-note-badge:hover .worker-note-edit-icon { - color: #0958d9; + opacity: 0.85; } .worker-note-add-btn { @@ -264,15 +349,49 @@ .worker-note-preset-tag { cursor: pointer; user-select: none; - padding: 2px 10px; - border-radius: 4px; + padding: 4px 12px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + border: 1px solid #d0d7de; + background: #f6f8fa; + color: #24292f; transition: all 0.2s ease; } -.worker-note-preset-tag:hover { - border-color: #1677ff; - color: #1677ff; +.worker-note-preset-tag.theme-success { + background: #f6ffed; + border-color: #b7eb8f; + color: #237804; +} + +.worker-note-preset-tag.theme-processing { background: #e6f4ff; + border-color: #91caff; + color: #0958d9; +} + +.worker-note-preset-tag.theme-danger { + background: #fff1f0; + border-color: #ffccc7; + color: #cf1322; +} + +.worker-note-preset-tag.theme-purple { + background: #f9f0ff; + border-color: #d3adf7; + color: #531dab; +} + +.worker-note-preset-tag.theme-warning { + background: #fff7e6; + border-color: #ffd591; + color: #d46b08; +} + +.worker-note-preset-tag:hover { + transform: translateY(-1px); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); } /* 接单平台顶部统计条 (PC) */