import request from '@/config/axios' import axios from 'axios' import * as FileApi from '@/api/infra/file' // ============================================================ // 类型定义 // ============================================================ export interface ReviewMeetingProjectRespVO { id: number reviewMeetingId: number seqNo: number startTime: string endTime: string agendaCategory: string projectTitle: string reporter: string reporterUnit: string host?: string reviewDate?: string reviewResult?: 'PASS' | 'REJECT' preMeetingMaterialsComplete?: boolean postMeetingMaterialsComplete?: boolean aiSummaryStatus?: number aiSummaryUpdatedTime?: string } export const REVIEW_AGENDA_CATEGORY_OPTIONS = ['项目立项', '预验收', '项目终验'] as const export type ReviewAgendaCategory = (typeof REVIEW_AGENDA_CATEGORY_OPTIONS)[number] export interface ReviewMeetingProjectSeqUpdateReqVO { id: number seqNo: number } export interface ReviewMeetingProjectPageReqVO { pageNo?: number pageSize?: number reviewMeetingId: number projectTitle?: string agendaCategory?: string reporter?: string reviewResult?: 'PASS' | 'REJECT' reviewDate?: string } /** 独立项目列表查询(meetingId 可选,用于独立菜单页) */ export interface ReviewProjectPageReqVO { pageNo?: number pageSize?: number reviewMeetingId?: number projectTitle?: string agendaCategory?: string reporter?: string reporterUnit?: string reviewResult?: 'PASS' | 'REJECT' reviewDate?: string } export interface ReviewMeetingFileRespVO { id: number reviewMeetingId: number reviewMeetingProjectId: number materialCode?: string materialNameSnapshot?: string agendaTypeSnapshot?: string version?: number fileName: string fileUrl: string fileSize: number fileType: string aiBuildStatus?: number aiBuildStatusName?: string aiBuildErrorMessage?: string aiBuildTime?: string creator: string createTime: string } export interface ReviewMeetingFileRegisterReqVO { reviewMeetingId: number reviewMeetingProjectId: number fileName: string fileUrl: string fileSize: number fileType?: string } export interface ReviewMeetingMaterialItemRespVO { materialCode: string materialName: string required: boolean acceptExts: string tabletVisible: boolean latestFile?: ReviewMeetingFileRespVO } export interface ReviewMeetingMaterialSummaryRespVO { agendaType: string templateAvailable: boolean materials: ReviewMeetingMaterialItemRespVO[] } export interface ReviewMeetingMaterialHistoryRespVO { materialCode: string materialName: string file: ReviewMeetingFileRespVO } export interface ReviewMeetingMaterialUploadReqVO { reviewMeetingId: number reviewMeetingProjectId: number materialCode: string fileName: string fileUrl: string fileSize: number fileType?: string } // ============================================================ // API 调用 // ============================================================ /** 分页查询评审项目列表(需要 meetingId) */ export const getReviewProjectPage = (params: ReviewMeetingProjectPageReqVO) => request.get({ url: '/project/review-project/page', params }) /** 独立分页查询评审项目列表(meetingId 可选) */ export const getReviewProjectPageStandalone = (params: ReviewProjectPageReqVO) => request.get({ url: '/project/review-project/page', params }) /** 创建评审项目 */ export const createReviewProject = (data: Partial) => request.post({ url: '/project/review-project/create', data }) /** 更新评审项目信息 */ export const updateReviewProject = (data: Partial) => request.put({ url: '/project/review-project/update', data }) /** 批量更新会中序号 */ export const updateReviewProjectSeqBatch = (data: ReviewMeetingProjectSeqUpdateReqVO[]) => request.put({ url: '/project/review-project/update-seq-batch', data }) /** 删除评审项目 */ export const deleteReviewProject = (ids: number[]) => request.delete({ url: '/project/review-project/delete', params: { ids: ids.join(',') } }) /** 更新评审项目主持人 */ export const updateProjectHost = (projectId: number, host: string) => request.put({ url: '/project/review-project/update-host', params: { projectId, host } }) /** 上传会议文件 */ export const uploadMeetingFile = ( reviewMeetingId: number, reviewMeetingProjectId: number, file: File ) => { return uploadMeetingFileByPresignedUrl(reviewMeetingId, reviewMeetingProjectId, file) } /** 登记会议文件 */ export const registerMeetingFile = (data: ReviewMeetingFileRegisterReqVO) => request.post({ url: '/project/review-project/register-file', data }) /** 获取项目文件列表 */ export const getMeetingFileList = (reviewMeetingProjectId: number) => request.get({ url: '/project/review-project/file-list', params: { reviewMeetingProjectId } }) /** 删除会议文件 */ export const deleteMeetingFile = (id: number) => request.delete({ url: '/project/review-project/delete-file', params: { id } }) /** 获取结构化材料汇总 */ export const getProjectMaterialSummary = (reviewMeetingProjectId: number) => request.get({ url: '/project/review-project/material-summary', params: { reviewMeetingProjectId } }) /** 获取材料历史版本 */ export const getProjectMaterialHistory = (reviewMeetingProjectId: number, materialCode: string) => request.get({ url: '/project/review-project/material-history', params: { reviewMeetingProjectId, materialCode } }) /** 下载当前议程分类模板包 */ export const downloadProjectTemplateBundle = (agendaType: string) => request.download({ url: '/project/review-project/download-template-bundle', params: { agendaType } }) /** 上传结构化材料(预签名直传) */ export const uploadProjectMaterial = async ( reviewMeetingId: number, reviewMeetingProjectId: number, materialCode: string, file: File ) => { const presignedInfo = await FileApi.getFilePresignedUrl(file.name, 'review-meeting') await axios.put(presignedInfo.uploadUrl, file, { headers: { 'Content-Type': file.type || 'application/octet-stream' } }) await FileApi.createFile({ configId: presignedInfo.configId, url: presignedInfo.url, path: presignedInfo.path, name: file.name, type: file.type || 'application/octet-stream', size: file.size }) return request.post({ url: '/project/review-project/upload-material', data: { reviewMeetingId, reviewMeetingProjectId, materialCode, fileName: file.name, fileUrl: presignedInfo.url, fileSize: file.size, fileType: file.name.includes('.') ? file.name.split('.').pop()?.toLowerCase() : '' } satisfies ReviewMeetingMaterialUploadReqVO }) } const uploadMeetingFileByPresignedUrl = async ( reviewMeetingId: number, reviewMeetingProjectId: number, file: File ) => { const presignedInfo = await FileApi.getFilePresignedUrl(file.name, 'review-meeting') await axios.put(presignedInfo.uploadUrl, file, { headers: { 'Content-Type': file.type || 'application/octet-stream' } }) await FileApi.createFile({ configId: presignedInfo.configId, url: presignedInfo.url, path: presignedInfo.path, name: file.name, type: file.type || 'application/octet-stream', size: file.size }) return registerMeetingFile({ reviewMeetingId, reviewMeetingProjectId, fileName: file.name, fileUrl: presignedInfo.url, fileSize: file.size, fileType: file.name.includes('.') ? file.name.split('.').pop()?.toLowerCase() : '' }) }