From f8af731bc3fbab8978f3b1df17c1b5a572d2466f Mon Sep 17 00:00:00 2001 From: hailin Date: Thu, 2 Jul 2026 16:18:04 +0800 Subject: [PATCH] refactor: refine ticket center operations view --- .../src/features/tickets/TicketListPage.tsx | 277 ++++++++++-------- 1 file changed, 155 insertions(+), 122 deletions(-) diff --git a/frontend/src/features/tickets/TicketListPage.tsx b/frontend/src/features/tickets/TicketListPage.tsx index b205694..18e485e 100644 --- a/frontend/src/features/tickets/TicketListPage.tsx +++ b/frontend/src/features/tickets/TicketListPage.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { ArrowRight, Plus } from 'lucide-react' +import { ArrowRight, Clock3, Plus } from 'lucide-react' import { AppDataTable, type AppDataTableColumn } from '@/components/app/AppDataTable' import { AppEmptyState } from '@/components/app/AppEmptyState' @@ -7,7 +7,9 @@ import { AppPage } from '@/components/app/AppPage' import { AppPageHeader } from '@/components/app/AppPageHeader' import { AppPriorityBadge } from '@/components/app/AppPriorityBadge' import { AppStatusBadge } from '@/components/app/AppStatusBadge' +import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' +import { Card, CardContent } from '@/components/ui/card' import { Pagination, PaginationContent, @@ -18,6 +20,7 @@ import { } from '@/components/ui/pagination' import { Skeleton } from '@/components/ui/skeleton' import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { formatDateTime } from '@/lib/format' import { cn } from '@/lib/utils' import { listTickets } from './api' @@ -43,61 +46,77 @@ const viewTabs: Array<{ value: TicketListView; label: string }> = [ { value: 'high_priority', label: '高优先级' } ] +function displayUser(user: Ticket['assignee']) { + return user?.display_name || user?.username || '未分派' +} + +function getTicketAgeLabel(ticket: Ticket) { + const createdAt = new Date(ticket.created_at).getTime() + const diffHours = Math.max(0, Math.floor((Date.now() - createdAt) / 1000 / 60 / 60)) + if (diffHours < 1) return '1 小时内' + if (diffHours < 24) return `${diffHours} 小时` + return `${Math.floor(diffHours / 24)} 天` +} + const columns: AppDataTableColumn[] = [ { - key: 'ticket_no', - header: '编号', - className: 'w-[14%]', - cell: (ticket) => {ticket.ticket_no} - }, - { - key: 'title', - header: '标题', - className: 'w-[24%] whitespace-normal', + key: 'ticket', + header: '工单', + className: 'w-[34%] whitespace-normal', cell: (ticket) => (
-

{ticket.title}

-

- {ticket.requester?.display_name ? `请求人:${ticket.requester.display_name}` : ticket.environment || '暂无环境信息'} -

+
+ {ticket.ticket_no} + 请求人:{displayUser(ticket.requester)} +
+

{ticket.title}

+

{ticket.description || '暂无问题描述'}

) }, { - key: 'status', - header: '状态', - className: 'w-[8%]', - cell: (ticket) => + key: 'workflow', + header: '流程', + className: 'w-[16%]', + cell: (ticket) => ( +
+ + +
+ ) }, { - key: 'priority', - header: '优先级', - className: 'w-[7%]', - cell: (ticket) => - }, - { - key: 'related_system', - header: '关联系统', - className: 'w-[10%]', - cell: (ticket) => {ticket.related_system || '-'} - }, - { - key: 'impact_scope', - header: '影响范围', - className: 'w-[11%]', - cell: (ticket) => {ticket.impact_scope || '-'} + key: 'context', + header: '业务上下文', + className: 'w-[22%] whitespace-normal', + cell: (ticket) => ( +
+

{ticket.related_system || '未填写系统'}

+

{ticket.impact_scope || ticket.environment || '暂无影响范围'}

+
+ ) }, { key: 'assignee', - header: '处理人', - className: 'w-[8%]', - cell: (ticket) => {ticket.assignee?.display_name || '未分派'} + header: '责任人', + className: 'w-[12%]', + cell: (ticket) => ( +
+ {displayUser(ticket.assignee)} + {ticket.assignee ? '已分派' : '待分派'} +
+ ) }, { - key: 'created_at', - header: '创建时间', + key: 'updated_at', + header: '时间', className: 'w-[10%]', - cell: (ticket) => {formatTicketTableDate(ticket.created_at)} + cell: (ticket) => ( +
+ {formatTicketTableDate(ticket.updated_at)} + 已创建 {getTicketAgeLabel(ticket)} +
+ ) }, { key: 'actions', @@ -194,97 +213,111 @@ export function TicketListPage() { setPage(target) } + const hasActiveFilters = status !== 'all' || priority !== 'all' || relatedSystem.trim() || q.trim() + return ( - (window.location.hash = '/tickets/new')}> - - 新建工单 - - } - /> + 已筛选 : 全部范围} + actions={ + + } + /> - + - - - {viewTabs.map((tab) => ( - - {tab.label} - - ))} - - + + + + + {viewTabs.map((tab) => ( + + {tab.label} + + ))} + + +
+ + 最近更新:{result.items[0]?.updated_at ? formatDateTime(result.items[0].updated_at) : '暂无数据'} +
+
+
- + - {error ? : null} - {loading && !error ? ( -
+ {error ? : null} + {loading && !error ? ( + + -
- ) : null} - {!error && !loading ? ( - ticket.id} - emptyTitle="暂无工单" - emptyDescription="调整筛选条件,或创建第一张工单后再查看。" - footer={ -
-

- 共 {result.total} 条,当前第 {result.page} / {Math.max(result.total_pages, 1)} 页 -

- - - - { - event.preventDefault() - goToPage(result.page - 1) - }} - /> - - - event.preventDefault()}> - {result.page} - - - - = result.total_pages && 'pointer-events-none opacity-50')} - onClick={(event) => { - event.preventDefault() - goToPage(result.page + 1) - }} - /> - - - -
- } - tableClassName="table-fixed" - onRowClick={(ticket) => (window.location.hash = `/tickets/${ticket.id}`)} - /> - ) : null} + + + ) : null} + {!error && !loading ? ( + ticket.id} + emptyTitle="暂无工单" + emptyDescription="调整筛选条件,或创建第一张工单后再查看。" + footer={ +
+

+ 共 {result.total} 条,当前第 {result.page} / {Math.max(result.total_pages, 1)} 页 +

+ + + + { + event.preventDefault() + goToPage(result.page - 1) + }} + /> + + + event.preventDefault()}> + {result.page} + + + + = result.total_pages && 'pointer-events-none opacity-50')} + onClick={(event) => { + event.preventDefault() + goToPage(result.page + 1) + }} + /> + + + +
+ } + tableClassName="table-fixed min-w-[980px]" + onRowClick={(ticket) => (window.location.hash = `/tickets/${ticket.id}`)} + /> + ) : null}
) }