feat: Excel 导入预检与动态问答,AI 聊天/文件解析体验修复
- imports: 新增 preflight_import 预检报告(判定/分阶段统计/阻断归因/问题/下一步/错误示例),导入任务 settings 落库(映射/校区/更新/重复/未匹配策略),预览应用策略,向导提交写操作日志 - ai-chat: 新增 excel_analyze(ExcelJS)工具,移除附件/上下文截断,start_import_wizard 支持确认参数,ui.import_preflight SSE,预览确认写操作日志 - admin: ImportPreflightCard 渲染与持久化,聊天抽屉布局/侧边栏修复,考勤页 CSS 引入,费用/学生页接口 schema 校验修复
This commit is contained in:
115
apps/admin/src/components/AiChat/ImportPreflightCard.tsx
Normal file
115
apps/admin/src/components/AiChat/ImportPreflightCard.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React from 'react';
|
||||
import { TableOutlined } from '@ant-design/icons';
|
||||
import { Card, Flex, Space, Tag, Typography } from 'antd';
|
||||
import type {
|
||||
AiImportPreflight,
|
||||
AiImportPreflightVerdict,
|
||||
} from './types';
|
||||
|
||||
const VERDICT_META: Record<
|
||||
AiImportPreflightVerdict,
|
||||
{ label: string; color: string }
|
||||
> = {
|
||||
ready: { label: '可导入', color: 'success' },
|
||||
needs_input: { label: '需要确认', color: 'warning' },
|
||||
blocked: { label: '暂无法导入', color: 'error' },
|
||||
};
|
||||
|
||||
/**
|
||||
* 上传 Excel 后的“可插入性预检报告”卡片:展示判定结论、
|
||||
* 分阶段统计、阻断原因、待确认问题与下一步建议。
|
||||
*/
|
||||
export const ImportPreflightCard: React.FC<{ preflight: AiImportPreflight }> = ({
|
||||
preflight,
|
||||
}) => {
|
||||
const verdict = VERDICT_META[preflight.verdict] ?? VERDICT_META.needs_input;
|
||||
return (
|
||||
<Card
|
||||
size="small"
|
||||
className="ai-chat-preflight-card"
|
||||
title={
|
||||
<Flex gap={8} align="center" wrap>
|
||||
<TableOutlined />
|
||||
<Typography.Text strong>Excel 导入预检</Typography.Text>
|
||||
<Tag color={verdict.color}>{verdict.label}</Tag>
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
<Space orientation="vertical" size={12} style={{ width: '100%' }}>
|
||||
{preflight.stages.length > 0 ? (
|
||||
<Space orientation="vertical" size={10} style={{ width: '100%' }}>
|
||||
{preflight.stages.map((stage) => (
|
||||
<Flex key={stage.stepKey} vertical gap={4}>
|
||||
<Flex gap={8} align="center" wrap>
|
||||
<Typography.Text strong>{stage.label}</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{stage.sheetNames.join('、')}
|
||||
</Typography.Text>
|
||||
</Flex>
|
||||
<Space size={4} wrap>
|
||||
<Tag color="blue">共 {stage.total} 行</Tag>
|
||||
<Tag color="green">新建 {stage.create}</Tag>
|
||||
<Tag color="orange">更新 {stage.update}</Tag>
|
||||
<Tag color="red">错误 {stage.error}</Tag>
|
||||
<Tag>跳过 {stage.skip}</Tag>
|
||||
</Space>
|
||||
</Flex>
|
||||
))}
|
||||
</Space>
|
||||
) : (
|
||||
<Typography.Text type="secondary">未识别到可导入的工作表</Typography.Text>
|
||||
)}
|
||||
|
||||
{preflight.blocks.length > 0 && (
|
||||
<Space orientation="vertical" size={4} style={{ width: '100%' }}>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
阻断原因
|
||||
</Typography.Text>
|
||||
{preflight.blocks.map((block) => (
|
||||
<Flex key={block.code} gap={8} align="baseline" wrap>
|
||||
<Tag color="red">{block.label}</Tag>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{block.message}({block.count} 行)
|
||||
</Typography.Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
|
||||
{preflight.questions.length > 0 && (
|
||||
<Space orientation="vertical" size={4} style={{ width: '100%' }}>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
需要确认
|
||||
</Typography.Text>
|
||||
{preflight.questions.map((question) => (
|
||||
<Flex key={question.key} gap={8} align="baseline" wrap>
|
||||
<Tag color="gold">{question.label}</Tag>
|
||||
{question.description && (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{question.description}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
|
||||
{preflight.nextSteps.length > 0 && (
|
||||
<Space orientation="vertical" size={4} style={{ width: '100%' }}>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
下一步建议
|
||||
</Typography.Text>
|
||||
{preflight.nextSteps.map((step) => (
|
||||
<Flex key={step.key} gap={8} align="baseline" wrap>
|
||||
<Tag color="blue">{step.label}</Tag>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||
{step.description}
|
||||
</Typography.Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user