重构移动端轻动作页面(05-30-mobile-light-action-surfaces)
这个提交包含在:
@@ -9,6 +9,7 @@ import {
|
||||
import { PageHeader } from "../components/common/PageHeader";
|
||||
import { StatusBadge } from "../components/common/StatusBadge";
|
||||
import { PageScaffold } from "../components/patterns/PageScaffold";
|
||||
import { Input } from "../components/ui/input";
|
||||
import { Tabs, TabsList, TabsTrigger } from "../components/ui/tabs";
|
||||
import { resolvePurchaseDisplayCode } from "../lib/approvalDisplay";
|
||||
import { joinBusinessMeta } from "../lib/businessDisplay";
|
||||
@@ -63,6 +64,29 @@ function resolveMobileEmptyState(status: PurchaseRequestStatus) {
|
||||
return "暂无已取消采购";
|
||||
}
|
||||
|
||||
function normalizeSearchValue(value: string | null | undefined) {
|
||||
return String(value ?? "").trim().toLowerCase();
|
||||
}
|
||||
|
||||
function matchesPurchaseSearch(item: PurchaseRequestSummary, keyword: string) {
|
||||
const searchValue = normalizeSearchValue(keyword);
|
||||
if (!searchValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const haystack = [
|
||||
resolvePurchaseDisplayCode(item),
|
||||
item.title,
|
||||
item.reason,
|
||||
item.totalAmount,
|
||||
item.applicant?.name,
|
||||
item.supplier?.name,
|
||||
item.project?.name,
|
||||
].map(normalizeSearchValue).join(" ");
|
||||
|
||||
return haystack.includes(searchValue);
|
||||
}
|
||||
|
||||
function PurchaseRequestCard({
|
||||
item,
|
||||
currentMobileSource,
|
||||
@@ -114,6 +138,7 @@ export function MobilePurchaseRequestsPage() {
|
||||
const queryStatus = searchParams.get("status");
|
||||
return isPurchaseStatus(queryStatus) ? queryStatus : "draft";
|
||||
});
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
|
||||
const currentMobileSource = useMemo(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
@@ -138,9 +163,13 @@ export function MobilePurchaseRequestsPage() {
|
||||
}
|
||||
|
||||
const items = listQuery.data?.items ?? [];
|
||||
const visibleItems = useMemo(
|
||||
() => items.filter((item) => matchesPurchaseSearch(item, searchInput)),
|
||||
[items, searchInput],
|
||||
);
|
||||
const totalAmount = useMemo(
|
||||
() => items.reduce((sum, item) => sum + Number(item.totalAmount ?? 0), 0),
|
||||
[items],
|
||||
() => visibleItems.reduce((sum, item) => sum + Number(item.totalAmount ?? 0), 0),
|
||||
[visibleItems],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -149,11 +178,11 @@ export function MobilePurchaseRequestsPage() {
|
||||
|
||||
<section className="mobile-card">
|
||||
<div className="text-sm font-semibold text-zinc-900">当前采购任务</div>
|
||||
<div className="mt-1 text-xs leading-5 text-zinc-500">手机端只看状态、材料摘要和下一步;台账筛选、全范围和导出留在电脑端。</div>
|
||||
<div className="mt-1 text-xs leading-5 text-zinc-500">先看材料是否齐,再进详情核对采购依据。</div>
|
||||
<div className="mobile-info-grid">
|
||||
<div>
|
||||
<div className="text-xs text-zinc-500">当前列表</div>
|
||||
<div className="mt-1 text-2xl font-semibold text-zinc-900">{listQuery.data?.total ?? 0}</div>
|
||||
<div className="mt-1 text-2xl font-semibold text-zinc-900">{visibleItems.length}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-zinc-500">当前金额</div>
|
||||
@@ -163,6 +192,14 @@ export function MobilePurchaseRequestsPage() {
|
||||
</section>
|
||||
|
||||
<section className="mobile-card" data-testid="mobile-purchase-requests-list">
|
||||
<div className="mobile-command-surface mb-4">
|
||||
<Input
|
||||
aria-label="搜索采购"
|
||||
value={searchInput}
|
||||
onChange={(event) => setSearchInput(event.target.value)}
|
||||
placeholder="搜索编号、标题、供应商"
|
||||
/>
|
||||
</div>
|
||||
<Tabs value={status} onValueChange={handleStatusChange}>
|
||||
<TabsList className="grid h-auto w-full grid-cols-3 gap-1 min-[390px]:grid-cols-5">
|
||||
{STATUS_TABS.map((item) => (
|
||||
@@ -180,10 +217,12 @@ export function MobilePurchaseRequestsPage() {
|
||||
{resolveErrorMessage(listQuery.error)}
|
||||
</div>
|
||||
) : null}
|
||||
{!listQuery.isLoading && !listQuery.isError && !items.length ? (
|
||||
<div className="mobile-empty-state">{resolveMobileEmptyState(status)}</div>
|
||||
{!listQuery.isLoading && !listQuery.isError && !visibleItems.length ? (
|
||||
<div className="mobile-empty-state">
|
||||
{searchInput.trim() ? "没有符合搜索的采购记录" : resolveMobileEmptyState(status)}
|
||||
</div>
|
||||
) : null}
|
||||
{items.map((item) => (
|
||||
{visibleItems.map((item) => (
|
||||
<PurchaseRequestCard currentMobileSource={currentMobileSource} item={item} key={item.id} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
在新工单中引用
屏蔽一个用户