fix: align rules list and review detail flows

This commit is contained in:
wren
2026-05-06 10:35:57 +08:00
parent 99fce169cb
commit 22ef99754c
9 changed files with 257 additions and 172 deletions
@@ -9,8 +9,8 @@ import type { ReviewPoint, CharPosition } from '../ReviewPointsList';
interface ReviewPointDetailCardProps {
reviewPoint: ReviewPoint;
onReviewPointSelect: (id: string, page?: number, charPositions?: CharPosition[], value?: string) => void;
onStatusChange: (id: string, editAuditStatusId: string | number, status: string, message: string) => void;
onReviewPointSelect: (id: string | number, page?: number, charPositions?: CharPosition[], value?: string) => void;
onStatusChange: (id: string | number, editAuditStatusId: string | number, status: string, message: string) => void;
fileFormat?: string;
}
@@ -223,7 +223,7 @@ function filterOtherRule(reviewPoint: ReviewPoint): MergedRule[] {
}
// ── renderOtherRule ──
function RenderOtherRule({ rule, reviewPoint, onReviewPointSelect }: { rule: MergedRule; reviewPoint: ReviewPoint; onReviewPointSelect: (id: string, page?: number, charPos?: CharPosition[], value?: string) => void }) {
function RenderOtherRule({ rule, reviewPoint, onReviewPointSelect }: { rule: MergedRule; reviewPoint: ReviewPoint; onReviewPointSelect: (id: string | number, page?: number, charPos?: CharPosition[], value?: string) => void }) {
const fieldKey = rule.fieldKey;
const fieldValue = rule.fieldValue;
const hasFailure = Object.values(fieldValue.type || {}).some(item => item.res === false);
@@ -273,7 +273,7 @@ function RenderOtherRule({ rule, reviewPoint, onReviewPointSelect }: { rule: Mer
// ── renderConsistencyRule ──
type ChainItem = { field: string; data: { key: string; page: number; value: string; char_positions?: CharPosition[] }; res: boolean; compareMethod?: string };
function RenderConsistencyRule({ rule, reviewPoint, onReviewPointSelect }: { rule: Record<string, unknown>; reviewPoint: ReviewPoint; onReviewPointSelect: (id: string, page?: number, charPos?: CharPosition[], value?: string) => void }) {
function RenderConsistencyRule({ rule, reviewPoint, onReviewPointSelect }: { rule: Record<string, unknown>; reviewPoint: ReviewPoint; onReviewPointSelect: (id: string | number, page?: number, charPos?: CharPosition[], value?: string) => void }) {
if (reviewPoint.result !== (rule.res as boolean)) return null;
const config = rule.config as { logic?: string; pairs?: Array<{ sourceField: Record<string, { page: number; value: string; char_positions?: CharPosition[] }>; targetField: Record<string, { page: number; value: string; char_positions?: CharPosition[] }>; res: boolean; compareMethod?: string }>; selectedFields?: string[] } | undefined;
if (!config || !config.pairs || !Array.isArray(config.pairs) || config.pairs.length === 0) return null;
@@ -389,7 +389,7 @@ function RenderConsistencyRule({ rule, reviewPoint, onReviewPointSelect }: { rul
}
// ── renderModelRule ──
function RenderModelRule({ rule, reviewPoint, onReviewPointSelect, fileFormat }: { rule: Record<string, unknown>; reviewPoint: ReviewPoint; onReviewPointSelect: (id: string, page?: number, charPos?: CharPosition[], value?: string) => void; fileFormat?: string }) {
function RenderModelRule({ rule, reviewPoint, onReviewPointSelect, fileFormat }: { rule: Record<string, unknown>; reviewPoint: ReviewPoint; onReviewPointSelect: (id: string | number, page?: number, charPos?: CharPosition[], value?: string) => void; fileFormat?: string }) {
const config = rule.config as { model?: string; fields?: Record<string, { page: number | string; value: string; char_positions?: CharPosition[]; res?: boolean }>; ai_suggestion?: { summary?: string; analysis?: { failure_reason?: string; solution_approach?: string; rule_understanding?: string }; suggestions?: Record<string, { reason: string; source: { page: number | null; type: string; field: string | null }; priority: string; confidence: number; suggested_value: string | null }>; generated_at?: string }; message?: string; res?: boolean } | undefined;
if (config?.res !== reviewPoint.result) return null;
@@ -434,6 +434,91 @@ function RenderModelRule({ rule, reviewPoint, onReviewPointSelect, fileFormat }:
return <>{fieldElements}</>;
}
function RenderGenericRule({
rule,
reviewPoint,
onReviewPointSelect,
}: {
rule: Record<string, unknown>;
reviewPoint: ReviewPoint;
onReviewPointSelect: (id: string | number, page?: number, charPos?: CharPosition[], value?: string) => void;
}) {
const config = (rule.config && typeof rule.config === 'object' ? rule.config : {}) as Record<string, unknown>;
const detail = (config.detail && typeof config.detail === 'object' ? config.detail : {}) as Record<string, unknown>;
const fieldNames = Array.isArray(detail.fields)
? detail.fields.map((field) => String(field))
: Array.isArray((config as any).fields)
? (config as any).fields.map((field: unknown) => String(field))
: [];
const reason = [config.reason, detail.reason, reviewPoint.failMessage, reviewPoint.passMessage]
.find((item) => typeof item === 'string' && item.trim()) as string | undefined;
const passed = typeof rule.res === 'boolean' ? rule.res : reviewPoint.result === true;
const checkType = typeof config.check_type === 'string' ? config.check_type : '';
const primitiveType = typeof config.primitive_type === 'string' ? config.primitive_type : '';
const badgeText = checkType || primitiveType || '规则检查';
const jumpToField = (fieldName: string) => {
const fieldData = reviewPoint.content?.[fieldName];
const page = fieldData?.page || reviewPoint.contentPage?.[fieldName];
const normalizedPage = page ? Number(page) : undefined;
if (normalizedPage && Number.isFinite(normalizedPage)) {
onReviewPointSelect(
reviewPoint.id,
normalizedPage,
fieldData?.char_positions,
typeof fieldData?.value === 'string' ? fieldData.value : undefined,
);
return;
}
toastService.info(`${fieldName} 当前没有可定位页码`);
};
return (
<div className={`mb-3 rounded-md border ${passed ? 'border-emerald-200 bg-emerald-50/60' : 'border-amber-200 bg-amber-50/70'} p-3`}>
<div className="flex items-center justify-between gap-2">
<div className="text-[11px] font-medium text-slate-600">{badgeText}</div>
<span className={`inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[10.5px] ${passed ? 'bg-emerald-100 text-emerald-700' : 'bg-amber-100 text-amber-700'}`}>
<i className={passed ? 'ri-checkbox-circle-line' : 'ri-error-warning-line'} />
{passed ? '通过' : '未通过'}
</span>
</div>
{reason && (
<div className="mt-2 text-[12px] leading-5 text-slate-700">
{reason}
</div>
)}
{fieldNames.length > 0 && (
<div className="mt-3 flex flex-wrap gap-2">
{fieldNames.map((fieldName) => {
const fieldData = reviewPoint.content?.[fieldName];
const fieldValue = fieldData?.value;
const displayValue =
typeof fieldValue === 'string'
? fieldValue
: fieldValue == null
? '未抽取到值'
: JSON.stringify(fieldValue);
return (
<button
key={fieldName}
type="button"
className="min-w-0 flex-1 rounded border border-slate-200 bg-white px-2.5 py-2 text-left hover:border-[#00684a] hover:bg-[#f6fffb]"
onClick={() => jumpToField(fieldName)}
>
<div className="text-[11px] font-medium text-slate-500">{fieldName}</div>
<div className="mt-1 text-[12px] leading-5 text-slate-700 break-all">{displayValue}</div>
</button>
);
})}
</div>
)}
</div>
);
}
// ── Main Component ──
export function ReviewPointDetailCard({ reviewPoint, onReviewPointSelect, onStatusChange, fileFormat }: ReviewPointDetailCardProps) {
const resolveManualNote = () => {
@@ -519,7 +604,7 @@ export function ReviewPointDetailCard({ reviewPoint, onReviewPointSelect, onStat
if (rule.type === 'ai') {
return <div key={`rule-${i}`}>{otherRules.length > 0 && <div className="bg-gray-50 rounded border border-gray-200 text-xs mb-3" />}<RenderModelRule rule={rule} reviewPoint={reviewPoint} onReviewPointSelect={onReviewPointSelect} fileFormat={fileFormat} /></div>;
}
return null;
return <RenderGenericRule key={`rule-${i}`} rule={rule} reviewPoint={reviewPoint} onReviewPointSelect={onReviewPointSelect} />;
})}
</section>