# 版本管理 v3 前端对接文档 > 完整的前端集成指南,包含 TypeScript 类型定义、API 调用示例和 UI 展示建议 **版本:** v3.0.0 **最后更新:** 2025-11-18 **适用前端框架:** React / Vue / Angular 通用 --- ## 📋 目录 - [快速开始](#快速开始) - [API 接口](#api-接口) - [TypeScript 类型定义](#typescript-类型定义) - [请求示例](#请求示例) - [响应数据处理](#响应数据处理) - [UI 展示建议](#ui-展示建议) - [错误处理](#错误处理) - [常见场景](#常见场景) --- ## 🚀 快速开始 ### 1. 基础配置 ```typescript // config/api.ts export const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000'; export const API_VERSION_PREFIX = '/admin/api/v3/versions'; // 获取完整的API路径 export const getVersionApiUrl = (path: string) => { return `${API_BASE_URL}${API_VERSION_PREFIX}${path}`; }; ``` ### 2. Axios 配置(推荐) ```typescript // utils/request.ts import axios from 'axios'; const request = axios.create({ baseURL: API_BASE_URL, timeout: 30000, }); // 请求拦截器 - 添加 Token request.interceptors.request.use( (config) => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error) ); // 响应拦截器 - 错误处理 request.interceptors.response.use( (response) => response.data, (error) => { if (error.response?.status === 401) { // Token 过期,跳转登录 window.location.href = '/login'; } return Promise.reject(error); } ); export default request; ``` --- ## 🔌 API 接口 ### 接口总览 | 接口名称 | 方法 | 路径 | 说明 | | ---------------- | ------ | ------------------------------------- | ------------------ | | 获取版本列表 | GET | `/versions/{entityId}` | 查询所有历史版本 | | 版本对比 | POST | `/versions/compare` | 对比两个版本 | | 获取失败评查点 | GET | `/versions/{entityId}/failed-points` | 查询不通过的评查点 | | 获取统计信息 | GET | `/versions/statistics` | 查询统计数据 | ### 通用查询参数 所有接口都支持 `table_name` 参数(可选): ```typescript ?table_name=documents // 文档(默认) ?table_name=dossiers // 卷宗 ``` --- ## 📘 TypeScript 类型定义 ### 完整类型定义文件 ```typescript // types/version.ts /** * 版本信息 */ export interface VersionInfo { version_number: number; // 版本号(1, 2, 3...) entity_id: number; // 实体ID entity_name: string; // 实体名称 created_at: string; // 创建时间(ISO 8601) status: string; // 状态 total_evaluation_points: number; // 总评查点数 failed_evaluation_points: number; // 不通过的评查点数 is_current: boolean; // 是否为当前版本 metadata?: { // 扩展元数据 document_number?: string; type_id?: number; file_size?: number; audit_status?: string; [key: string]: any; }; } /** * 版本列表响应 */ export interface VersionListResponse { current_version: VersionInfo; // 当前版本信息 entity_name: string; // 实体名称 total_versions: number; // 总版本数 versions: VersionInfo[]; // 版本列表 } /** * 问题详情 */ export interface IssueDetail { name: string; // 问题名称 description: string; // 问题描述 current_message?: string; // 当前版本消息 previous_message?: string; // 上个版本消息 } /** * 对比统计数据 */ export interface ComparisonStatistics { old_total: number; // 上个版本问题总数 new_total: number; // 当前版本问题总数 change_from_previous: number; // ✅ 对比上个版本的变化(负数=减少,正数=增加) change_type: 'decreased' | 'increased' | 'unchanged'; // 变化类型 change_amount: number; // 变化的绝对值 improvement_rate: number; // 改进率(0-100) resolved_count: number; // 已解决的问题数 remaining_count: number; // 仍然存在的问题数 new_issues_count: number; // 新增的问题数 } /** * 统计分析结果 */ export interface StatisticalAnalysis { type: 'statistical'; // 分析类型 summary: string; // 分析总结 conclusion: 'positive' | 'negative' | 'neutral'; // 结论 recommendations: string[]; // 建议列表 change_details: { resolved: number; remaining: number; new: number; }; } /** * 风险评估 */ export interface RiskAssessment { overall_risk: 'high' | 'medium' | 'low'; // 整体风险 critical_issues: string[]; // 关键问题列表 } /** * 优先级问题 */ export interface PriorityIssue { name: string; // 问题名称 priority: 'high' | 'medium' | 'low'; // 优先级 reason: string; // 原因说明 } /** * LLM 分析结果 */ export interface LLMAnalysis { type: 'llm'; // 分析类型 summary: string; // 分析总结 conclusion: 'positive' | 'negative' | 'neutral'; // 结论 recommendations: string[]; // 建议列表 risk_assessment?: RiskAssessment; // 风险评估 priority_issues?: PriorityIssue[]; // 优先级问题列表 change_details: { resolved: number; remaining: number; new: number; }; raw_llm_output?: Record; // 原始LLM输出 } /** * 对比响应 */ export interface ComparisonResponse { statistics: ComparisonStatistics; // 统计数据 resolved_issues: IssueDetail[]; // 已解决的问题列表 remaining_issues: IssueDetail[]; // 仍存在的问题列表 new_issues: IssueDetail[]; // 新增的问题列表 analysis: StatisticalAnalysis | LLMAnalysis; // 分析结果(统一格式) } /** * 对比请求 */ export interface ComparisonRequest { old_version_id: number; // 旧版本ID new_version_id: number; // 新版本ID enable_llm: boolean; // 是否启用LLM深度分析(默认false) } /** * 失败评查点响应 */ export interface FailedPointsResponse { entity_id: number; // 实体ID failed_count: number; // 失败评查点数 failed_points: Array<{ evaluation_result_id: number; evaluation_point_id: number; point_name: string; point_description: string; evaluated_results: { result: boolean; message: string; [key: string]: any; }; evaluated_point_results_log?: any; }>; } /** * 统计响应 */ export interface StatisticsResponse { total_documents: number; // 文档总数 total_versions: number; // 版本总数 unique_documents: number; // 唯一文档数 average_versions_per_document: number; // 平均版本数 version_distribution?: Record; // 版本分布 } /** * 错误响应 */ export interface ErrorResponse { detail: string; // 错误详情 } ``` --- ## 📡 请求示例 ### 1. 获取版本列表 ```typescript // api/version.ts import request from '@/utils/request'; import type { VersionListResponse } from '@/types/version'; /** * 获取版本列表 */ export const getVersionList = async ( entityId: number, tableName: string = 'documents' ): Promise => { return request.get(`/admin/api/v3/versions/${entityId}`, { params: { table_name: tableName } }); }; // 使用示例 const versionList = await getVersionList(123); console.log('当前版本:', versionList.current_version); console.log('总版本数:', versionList.total_versions); ``` ### 2. 版本对比(统计模式) ```typescript /** * 版本对比 - 统计模式(快速) */ export const compareVersions = async ( oldVersionId: number, newVersionId: number, tableName: string = 'documents' ): Promise => { return request.post( '/admin/api/v3/versions/compare', { old_version_id: oldVersionId, new_version_id: newVersionId, enable_llm: false // 统计模式 }, { params: { table_name: tableName } } ); }; // 使用示例 const comparison = await compareVersions(122, 123); console.log('问题变化:', comparison.statistics.change_from_previous); console.log('改进率:', comparison.statistics.improvement_rate); ``` ### 3. 版本对比(LLM 模式) ```typescript /** * 版本对比 - LLM 模式(智能) */ export const compareVersionsWithLLM = async ( oldVersionId: number, newVersionId: number, tableName: string = 'documents' ): Promise => { return request.post( '/admin/api/v3/versions/compare', { old_version_id: oldVersionId, new_version_id: newVersionId, enable_llm: true // ✅ 启用 LLM }, { params: { table_name: tableName } } ); }; // 使用示例 const comparisonLLM = await compareVersionsWithLLM(122, 123); if (comparisonLLM.analysis.type === 'llm') { console.log('风险评估:', comparisonLLM.analysis.risk_assessment); console.log('优先级问题:', comparisonLLM.analysis.priority_issues); } ``` ### 4. 获取失败评查点 ```typescript /** * 获取失败评查点 */ export const getFailedPoints = async ( entityId: number, tableName: string = 'documents' ): Promise => { return request.get(`/admin/api/v3/versions/${entityId}/failed-points`, { params: { table_name: tableName } }); }; // 使用示例 const failedPoints = await getFailedPoints(123); console.log('失败评查点数:', failedPoints.failed_count); ``` ### 5. 获取统计信息 ```typescript /** * 获取统计信息 */ export const getStatistics = async ( tableName: string = 'documents', dateFrom?: string, dateTo?: string ): Promise => { return request.get('/admin/api/v3/versions/statistics', { params: { table_name: tableName, date_from: dateFrom, date_to: dateTo } }); }; // 使用示例 const stats = await getStatistics('documents', '2025-01-01', '2025-12-31'); console.log('平均版本数:', stats.average_versions_per_document); ``` --- ## 🎨 UI 展示建议 ### 1. 版本列表展示 #### React 示例 ```tsx // components/VersionList.tsx import React from 'react'; import { Table, Tag, Badge } from 'antd'; import type { VersionInfo } from '@/types/version'; interface VersionListProps { versions: VersionInfo[]; onVersionClick?: (version: VersionInfo) => void; } export const VersionList: React.FC = ({ versions, onVersionClick }) => { const columns = [ { title: '版本号', dataIndex: 'version_number', key: 'version_number', width: 100, render: (num: number, record: VersionInfo) => (
版本 {num} {record.is_current && 当前}
), }, { title: '创建时间', dataIndex: 'created_at', key: 'created_at', render: (date: string) => new Date(date).toLocaleString('zh-CN'), }, { title: '评查点统计', key: 'evaluation', render: (_: any, record: VersionInfo) => (
总数: {record.total_evaluation_points} {record.failed_evaluation_points > 0 ? ( ) : ( 全部通过 )}
), }, { title: '状态', dataIndex: 'status', key: 'status', render: (status: string) => { const statusMap: Record = { completed: { color: 'success', text: '已完成' }, processing: { color: 'processing', text: '处理中' }, failed: { color: 'error', text: '失败' }, }; const config = statusMap[status] || { color: 'default', text: status }; return {config.text}; }, }, ]; return ( ({ onClick: () => onVersionClick?.(record), style: { cursor: 'pointer' }, })} /> ); }; ``` ### 2. 版本对比结果展示 #### 关键指标卡片 ```tsx // components/ComparisonSummary.tsx import React from 'react'; import { Card, Statistic, Row, Col, Tag } from 'antd'; import { ArrowDownOutlined, ArrowUpOutlined, MinusOutlined } from '@ant-design/icons'; import type { ComparisonStatistics } from '@/types/version'; interface ComparisonSummaryProps { statistics: ComparisonStatistics; } export const ComparisonSummary: React.FC = ({ statistics }) => { // 计算变化趋势的图标和颜色 const getTrendIcon = () => { if (statistics.change_from_previous < 0) { return ; } else if (statistics.change_from_previous > 0) { return ; } return ; }; const getTrendColor = () => { if (statistics.change_from_previous < 0) return '#52c41a'; if (statistics.change_from_previous > 0) return '#ff4d4f'; return '#8c8c8c'; }; const getTrendText = () => { const abs = Math.abs(statistics.change_from_previous); if (statistics.change_from_previous < 0) { return `减少 ${abs} 个问题`; } else if (statistics.change_from_previous > 0) { return `增加 ${abs} 个问题`; } return '无变化'; }; return (
对比上个版本
{getTrendIcon()} {getTrendText()}
0 ? '#52c41a' : '#8c8c8c' }} /> {/* 变化类型标签 */}
{statistics.change_type === 'decreased' && ( }>版本改进 )} {statistics.change_type === 'increased' && ( }>版本退步 )} {statistics.change_type === 'unchanged' && ( }>版本持平 )}
); }; ``` #### 分析结果展示 ```tsx // components/AnalysisResult.tsx import React from 'react'; import { Card, Alert, List, Tag, Descriptions } from 'antd'; import type { StatisticalAnalysis, LLMAnalysis } from '@/types/version'; interface AnalysisResultProps { analysis: StatisticalAnalysis | LLMAnalysis; } export const AnalysisResult: React.FC = ({ analysis }) => { const conclusionConfig = { positive: { type: 'success' as const, text: '积极' }, negative: { type: 'error' as const, text: '消极' }, neutral: { type: 'info' as const, text: '中性' }, }; const config = conclusionConfig[analysis.conclusion]; return ( 分析结果 {analysis.type === 'llm' ? '🤖 AI 分析' : '📊 统计分析'} } bordered={false} > {/* 总结 */} {/* 建议列表 */} ( {index + 1}. {item} )} /> {/* LLM 特有分析 */} {analysis.type === 'llm' && ( <> {/* 风险评估 */} {analysis.risk_assessment && ( {analysis.risk_assessment.overall_risk.toUpperCase()} {analysis.risk_assessment.critical_issues?.length > 0 && ( ( ! {issue} )} /> )} )} {/* 优先级问题 */} {analysis.priority_issues && analysis.priority_issues.length > 0 && ( ( {issue.priority.toUpperCase()} } title={issue.name} description={issue.reason} /> )} /> )} )} ); }; ``` ### 3. 问题列表展示 ```tsx // components/IssueList.tsx import React from 'react'; import { Collapse, Badge, Tag, Empty } from 'antd'; import { CheckCircleOutlined, ExclamationCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; import type { IssueDetail } from '@/types/version'; interface IssueListProps { resolvedIssues: IssueDetail[]; remainingIssues: IssueDetail[]; newIssues: IssueDetail[]; } export const IssueList: React.FC = ({ resolvedIssues, remainingIssues, newIssues, }) => { const { Panel } = Collapse; return ( {/* 已解决的问题 */} 已解决的问题 } key="resolved" > {resolvedIssues.length === 0 ? ( ) : (
{resolvedIssues.map((issue, index) => (
{issue.name}
{issue.description && (
{issue.description}
)} {issue.previous_message && (
原因: {issue.previous_message}
)}
))}
)}
{/* 仍存在的问题 */} 仍存在的问题 } key="remaining" > {remainingIssues.length === 0 ? ( ) : (
{remainingIssues.map((issue, index) => (
! {issue.name}
{issue.description && (
{issue.description}
)} {issue.current_message && (
当前: {issue.current_message}
)} {issue.previous_message && issue.previous_message !== issue.current_message && (
之前: {issue.previous_message}
)}
))}
)}
{/* 新增的问题 */} {newIssues.length > 0 && ( 新增的问题 } key="new" >
{newIssues.map((issue, index) => (
+ {issue.name}
{issue.description && (
{issue.description}
)} {issue.current_message && (
{issue.current_message}
)}
))}
)}
); }; ``` --- ## ⚠️ 错误处理 ### 错误处理封装 ```typescript // utils/errorHandler.ts import { message } from 'antd'; import type { AxiosError } from 'axios'; import type { ErrorResponse } from '@/types/version'; export const handleApiError = (error: AxiosError) => { if (error.response) { const { status, data } = error.response; switch (status) { case 401: message.error('登录已过期,请重新登录'); // 跳转到登录页 window.location.href = '/login'; break; case 404: message.error(data.detail || '请求的资源不存在或无权限访问'); break; case 500: message.error(data.detail || '服务器内部错误,请稍后重试'); break; default: message.error(data.detail || '请求失败,请稍后重试'); } } else if (error.request) { message.error('网络连接失败,请检查网络'); } else { message.error('请求配置错误'); } return Promise.reject(error); }; ``` ### 在组件中使用 ```tsx // pages/VersionComparison.tsx import React, { useState } from 'react'; import { Button, message, Spin } from 'antd'; import { compareVersions } from '@/api/version'; import { handleApiError } from '@/utils/errorHandler'; export const VersionComparison: React.FC = () => { const [loading, setLoading] = useState(false); const handleCompare = async () => { try { setLoading(true); const result = await compareVersions(122, 123); message.success('对比完成'); // 处理结果... } catch (error) { handleApiError(error as any); } finally { setLoading(false); } }; return ( ); }; ``` --- ## 🎯 常见场景 ### 场景 1: 文档版本历史页面 ```tsx // pages/DocumentVersionHistory.tsx import React, { useEffect, useState } from 'react'; import { Card, Button, Modal, message } from 'antd'; import { getVersionList, compareVersions } from '@/api/version'; import { VersionList } from '@/components/VersionList'; import { ComparisonSummary } from '@/components/ComparisonSummary'; import { IssueList } from '@/components/IssueList'; import type { VersionInfo, ComparisonResponse } from '@/types/version'; interface Props { documentId: number; } export const DocumentVersionHistory: React.FC = ({ documentId }) => { const [versions, setVersions] = useState([]); const [loading, setLoading] = useState(false); const [selectedVersions, setSelectedVersions] = useState([]); const [comparisonResult, setComparisonResult] = useState(null); const [modalVisible, setModalVisible] = useState(false); // 加载版本列表 useEffect(() => { loadVersions(); }, [documentId]); const loadVersions = async () => { try { setLoading(true); const data = await getVersionList(documentId); setVersions(data.versions); } catch (error) { message.error('加载版本列表失败'); } finally { setLoading(false); } }; // 选择版本进行对比 const handleVersionSelect = (version: VersionInfo) => { if (selectedVersions.includes(version.entity_id)) { setSelectedVersions(selectedVersions.filter(id => id !== version.entity_id)); } else if (selectedVersions.length < 2) { setSelectedVersions([...selectedVersions, version.entity_id]); } else { message.warning('最多只能选择两个版本进行对比'); } }; // 执行对比 const handleCompare = async () => { if (selectedVersions.length !== 2) { message.warning('请选择两个版本进行对比'); return; } try { setLoading(true); const [oldId, newId] = selectedVersions.sort((a, b) => a - b); const result = await compareVersions(oldId, newId); setComparisonResult(result); setModalVisible(true); } catch (error) { message.error('版本对比失败'); } finally { setLoading(false); } }; return (
对比选中的版本 } > {/* 对比结果弹窗 */} setModalVisible(false)} width={1000} footer={null} > {comparisonResult && (
)}
); }; ``` ### 场景 2: LLM 分析开关 ```tsx // components/ComparisonWithLLMOption.tsx import React, { useState } from 'react'; import { Button, Switch, Space, message, Tooltip } from 'antd'; import { compareVersions, compareVersionsWithLLM } from '@/api/version'; interface Props { oldVersionId: number; newVersionId: number; onResult: (result: any) => void; } export const ComparisonWithLLMOption: React.FC = ({ oldVersionId, newVersionId, onResult, }) => { const [enableLLM, setEnableLLM] = useState(false); const [loading, setLoading] = useState(false); const handleCompare = async () => { try { setLoading(true); const result = enableLLM ? await compareVersionsWithLLM(oldVersionId, newVersionId) : await compareVersions(oldVersionId, newVersionId); onResult(result); message.success(enableLLM ? '智能分析完成' : '快速对比完成'); } catch (error) { message.error('对比失败'); } finally { setLoading(false); } }; return ( {enableLLM && ( 预计耗时: 2-5秒 )} ); }; ``` ### 场景 3: 实时加载优化 ```typescript // hooks/useVersionComparison.ts import { useState, useCallback } from 'react'; import { compareVersions, compareVersionsWithLLM } from '@/api/version'; import type { ComparisonResponse } from '@/types/version'; export const useVersionComparison = () => { const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const [error, setError] = useState(null); const compare = useCallback(async ( oldVersionId: number, newVersionId: number, enableLLM: boolean = false ) => { try { setLoading(true); setError(null); // 先快速获取统计结果 const statisticalResult = await compareVersions(oldVersionId, newVersionId); setResult(statisticalResult); // 如果启用 LLM,后台更新为 LLM 结果 if (enableLLM) { const llmResult = await compareVersionsWithLLM(oldVersionId, newVersionId); setResult(llmResult); } } catch (err) { setError(err as Error); } finally { setLoading(false); } }, []); return { loading, result, error, compare }; }; // 使用示例 const MyComponent = () => { const { loading, result, compare } = useVersionComparison(); useEffect(() => { compare(122, 123, true); // 先显示统计,后更新为 LLM }, []); return
{/* 渲染结果 */}
; }; ``` --- ## 📚 完整示例 ### 完整的版本对比页面 ```tsx // pages/VersionComparisonPage.tsx import React, { useState, useEffect } from 'react'; import { Card, Button, Select, Space, message, Spin, Tabs } from 'antd'; import { getVersionList } from '@/api/version'; import { useVersionComparison } from '@/hooks/useVersionComparison'; import { ComparisonSummary } from '@/components/ComparisonSummary'; import { AnalysisResult } from '@/components/AnalysisResult'; import { IssueList } from '@/components/IssueList'; import { ComparisonWithLLMOption } from '@/components/ComparisonWithLLMOption'; import type { VersionInfo } from '@/types/version'; interface Props { documentId: number; } export const VersionComparisonPage: React.FC = ({ documentId }) => { const [versions, setVersions] = useState([]); const [oldVersionId, setOldVersionId] = useState(); const [newVersionId, setNewVersionId] = useState(); const { loading, result, compare } = useVersionComparison(); useEffect(() => { loadVersions(); }, [documentId]); const loadVersions = async () => { try { const data = await getVersionList(documentId); setVersions(data.versions); // 自动选择最新两个版本 if (data.versions.length >= 2) { setNewVersionId(data.versions[0].entity_id); setOldVersionId(data.versions[1].entity_id); } } catch (error) { message.error('加载版本列表失败'); } }; const handleCompare = (enableLLM: boolean) => { if (!oldVersionId || !newVersionId) { message.warning('请选择要对比的版本'); return; } compare(oldVersionId, newVersionId, enableLLM); }; return (
{/* 版本选择 */} 旧版本: 新版本: compare(oldVersionId!, newVersionId!, false)} /> {/* 对比结果 */} {result && ( , }, { key: '2', label: '💡 分析结果', children: , }, { key: '3', label: '📋 问题详情', children: ( ), }, ]} /> )}
); }; ``` --- ## 📝 总结 ### 核心要点 1. **change_from_previous 是关键指标** - 负数 = 改进 ✅ - 正数 = 退步 ❌ - 零 = 持平 ➡️ 2. **LLM 开关灵活使用** - 默认关闭(快速、免费) - 需要时开启(智能、有成本) 3. **分步加载优化体验** - 先显示统计结果 - 后台加载 LLM 分析 4. **完善的错误处理** - 401: 跳转登录 - 404: 权限提示 - 500: 重试提示 --- ## 🔗 相关资源 - [后端 API 文档](./version_management_v3_quick_reference.md) - [完整实现文档](./version_management_v3_implementation_summary.md) - [API 测试脚本](../test_version_api_v3.py) --- **维护者:** Claude Code **联系方式:** 通过 GitHub Issues 反馈问题 **最后更新:** 2025-11-18