Files
daily_publish/src/composables/useApi.js
T
panda afcd18c54f fix: evaluation report P0/P1/P2 fixes, remove Docker, add upload UI
Backend:
- Add NotFoundException + BusinessException, return correct HTTP status (404/400)
- Add @Index on reports.project_id and reports.upload_time
- Add fileSize column to reports, populate on upload, return in DTO
- Cascade delete: deleting project now removes all reports (DB + files + PDFs)
- Delete report: also clean up pre-rendered PDF
- File upload MIME validation (extension + Content-Type)
- Remove duplicate @ExceptionHandler from ReportController
- Switch from System.err to SLF4J logger
- Handle MethodArgumentNotValid, MissingServletRequestPart, etc.

Frontend:
- Remove all Docker files (project uses 宝塔 panel deployment)
- Upgrade axios 1.6.8 -> 1.7.7 (CVE-2024-39338)
- Remove unused @vue-office/pptx + vue-demi (see CHANGELOG for rationale)
- Fix vite proxy port 37821 -> 30081
- Remove mock data fallback in production
- Add upload report UI (button + modal in ProjectDetail)
- Add create project UI (button + modal in ProjectList)
- Add filename search box in ProjectDetail
- New useApi methods: createProject, uploadReport, deleteProject, deleteReport
- FilePreview/ReportCard: show fileSize (was undefined before)

Docs:
- Add README.md (overview, quick start, structure)
- Add CHANGELOG.md (full change log + pptx removal rationale)
- Include EVALUATION_REPORT.md and blog-vibe-coding.md

Tests:
- All 73 backend tests pass
- All 43 frontend tests pass
- Updated test fixtures for new API contract
2026-06-01 21:35:13 +08:00

209 lines
4.9 KiB
JavaScript

import axios from 'axios'
import { ref } from 'vue'
const api = axios.create({
baseURL: '/api',
timeout: 30000
})
export function useApi() {
const loading = ref(false)
const error = ref(null)
const handleError = (e, fallback = null) => {
const status = e?.response?.status
const message = e?.response?.data?.error || e?.message || 'Request failed'
error.value = { status, message }
console.error(`API error [${status}]:`, message)
if (fallback !== null) return fallback
throw e
}
// ============== Projects ==============
const fetchProjects = async () => {
loading.value = true
error.value = null
try {
const response = await api.get('/projects')
return response.data
} catch (e) {
handleError(e, [])
return []
} finally {
loading.value = false
}
}
const fetchProject = async (id) => {
loading.value = true
error.value = null
try {
const response = await api.get(`/projects/${id}`)
return response.data
} catch (e) {
handleError(e, null)
return null
} finally {
loading.value = false
}
}
const createProject = async (data) => {
loading.value = true
error.value = null
try {
const response = await api.post('/projects', data)
return response.data
} catch (e) {
handleError(e, null)
return null
} finally {
loading.value = false
}
}
const updateProject = async (id, data) => {
loading.value = true
error.value = null
try {
let response
if (data instanceof FormData) {
response = await api.put(`/projects/${id}`, data, {
headers: { 'Content-Type': 'multipart/form-data' }
})
} else {
// Wrap in FormData for multipart backend endpoint
const formData = new FormData()
if (data.name != null) formData.append('name', data.name)
if (data.description != null) formData.append('description', data.description)
response = await api.put(`/projects/${id}`, formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
return response.data
} catch (e) {
handleError(e, null)
return null
} finally {
loading.value = false
}
}
const deleteProject = async (id) => {
loading.value = true
error.value = null
try {
await api.delete(`/projects/${id}`)
return true
} catch (e) {
handleError(e, false)
return false
} finally {
loading.value = false
}
}
// ============== Reports ==============
const fetchReports = async (projectId) => {
loading.value = true
error.value = null
try {
const response = await api.get(`/reports`, {
params: projectId ? { projectId } : {}
})
return response.data
} catch (e) {
handleError(e, [])
return []
} finally {
loading.value = false
}
}
const fetchReportContent = async (reportId) => {
loading.value = true
error.value = null
try {
const response = await api.get(`/reports/${reportId}`)
return { content: response.data.fileContent, type: response.data.fileType }
} catch (e) {
handleError(e, null)
return null
} finally {
loading.value = false
}
}
const fetchReportBytes = async (reportId) => {
try {
const response = await api.get(`/reports/${reportId}/download`, { responseType: 'arraybuffer' })
return response.data
} catch (e) {
handleError(e, null)
return null
}
}
const fetchReportPdf = async (reportId) => {
try {
const response = await api.get(`/reports/${reportId}/pdf`, { responseType: 'arraybuffer' })
return new Blob([response.data], { type: 'application/pdf' })
} catch (e) {
handleError(e, null)
return null
}
}
const uploadReport = async (file, projectId, fileType) => {
loading.value = true
error.value = null
try {
const formData = new FormData()
formData.append('file', file)
formData.append('projectId', projectId)
formData.append('fileType', fileType)
const response = await api.post('/reports', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
return response.data
} catch (e) {
handleError(e, null)
return null
} finally {
loading.value = false
}
}
const deleteReport = async (id) => {
loading.value = true
error.value = null
try {
await api.delete(`/reports/${id}`)
return true
} catch (e) {
handleError(e, false)
return false
} finally {
loading.value = false
}
}
return {
loading,
error,
// projects
fetchProjects,
fetchProject,
createProject,
updateProject,
deleteProject,
// reports
fetchReports,
fetchReportContent,
fetchReportBytes,
fetchReportPdf,
uploadReport,
deleteReport
}
}