R97 B1-B5 精修高频页面操作密度
这个提交包含在:
@@ -771,66 +771,73 @@ export function MobileApprovalsPage() {
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<Tabs value={entityFilter} onValueChange={(value) => setEntityFilter(value as EntityFilter)}>
|
||||
<TabsList className="flex w-full flex-wrap justify-start gap-1">
|
||||
{ENTITY_FILTER_OPTIONS.map((option) => (
|
||||
<TabsTrigger key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Input
|
||||
data-testid="mobile-approvals-search"
|
||||
value={searchInput}
|
||||
onChange={(event) => setSearchInput(event.target.value)}
|
||||
placeholder="搜索步骤名、业务类型、任务 ID 或业务实体 ID"
|
||||
placeholder="搜索待办"
|
||||
/>
|
||||
|
||||
{taskTab === "pending" ? (
|
||||
<div className="grid gap-2 sm:flex sm:flex-wrap sm:items-center">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant={batchMode ? "default" : "secondary"}
|
||||
className="min-h-11 w-full sm:w-auto"
|
||||
disabled={!filteredTaskCards.length}
|
||||
onClick={() => {
|
||||
setBatchMode((current) => !current);
|
||||
setSelectedBatchTaskIds([]);
|
||||
setBatchRejectComment("");
|
||||
}}
|
||||
>
|
||||
{batchMode ? "退出批量处理" : "批量处理"}
|
||||
</Button>
|
||||
{batchMode ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="min-h-11 w-full sm:w-auto"
|
||||
disabled={!filteredTaskCards.length}
|
||||
onClick={toggleSelectAllVisibleTasks}
|
||||
>
|
||||
{allVisibleSelected ? "清空选择" : "全选当前筛选结果"}
|
||||
</Button>
|
||||
) : null}
|
||||
<span className="text-sm text-zinc-500 sm:ml-1">
|
||||
当前结果 {filteredTaskCards.length} 条
|
||||
{batchMode ? `,已选 ${selectedBatchTaskIds.length} 条` : ""}
|
||||
</span>
|
||||
<details className="rounded-xl border border-zinc-200 bg-zinc-50/70 px-3 py-2 text-sm">
|
||||
<summary className="cursor-pointer select-none font-medium text-zinc-700">
|
||||
筛选与批量
|
||||
</summary>
|
||||
<div className="mt-3 space-y-3">
|
||||
<Tabs value={entityFilter} onValueChange={(value) => setEntityFilter(value as EntityFilter)}>
|
||||
<TabsList className="flex w-full flex-wrap justify-start gap-1">
|
||||
{ENTITY_FILTER_OPTIONS.map((option) => (
|
||||
<TabsTrigger key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
{taskTab === "pending" ? (
|
||||
<div className="grid gap-2 sm:flex sm:flex-wrap sm:items-center">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant={batchMode ? "default" : "secondary"}
|
||||
className="min-h-11 w-full sm:w-auto"
|
||||
disabled={!filteredTaskCards.length}
|
||||
onClick={() => {
|
||||
setBatchMode((current) => !current);
|
||||
setSelectedBatchTaskIds([]);
|
||||
setBatchRejectComment("");
|
||||
}}
|
||||
>
|
||||
{batchMode ? "退出批量" : "批量"}
|
||||
</Button>
|
||||
{batchMode ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="min-h-11 w-full sm:w-auto"
|
||||
disabled={!filteredTaskCards.length}
|
||||
onClick={toggleSelectAllVisibleTasks}
|
||||
>
|
||||
{allVisibleSelected ? "清空选择" : "全选当前"}
|
||||
</Button>
|
||||
) : null}
|
||||
<span className="text-sm text-zinc-500 sm:ml-1">
|
||||
当前 {filteredTaskCards.length} 条
|
||||
{batchMode ? `,已选 ${selectedBatchTaskIds.length}` : ""}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-zinc-500">仅显示最近 30 天。</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-zinc-500">已处理页仅展示最近 30 天内的 completed 任务。</p>
|
||||
)}
|
||||
</details>
|
||||
</div>
|
||||
|
||||
{batchMode ? (
|
||||
<div className="rounded-2xl border border-zinc-200 bg-zinc-50 p-3 text-sm">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="text-zinc-700">已选择 {selectedBatchTaskIds.length} 条待办,可在线串行处理或离线逐条入队。</div>
|
||||
<div className="text-zinc-700">已选 {selectedBatchTaskIds.length} 条</div>
|
||||
<div className="grid w-full gap-2 sm:flex sm:w-auto sm:flex-wrap">
|
||||
<Button
|
||||
type="button"
|
||||
@@ -839,7 +846,7 @@ export function MobileApprovalsPage() {
|
||||
disabled={!selectedBatchTaskIds.length || batchActionMutation.isPending}
|
||||
onClick={() => handleBatchAction("approve")}
|
||||
>
|
||||
{canUseRemote ? "批量通过" : "离线批量加入通过草稿"}
|
||||
{canUseRemote ? "批量通过" : "加入通过草稿"}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -849,7 +856,7 @@ export function MobileApprovalsPage() {
|
||||
disabled={!selectedBatchTaskIds.length || batchActionMutation.isPending || !batchRejectComment.trim()}
|
||||
onClick={() => handleBatchAction("reject")}
|
||||
>
|
||||
{canUseRemote ? "批量驳回" : "离线批量加入驳回草稿"}
|
||||
{canUseRemote ? "批量驳回" : "加入驳回草稿"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -920,7 +927,7 @@ export function MobileApprovalsPage() {
|
||||
<div className="font-medium text-zinc-900">{task.title}</div>
|
||||
<StatusBadge status={task.status} label={workflowStatusLabel(task.status)} />
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">任务:{task.id}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">任务:{task.id}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">{task.subtitle}</div>
|
||||
{task.isCached ? <div className="mt-1 text-xs text-zinc-500">来源:离线缓存</div> : null}
|
||||
</button>
|
||||
@@ -955,7 +962,7 @@ export function MobileApprovalsPage() {
|
||||
</section>
|
||||
|
||||
<section ref={detailSectionRef} className="card-std scroll-mt-4 space-y-3 p-4" data-testid="mobile-approvals-detail">
|
||||
<h3 className="text-base font-semibold text-zinc-900">详情与处理</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">处理</h3>
|
||||
|
||||
{!selectedTaskId ? <p className="text-sm text-zinc-500">请先选择任务。</p> : null}
|
||||
|
||||
@@ -1025,13 +1032,13 @@ export function MobileApprovalsPage() {
|
||||
<Textarea
|
||||
data-testid="mobile-approvals-comment"
|
||||
onChange={(event) => setComment(event.target.value)}
|
||||
placeholder="驳回 / 退回意见;离线仅支持驳回意见入队。"
|
||||
placeholder="意见(驳回/退回必填)"
|
||||
rows={3}
|
||||
value={comment}
|
||||
/>
|
||||
|
||||
{!canUseRemote ? (
|
||||
<p className="text-xs text-zinc-500">离线状态下仅支持通过 / 驳回草稿,退回与转审需联网执行。</p>
|
||||
<p className="text-xs text-zinc-500">离线仅支持通过 / 驳回草稿。</p>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-2 sm:grid-cols-3">
|
||||
@@ -1123,8 +1130,8 @@ export function MobileApprovalsPage() {
|
||||
|
||||
<section className="card-std space-y-3 p-4" data-testid="mobile-approvals-queue">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<h3 className="text-base font-semibold text-zinc-900">离线审批草稿队列</h3>
|
||||
<span className="text-sm text-zinc-500">{currentQueueItems.length ? `待处理 ${currentQueueItems.length} 项` : "无待处理项"}</span>
|
||||
<h3 className="text-base font-semibold text-zinc-900">离线草稿</h3>
|
||||
<span className="text-sm text-zinc-500">{currentQueueItems.length ? `待同步 ${currentQueueItems.length}` : "无草稿"}</span>
|
||||
</div>
|
||||
|
||||
{currentQueueItems.length ? (
|
||||
@@ -1137,12 +1144,12 @@ export function MobileApprovalsPage() {
|
||||
type="button"
|
||||
variant="secondary"
|
||||
>
|
||||
{syncMutation.isPending ? "同步中..." : `同步草稿队列 (${retryableQueueItems.length})`}
|
||||
{syncMutation.isPending ? "同步中..." : `同步草稿 (${retryableQueueItems.length})`}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!currentQueueItems.length ? <p className="text-sm text-zinc-500">暂无待同步审批草稿。</p> : null}
|
||||
{!currentQueueItems.length ? <p className="text-sm text-zinc-500">暂无待同步草稿。</p> : null}
|
||||
|
||||
{currentQueueItems.map((item) => (
|
||||
<div key={item.id} className="rounded-lg border border-zinc-200 px-3 py-3 text-sm">
|
||||
|
||||
在新工单中引用
屏蔽一个用户