统一业务列表 Ant Design 风格(05-31-unify-business-list-ant-design)
这个提交包含在:
@@ -14,7 +14,12 @@ import { useAuth } from "../auth/AuthContext";
|
||||
import { hasAnyPermission } from "../auth/permissions";
|
||||
import { DateTextInput } from "../components/common/DateTextInput";
|
||||
import { StatusBadge } from "../components/common/StatusBadge";
|
||||
import { FilterBar, ListWorkspace, WorkspaceHeader, WorkspaceNotice } from "../components/patterns/ListWorkspace";
|
||||
import { ListWorkspace, WorkspaceHeader, WorkspaceNotice } from "../components/patterns/ListWorkspace";
|
||||
import {
|
||||
BusinessListToolbar,
|
||||
BusinessMobileList,
|
||||
BusinessTable,
|
||||
} from "../components/patterns/BusinessListTemplate";
|
||||
import { SectionPanel } from "../components/patterns/SectionPanel";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Input } from "../components/ui/input";
|
||||
@@ -255,8 +260,15 @@ export function ProjectsPage() {
|
||||
</SectionPanel>
|
||||
) : null}
|
||||
|
||||
<div className="space-y-6">
|
||||
<FilterBar className="grid gap-3 md:grid-cols-[minmax(0,1fr),180px,160px]">
|
||||
<div className="space-y-4">
|
||||
<BusinessListToolbar
|
||||
actions={
|
||||
<Button type="button" size="sm" variant={mine ? "default" : "secondary"} onClick={() => updateSearch({ mine: !mine })}>
|
||||
{mine ? "我相关" : "全范围"}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="grid gap-3 md:grid-cols-[minmax(0,1fr),180px]">
|
||||
<Input placeholder="搜索项目编号、项目名、客户名" value={q} onChange={(event) => updateSearch({ q: event.target.value })} />
|
||||
<select className="select-std" value={status} onChange={(event) => updateSearch({ status: event.target.value })}>
|
||||
{STATUS_OPTIONS.map((item) => (
|
||||
@@ -265,10 +277,8 @@ export function ProjectsPage() {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Button type="button" variant={mine ? "default" : "secondary"} onClick={() => updateSearch({ mine: !mine })}>
|
||||
{mine ? "我相关" : "全范围"}
|
||||
</Button>
|
||||
</FilterBar>
|
||||
</div>
|
||||
</BusinessListToolbar>
|
||||
|
||||
{!showTrialData && hiddenTrialProjectCount > 0 ? (
|
||||
<WorkspaceNotice title={`已隐藏 ${hiddenTrialProjectCount} 条 TEST/SMOKE 项目`} tone="neutral">
|
||||
@@ -276,18 +286,16 @@ export function ProjectsPage() {
|
||||
</WorkspaceNotice>
|
||||
) : null}
|
||||
|
||||
<SectionPanel title="项目列表">
|
||||
<div className="space-y-4">
|
||||
{listQuery.isLoading ? <div className="text-sm text-zinc-500">项目加载中...</div> : null}
|
||||
{listQuery.isError ? <div className="text-sm text-red-600">{resolveError(listQuery.error, "加载项目失败")}</div> : null}
|
||||
|
||||
<div className="grid gap-4">
|
||||
<BusinessMobileList>
|
||||
{visibleProjectItems.map((item) => (
|
||||
<Link key={item.id} to={`/projects/${item.id}`} className="card-std block space-y-3 p-5 hover:border-primary/40">
|
||||
<Link key={item.id} to={`/projects/${item.id}`} className="block rounded-lg border border-zinc-200 bg-white p-3 text-sm">
|
||||
<div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-lg font-semibold text-zinc-900">{item.projectNo}</div>
|
||||
<div className="font-semibold text-zinc-900">{item.projectNo}</div>
|
||||
<StatusBadge status={item.status} />
|
||||
{item.isConfidential ? <span className="rounded-full bg-amber-100 px-2 py-1 text-xs text-amber-700">涉密</span> : null}
|
||||
</div>
|
||||
@@ -305,13 +313,61 @@ export function ProjectsPage() {
|
||||
</Link>
|
||||
))}
|
||||
{!listQuery.isLoading && !visibleProjectItems.length ? (
|
||||
<div className="rounded-xl border border-dashed border-zinc-200 bg-zinc-50 px-4 py-8 text-sm text-zinc-500">
|
||||
当前没有符合筛选条件的项目。
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-200 bg-white p-4 text-sm text-zinc-500">暂无项目</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</SectionPanel>
|
||||
</BusinessMobileList>
|
||||
|
||||
<BusinessTable testId="projects-table">
|
||||
<thead className="table-header">
|
||||
<tr>
|
||||
<th className="table-th">项目</th>
|
||||
<th className="table-th">客户</th>
|
||||
<th className="table-th">负责人</th>
|
||||
<th className="table-th">进度</th>
|
||||
<th className="table-th">状态</th>
|
||||
<th className="table-th w-28">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-zinc-50">
|
||||
{visibleProjectItems.length ? (
|
||||
visibleProjectItems.map((item) => (
|
||||
<tr key={item.id} className="table-row">
|
||||
<td className="table-td">
|
||||
<div className="font-semibold text-zinc-900">{item.projectNo}</div>
|
||||
<div className="text-xs text-zinc-500">{item.name}</div>
|
||||
{item.isConfidential ? <div className="text-xs text-amber-700">涉密</div> : null}
|
||||
</td>
|
||||
<td className="table-td">
|
||||
<div className="font-medium text-zinc-900">{item.clientAlias || item.clientName}</div>
|
||||
<div className="line-clamp-2 text-xs text-zinc-500">{item.summary || "-"}</div>
|
||||
</td>
|
||||
<td className="table-td">
|
||||
<div className="font-medium text-zinc-900">{item.owner?.name ?? "-"}</div>
|
||||
<div className="text-xs text-zinc-500">项目经理 {item.manager?.name ?? "-"}</div>
|
||||
</td>
|
||||
<td className="table-td">
|
||||
<div className="text-xs text-zinc-500">{summaryLine(item)}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">目标 {formatDisplayDate(item.targetEndAt)}</div>
|
||||
</td>
|
||||
<td className="table-td">
|
||||
<StatusBadge status={item.status} />
|
||||
</td>
|
||||
<td className="table-td">
|
||||
<Button asChild type="button" size="sm" variant="secondary">
|
||||
<Link to={`/projects/${item.id}`}>查看详情</Link>
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td className="py-8 text-center text-sm text-zinc-500" colSpan={6}>
|
||||
暂无项目
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</BusinessTable>
|
||||
</div>
|
||||
</ListWorkspace>
|
||||
);
|
||||
|
||||
在新工单中引用
屏蔽一个用户