afcd18c54f
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
209 lines
4.9 KiB
JavaScript
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
|
|
}
|
|
}
|