feat: rebuild review detail decision surface

This commit is contained in:
afei A
2026-03-28 00:22:08 +08:00
parent f2deb54f3a
commit edd03b03a7
6 changed files with 87 additions and 89 deletions

View File

@@ -51,14 +51,14 @@ export function ReviewActionPanel({
<div className="space-y-2">
<CardEyebrow>Review action</CardEyebrow>
<div className="space-y-1">
<CardTitle></CardTitle>
<CardTitle></CardTitle>
<CardDescription>
</CardDescription>
</div>
</div>
{order ? (
<div className="rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-4 py-4 text-sm text-[var(--ink-muted)]">
<div className="rounded-[var(--panel-radius)] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-3 py-3 text-sm text-[var(--ink-muted)]">
<p className="text-sm font-semibold text-[var(--ink-strong)]">
#{order.orderId}
</p>
@@ -74,20 +74,20 @@ export function ReviewActionPanel({
<textarea
value={comment}
onChange={(event) => setComment(event.target.value)}
rows={5}
rows={4}
placeholder="重跑或驳回时填写原因,便于流程追踪和复盘。"
className="min-h-[132px] w-full rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-4 py-3 text-sm leading-6 text-[var(--ink-strong)] outline-none transition placeholder:text-[var(--ink-faint)] focus:border-[var(--accent-primary)] focus:bg-[var(--surface)]"
className="min-h-[112px] w-full rounded-[var(--panel-radius)] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-3 py-2.5 text-sm leading-6 text-[var(--ink-strong)] outline-none transition placeholder:text-[var(--ink-faint)] focus:border-[var(--accent-primary)] focus:bg-[var(--surface)]"
/>
</label>
{submissionError ? (
<div className="rounded-[24px] border border-[#b88472] bg-[#f8ece5] px-5 py-4 text-sm text-[#7f4b3b]">
<div className="rounded-[var(--panel-radius)] border border-[#b88472] bg-[#f8ece5] px-4 py-3 text-sm text-[#7f4b3b]">
{submissionError}
</div>
) : null}
{submissionResult ? (
<div className="rounded-[24px] border border-[rgba(88,106,56,0.18)] bg-[rgba(110,127,82,0.12)] px-5 py-4 text-sm text-[#50633b]">
<div className="rounded-[var(--panel-radius)] border border-[rgba(88,106,56,0.18)] bg-[rgba(110,127,82,0.12)] px-4 py-3 text-sm text-[#50633b]">
<div className="flex flex-wrap items-center gap-3">
<span></span>
<StatusBadge variant="reviewDecision" status={submissionResult.decision} />
@@ -96,12 +96,12 @@ export function ReviewActionPanel({
</div>
) : null}
<div className="grid gap-3">
<div className="grid gap-2">
{ACTIONS.map((action) => (
<Button
key={action.decision}
variant={action.variant}
size="lg"
size="md"
disabled={!order || isSubmitting}
onClick={() => onSubmit(action.decision, comment)}
>

View File

@@ -108,8 +108,8 @@ export function ReviewImagePanel({
</CardHeader>
<CardContent className="space-y-6">
{selectedAsset ? (
<div className="rounded-[28px] border border-[var(--border-soft)] bg-[linear-gradient(180deg,rgba(250,247,242,0.96),rgba(238,231,221,0.92))] p-5">
<div className="flex min-h-[320px] items-center justify-center rounded-[22px] border border-dashed border-[var(--border-strong)] bg-[rgba(255,255,255,0.55)] p-6 text-center">
<div className="rounded-[var(--panel-radius)] border border-[var(--border-soft)] bg-[linear-gradient(180deg,rgba(250,247,242,0.96),rgba(238,231,221,0.92))] p-4">
<div className="flex min-h-[280px] items-center justify-center rounded-[12px] border border-dashed border-[var(--border-strong)] bg-[rgba(255,255,255,0.55)] p-6 text-center">
<div className="space-y-3">
<p className="font-[var(--font-mono)] text-[11px] uppercase tracking-[0.22em] text-[var(--ink-faint)]">
{selectedAsset.isMock ? "Mock preview asset" : "Preview asset"}
@@ -135,13 +135,13 @@ export function ReviewImagePanel({
)}
{order.hasMockAssets ? (
<div className="rounded-[24px] border border-[rgba(145,104,46,0.2)] bg-[rgba(202,164,97,0.12)] px-5 py-4 text-sm text-[#7a5323]">
<div className="rounded-[var(--panel-radius)] border border-[rgba(145,104,46,0.2)] bg-[rgba(202,164,97,0.12)] px-4 py-3 text-sm text-[#7a5323]">
mock
</div>
) : null}
{assets.length ? (
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
<div className="grid gap-2 md:grid-cols-2 xl:grid-cols-3">
{assets.map((asset) => {
const isSelected = asset.id === selectedAsset?.id;
@@ -151,7 +151,7 @@ export function ReviewImagePanel({
type="button"
onClick={() => onSelectAsset(asset.id)}
className={joinClasses(
"rounded-[24px] border px-4 py-4 text-left transition duration-150",
"rounded-[var(--panel-radius)] border px-3 py-3 text-left transition duration-150",
isSelected
? "border-[var(--accent-primary)] bg-[rgba(110,127,82,0.09)]"
: "border-[var(--border-soft)] bg-[var(--surface-muted)] hover:bg-[var(--surface)]",

View File

@@ -54,7 +54,7 @@ export function ReviewRevisionPanel({
<div className="space-y-2">
<CardEyebrow>Manual revision</CardEyebrow>
<div className="space-y-1">
<CardTitle>稿</CardTitle>
<CardTitle></CardTitle>
<CardDescription>
线稿稿 approve
signal 线
@@ -83,7 +83,7 @@ export function ReviewRevisionPanel({
value={uploadedUri}
onChange={(event) => setUploadedUri(event.target.value)}
placeholder="mock://manual-revision-v1"
className="min-h-12 w-full rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-4 text-sm text-[var(--ink-strong)] outline-none transition placeholder:text-[var(--ink-faint)] focus:border-[var(--accent-primary)] focus:bg-[var(--surface)]"
className="h-9 w-full rounded-[var(--panel-radius)] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-3 text-sm text-[var(--ink-strong)] outline-none transition placeholder:text-[var(--ink-faint)] focus:border-[var(--accent-primary)] focus:bg-[var(--surface)]"
/>
</label>
@@ -96,32 +96,32 @@ export function ReviewRevisionPanel({
onChange={(event) => setComment(event.target.value)}
rows={4}
placeholder="说明这版离线修订解决了什么问题。"
className="min-h-[112px] w-full rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-4 py-3 text-sm leading-6 text-[var(--ink-strong)] outline-none transition placeholder:text-[var(--ink-faint)] focus:border-[var(--accent-primary)] focus:bg-[var(--surface)]"
className="min-h-[104px] w-full rounded-[var(--panel-radius)] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-3 py-2.5 text-sm leading-6 text-[var(--ink-strong)] outline-none transition placeholder:text-[var(--ink-faint)] focus:border-[var(--accent-primary)] focus:bg-[var(--surface)]"
/>
</label>
{revisionError ? (
<div className="rounded-[24px] border border-[#b88472] bg-[#f8ece5] px-5 py-4 text-sm text-[#7f4b3b]">
<div className="rounded-[var(--panel-radius)] border border-[#b88472] bg-[#f8ece5] px-4 py-3 text-sm text-[#7f4b3b]">
{revisionError}
</div>
) : null}
{revisionResult ? (
<div className="rounded-[24px] border border-[rgba(88,106,56,0.18)] bg-[rgba(110,127,82,0.12)] px-5 py-4 text-sm text-[#50633b]">
<div className="rounded-[var(--panel-radius)] border border-[rgba(88,106,56,0.18)] bg-[rgba(110,127,82,0.12)] px-4 py-3 text-sm text-[#50633b]">
稿 v{revisionResult.versionNo} {revisionResult.revisionCount}
</div>
) : null}
{confirmResult ? (
<div className="rounded-[24px] border border-[rgba(88,106,56,0.18)] bg-[rgba(110,127,82,0.12)] px-5 py-4 text-sm text-[#50633b]">
<div className="rounded-[var(--panel-radius)] border border-[rgba(88,106,56,0.18)] bg-[rgba(110,127,82,0.12)] px-4 py-3 text-sm text-[#50633b]">
稿线
</div>
) : null}
<div className="grid gap-3">
<div className="grid gap-2">
<Button
variant="secondary"
size="lg"
size="md"
disabled={!order || !selectedAsset || isSubmitting}
onClick={() => onRegisterRevision({ uploadedUri, comment })}
>
@@ -130,7 +130,7 @@ export function ReviewRevisionPanel({
{pendingManualConfirm ? (
<Button
variant="primary"
size="lg"
size="md"
disabled={!order || isSubmitting}
onClick={() => onConfirmRevision(comment)}
>

View File

@@ -54,8 +54,8 @@ export function ReviewWorkflowSummary({
{!isLoading && !error && workflow ? (
<>
<div className="grid gap-3 sm:grid-cols-2">
<div className="rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-4 py-4">
<div className="grid gap-2 sm:grid-cols-2">
<div className="rounded-[var(--panel-radius)] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-3 py-3">
<p className="font-[var(--font-mono)] text-[11px] uppercase tracking-[0.2em] text-[var(--ink-faint)]">
Current step
</p>
@@ -64,7 +64,7 @@ export function ReviewWorkflowSummary({
<StatusBadge status={workflow.status} />
</div>
</div>
<div className="rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-4 py-4">
<div className="rounded-[var(--panel-radius)] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-3 py-3">
<p className="font-[var(--font-mono)] text-[11px] uppercase tracking-[0.2em] text-[var(--ink-faint)]">
Failure count
</p>
@@ -75,7 +75,7 @@ export function ReviewWorkflowSummary({
</div>
{workflow.hasMockAssets ? (
<div className="rounded-[24px] border border-[rgba(145,104,46,0.2)] bg-[rgba(202,164,97,0.12)] px-5 py-4 text-sm text-[#7a5323]">
<div className="rounded-[var(--panel-radius)] border border-[rgba(145,104,46,0.2)] bg-[rgba(202,164,97,0.12)] px-4 py-3 text-sm text-[#7a5323]">
mock
</div>
) : null}
@@ -85,7 +85,7 @@ export function ReviewWorkflowSummary({
<div
key={step.id}
className={joinClasses(
"rounded-[24px] border px-4 py-4",
"rounded-[var(--panel-radius)] border px-3 py-3",
step.isCurrent
? "border-[var(--accent-primary)] bg-[rgba(110,127,82,0.09)]"
: "border-[var(--border-soft)] bg-[var(--surface-muted)]",

View File

@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { EmptyState } from "@/components/ui/empty-state";
import { PageHeader } from "@/components/ui/page-header";
import { MetricChip } from "@/components/ui/metric-chip";
import { StatusBadge } from "@/components/ui/status-badge";
import { ReviewActionPanel } from "@/features/reviews/components/review-action-panel";
import { ReviewImagePanel } from "@/features/reviews/components/review-image-panel";
@@ -340,61 +340,48 @@ export function ReviewWorkbenchDetailScreen({
}
return (
<section className="space-y-8">
<PageHeader
eyebrow="Review detail"
title={`订单 #${orderDetail.orderId}`}
description="审核详情页只处理单个订单,列表筛选和切单行为统一留在审核工作台首页。"
meta={`更新于 ${formatTimestamp(orderDetail.updatedAt)}`}
actions={
<Link
href="/reviews/workbench"
className="inline-flex min-h-11 items-center justify-center rounded-full border border-[var(--border-strong)] bg-[var(--surface)] px-4 text-sm font-medium text-[var(--ink-strong)] transition hover:bg-[var(--surface-muted)]"
>
</Link>
}
/>
<section className="space-y-5">
<div className="sticky top-0 z-10 -mx-4 border-b border-[var(--border-soft)] bg-[rgba(255,250,242,0.95)] px-4 py-4 backdrop-blur md:-mx-6 md:px-6">
<div className="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div className="space-y-2">
<p className="font-[var(--font-mono)] text-[11px] uppercase tracking-[0.24em] text-[var(--ink-faint)]">
Review detail
</p>
<div className="flex flex-wrap items-center gap-2">
<h1 className="text-2xl font-semibold tracking-[-0.03em] text-[var(--ink-strong)]">
#{orderDetail.orderId}
</h1>
<StatusBadge status={orderDetail.status} />
<StatusBadge variant="workflowStep" status={orderDetail.currentStep} />
</div>
<div className="flex flex-wrap gap-2">
<MetricChip
label="workflow"
value={orderDetail.workflowId ?? "暂未分配"}
/>
<MetricChip label="step" value={orderDetail.currentStepLabel} />
<MetricChip
label="revision"
value={orderDetail.pendingManualConfirm ? "修订待确认" : "无待确认修订"}
/>
</div>
</div>
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
<div className="rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface)] px-4 py-4">
<p className="font-[var(--font-mono)] text-[11px] uppercase tracking-[0.2em] text-[var(--ink-faint)]">
Order status
</p>
<div className="mt-3">
<StatusBadge status={orderDetail.status} />
</div>
</div>
<div className="rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface)] px-4 py-4">
<p className="font-[var(--font-mono)] text-[11px] uppercase tracking-[0.2em] text-[var(--ink-faint)]">
Workflow
</p>
<p className="mt-3 text-lg font-semibold tracking-[-0.02em] text-[var(--ink-strong)]">
{orderDetail.workflowId ?? "暂未分配"}
</p>
</div>
<div className="rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface)] px-4 py-4">
<p className="font-[var(--font-mono)] text-[11px] uppercase tracking-[0.2em] text-[var(--ink-faint)]">
Current step
</p>
<div className="mt-3 flex flex-wrap items-center gap-2">
<StatusBadge variant="workflowStep" status={orderDetail.currentStep} />
<span className="text-sm text-[var(--ink-muted)]">
{orderDetail.currentStepLabel}
</span>
</div>
</div>
<div className="rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface)] px-4 py-4">
<p className="font-[var(--font-mono)] text-[11px] uppercase tracking-[0.2em] text-[var(--ink-faint)]">
Workflow status
</p>
<div className="mt-3">
<StatusBadge status={workflowDetail.status} />
<div className="flex flex-col items-start gap-2 xl:items-end">
<p className="font-[var(--font-mono)] text-[11px] uppercase tracking-[0.18em] text-[var(--ink-faint)]">
{formatTimestamp(orderDetail.updatedAt)}
</p>
<Link
href="/reviews/workbench"
className="inline-flex h-9 items-center justify-center rounded-md border border-[var(--border-strong)] bg-[var(--surface)] px-3 text-sm font-medium text-[var(--ink-strong)] transition hover:bg-[var(--surface-muted)]"
>
</Link>
</div>
</div>
</div>
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
<div className="grid gap-5 xl:grid-cols-[minmax(0,1fr)_380px]">
<ReviewImagePanel
error={contextError}
isLoading={isLoadingContext}
@@ -403,7 +390,16 @@ export function ReviewWorkbenchDetailScreen({
onSelectAsset={setSelectedAssetId}
/>
<div className="grid gap-6">
<div className="grid gap-4 xl:sticky xl:top-24 xl:self-start">
<ReviewActionPanel
key={`${orderDetail.orderId}-${submissionResult?.decision ?? "idle"}`}
isSubmitting={isSubmitting}
order={orderDetail}
selectedAsset={selectedAsset}
submissionError={submissionError}
submissionResult={submissionResult}
onSubmit={handleSubmit}
/>
<ReviewRevisionPanel
isSubmitting={isSubmitting}
order={orderDetail}
@@ -415,15 +411,6 @@ export function ReviewWorkbenchDetailScreen({
onRegisterRevision={handleRegisterRevision}
onConfirmRevision={handleConfirmRevision}
/>
<ReviewActionPanel
key={`${orderDetail.orderId}-${submissionResult?.decision ?? "idle"}`}
isSubmitting={isSubmitting}
order={orderDetail}
selectedAsset={selectedAsset}
submissionError={submissionError}
submissionResult={submissionResult}
onSubmit={handleSubmit}
/>
<ReviewWorkflowSummary
error={contextError}
isLoading={isLoadingContext}