fix: 修复附件上传字段名不匹配及下载接口问题

- 修复前端附件字段名 filePath -> fileUrl(管理后台expense/requirement)
- 修复移动端附件上传字段名及预览逻辑(base64->URL)
- 修复后端FileController下载接口支持COS文件重定向
- 修复其他前端API路径问题
This commit is contained in:
zhangjf 2026-03-03 23:42:14 +08:00
parent cc6577b3df
commit d613eae423
16 changed files with 224 additions and 254 deletions

View File

@ -19,6 +19,7 @@ CREATE TABLE IF NOT EXISTS project (
project_code VARCHAR(64) NOT NULL COMMENT '项目编码',
project_name VARCHAR(128) NOT NULL COMMENT '项目名称',
customer_id VARCHAR(32) NOT NULL COMMENT '客户ID',
customer_name VARCHAR(128) COMMENT '客户名称',
project_type VARCHAR(32) NOT NULL COMMENT '项目类型',
budget_amount DECIMAL(18,2) COMMENT '预算金额',
start_date DATE COMMENT '开始日期',

View File

@ -13,20 +13,20 @@ export function createExpenseType(data: any) {
return request.post('/exp/expense-type', data)
}
export function updateExpenseType(id: number, data: any) {
export function updateExpenseType(id: string, data: any) {
return request.put(`/exp/expense-type/${id}`, data)
}
export function deleteExpenseType(id: number) {
export function deleteExpenseType(id: string) {
return request.delete(`/exp/expense-type/${id}`)
}
// 支出管理
export function getExpenseList(params: { pageNum: number; pageSize: number; title?: string; expenseType?: number; approvalStatus?: number; payStatus?: number }) {
export function getExpenseList(params: { pageNum: number; pageSize: number; title?: string; expenseType?: string; approvalStatus?: number; payStatus?: number }) {
return request.get('/exp/expense/page', { params })
}
export function getExpenseById(id: number) {
export function getExpenseById(id: string) {
return request.get(`/exp/expense/${id}`)
}
@ -34,49 +34,49 @@ export function createExpense(data: any) {
return request.post('/exp/expense', data)
}
export function updateExpense(id: number, data: any) {
export function updateExpense(id: string, data: any) {
return request.put(`/exp/expense/${id}`, data)
}
export function deleteExpense(id: number) {
export function deleteExpense(id: string) {
return request.delete(`/exp/expense/${id}`)
}
// 审批流程
export function submitExpense(id: number) {
export function submitExpense(id: string) {
return request.post(`/exp/expense/${id}/submit`)
}
export function withdrawExpense(id: number) {
export function withdrawExpense(id: string) {
return request.post(`/exp/expense/${id}/withdraw`)
}
export function approveExpense(id: number, comment: string) {
export function approveExpense(id: string, comment: string) {
return request.put(`/exp/expense/${id}/approve?comment=${encodeURIComponent(comment)}`)
}
export function rejectExpense(id: number, comment: string) {
export function rejectExpense(id: string, comment: string) {
return request.put(`/exp/expense/${id}/reject?comment=${encodeURIComponent(comment)}`)
}
export function confirmPayExpense(id: number, payChannel: string, payVoucher?: string) {
export function confirmPayExpense(id: string, payChannel: string, payVoucher?: string) {
return request.put(`/exp/expense/${id}/confirm-pay?payChannel=${payChannel}&payVoucher=${payVoucher || ''}`)
}
// 导出支出明细
export function exportExpense(params?: { title?: string; expenseType?: number; approvalStatus?: number; payStatus?: number }) {
export function exportExpense(params?: { title?: string; expenseType?: string; approvalStatus?: number; payStatus?: number }) {
const baseUrl = import.meta.env.VITE_API_URL || ''
const token = localStorage.getItem('token')
const tenantId = localStorage.getItem('tenantId') || '1'
const queryParams = new URLSearchParams()
if (params?.title) queryParams.append('title', params.title)
if (params?.expenseType) queryParams.append('expenseType', String(params.expenseType))
if (params?.expenseType) queryParams.append('expenseType', params.expenseType)
if (params?.approvalStatus !== undefined) queryParams.append('approvalStatus', String(params.approvalStatus))
if (params?.payStatus !== undefined) queryParams.append('payStatus', String(params.payStatus))
const queryString = queryParams.toString()
const url = `${baseUrl}/exp/expense/export${queryString ? '?' + queryString : ''}`
const url = `${baseUrl}/fund/exp/expense/export${queryString ? '?' + queryString : ''}`
return fetch(url, {
headers: {

View File

@ -1,11 +1,11 @@
import { request } from './request'
// 文件上传
export function uploadFile(file: File, businessType?: string, businessId?: number, description?: string) {
export function uploadFile(file: File, businessType?: string, businessId?: string, description?: string) {
const formData = new FormData()
formData.append('file', file)
if (businessType) formData.append('businessType', businessType)
if (businessId) formData.append('businessId', String(businessId))
if (businessId) formData.append('businessId', businessId)
if (description) formData.append('description', description)
return request.post('/file/upload', formData, {
@ -16,26 +16,26 @@ export function uploadFile(file: File, businessType?: string, businessId?: numbe
}
// 获取文件列表
export function getFileList(params: { pageNum: number; pageSize: number; businessType?: string; businessId?: number; fileType?: string }) {
export function getFileList(params: { pageNum: number; pageSize: number; businessType?: string; businessId?: string; fileType?: string }) {
return request.get('/file/page', { params })
}
// 根据业务查询文件
export function getFilesByBusiness(businessType: string, businessId: number) {
export function getFilesByBusiness(businessType: string, businessId: string) {
return request.get('/file/list', { params: { businessType, businessId } })
}
// 获取文件详情
export function getFileById(id: number) {
export function getFileById(id: string) {
return request.get(`/file/${id}`)
}
// 删除文件
export function deleteFile(id: number) {
export function deleteFile(id: string) {
return request.delete(`/file/${id}`)
}
// 获取文件下载URL
export function getFileDownloadUrl(filePath: string) {
return `/file/download/${filePath}`
return `/fund/file/download/${filePath}`
}

View File

@ -4,7 +4,7 @@ export function getProjectList(params: { pageNum: number; pageSize: number; proj
return request.get('/project/page', { params })
}
export function getProjectById(id: number) {
export function getProjectById(id: string) {
return request.get(`/project/${id}`)
}
@ -12,11 +12,11 @@ export function createProject(data: any) {
return request.post('/project', data)
}
export function updateProject(id: number, data: any) {
export function updateProject(id: string, data: any) {
return request.put(`/project/${id}`, data)
}
export function deleteProject(id: number) {
export function deleteProject(id: string) {
return request.delete(`/project/${id}`)
}
@ -25,7 +25,7 @@ export function getRequirementList(params: { pageNum: number; pageSize: number;
return request.get('/requirement/page', { params })
}
export function getRequirementById(id: number) {
export function getRequirementById(id: string) {
return request.get(`/requirement/${id}`)
}
@ -33,10 +33,10 @@ export function createRequirement(data: any) {
return request.post('/requirement', data)
}
export function updateRequirement(id: number, data: any) {
export function updateRequirement(id: string, data: any) {
return request.put(`/requirement/${id}`, data)
}
export function deleteRequirement(id: number) {
export function deleteRequirement(id: string) {
return request.delete(`/requirement/${id}`)
}

View File

@ -70,22 +70,18 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="微信" prop="wechat">
<el-input v-model="form.wechat" placeholder="请输入微信号" />
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="主联系人" prop="isPrimary">
<el-switch v-model="form.isPrimary" />
<el-switch v-model="form.isPrimary" :active-value="1" :inactive-value="0" />
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="form.remarks" type="textarea" :rows="3" placeholder="请输入备注" />
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-form>
@ -126,15 +122,15 @@ const dialogTitle = ref('新增联系人')
const formRef = ref<FormInstance>()
const form = reactive({
contactId: null as number | null,
id: null as string | null,
customerId: customerId.value,
contactName: '',
position: '',
phone: '',
email: '',
wechat: '',
isPrimary: false,
remarks: ''
isPrimary: 0,
status: 1,
remark: ''
})
const rules = reactive<FormRules>({
@ -210,8 +206,8 @@ const handleSubmit = async () => {
submitLoading.value = true
try {
if (form.contactId) {
await updateContact(form.contactId, form)
if (form.id) {
await updateContact(form.id, form)
ElMessage.success('更新成功')
} else {
await createContact(form)
@ -228,15 +224,15 @@ const handleSubmit = async () => {
}
const resetForm = () => {
form.contactId = null
form.id = null
form.customerId = customerId.value
form.contactName = ''
form.position = ''
form.phone = ''
form.email = ''
form.wechat = ''
form.isPrimary = false
form.remarks = ''
form.isPrimary = 0
form.status = 1
form.remark = ''
formRef.value?.clearValidate()
}

View File

@ -6,12 +6,12 @@
<el-input v-model="queryParams.title" placeholder="请输入支出标题" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="支出类型">
<el-select v-model="queryParams.expenseTypeId" placeholder="请选择" clearable filterable style="width: 150px">
<el-select v-model="queryParams.expenseType" placeholder="请选择" clearable filterable style="width: 150px">
<el-option
v-for="item in expenseTypeList"
:key="item.typeId"
:key="item.id"
:label="item.typeName"
:value="item.typeId"
:value="item.id"
/>
</el-select>
</el-form-item>
@ -44,7 +44,7 @@
</div>
<el-table :data="tableData" v-loading="loading" border stripe>
<el-table-column prop="expenseId" label="支出ID" width="80" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="title" label="支出标题" min-width="180" show-overflow-tooltip />
<el-table-column prop="expenseTypeName" label="支出类型" width="120" />
<el-table-column prop="projectName" label="关联项目" width="140" show-overflow-tooltip />
@ -57,20 +57,20 @@
<el-table-column prop="expenseDate" label="支出日期" width="120" />
<el-table-column prop="approvalStatus" label="审批状态" width="100">
<template #default="{ row }">
<el-tag v-if="row.approvalStatus === 'DRAFT'" type="info">草稿</el-tag>
<el-tag v-else-if="row.approvalStatus === 'PENDING'" type="warning">审批</el-tag>
<el-tag v-else-if="row.approvalStatus === 'APPROVED'" type="success">已通过</el-tag>
<el-tag v-else-if="row.approvalStatus === 'REJECTED'" type="danger">已拒绝</el-tag>
<el-tag v-if="row.approvalStatus === 0" type="info">待审批</el-tag>
<el-tag v-else-if="row.approvalStatus === 1" type="warning">审批</el-tag>
<el-tag v-else-if="row.approvalStatus === 2" type="success">已通过</el-tag>
<el-tag v-else-if="row.approvalStatus === 3" type="danger">已拒绝</el-tag>
<el-tag v-else type="info">已撤回</el-tag>
</template>
</el-table-column>
<el-table-column prop="payStatus" label="支付状态" width="100">
<template #default="{ row }">
<el-tag v-if="row.payStatus === 'UNPAID'" type="warning">未支付</el-tag>
<el-tag v-if="row.payStatus === 0" type="warning">未支付</el-tag>
<el-tag v-else type="success">已支付</el-tag>
</template>
</el-table-column>
<el-table-column prop="applicant" label="申请人" width="100" />
<el-table-column prop="createdBy" label="申请人" width="100" />
<el-table-column label="附件" width="80">
<template #default="{ row }">
<el-icon v-if="row.attachments" style="color: #409EFF;"><Document /></el-icon>
@ -80,10 +80,10 @@
<el-table-column label="操作" width="320" fixed="right">
<template #default="{ row }">
<el-button link type="primary" :icon="View" @click="handleView(row)">详情</el-button>
<el-button link type="primary" :icon="Edit" v-if="row.approvalStatus === 'DRAFT'" @click="handleEdit(row)">编辑</el-button>
<el-button link type="success" :icon="Check" v-if="row.approvalStatus === 'DRAFT'" @click="handleSubmit(row)">提交</el-button>
<el-button link type="warning" v-if="row.approvalStatus === 'PENDING'" @click="handleApprove(row)">审批</el-button>
<el-button link type="danger" :icon="Delete" v-if="row.approvalStatus === 'DRAFT'" @click="handleDelete(row)">删除</el-button>
<el-button link type="primary" :icon="Edit" v-if="row.approvalStatus === 0" @click="handleEdit(row)">编辑</el-button>
<el-button link type="success" :icon="Check" v-if="row.approvalStatus === 0" @click="handleSubmit(row)">提交</el-button>
<el-button link type="warning" v-if="row.approvalStatus === 1" @click="handleApprove(row)">审批</el-button>
<el-button link type="danger" :icon="Delete" v-if="row.approvalStatus === 0" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
@ -115,13 +115,13 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="支出类型" prop="expenseTypeId">
<el-select v-model="form.expenseTypeId" placeholder="请选择" filterable style="width: 100%">
<el-form-item label="支出类型" prop="expenseType">
<el-select v-model="form.expenseType" placeholder="请选择" filterable style="width: 100%">
<el-option
v-for="item in expenseTypeList"
:key="item.typeId"
:key="item.id"
:label="item.typeName"
:value="item.typeId"
:value="item.id"
/>
</el-select>
</el-form-item>
@ -134,9 +134,9 @@
<el-select v-model="form.projectId" placeholder="请选择项目" filterable clearable style="width: 100%">
<el-option
v-for="item in projectList"
:key="item.projectId"
:key="item.id"
:label="item.projectName"
:value="item.projectId"
:value="item.id"
/>
</el-select>
</el-form-item>
@ -156,22 +156,29 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="支出日期" prop="expenseDate">
<el-date-picker v-model="form.expenseDate" type="date" placeholder="请选择支出日期" style="width: 100%" />
<el-date-picker v-model="form.expenseDate" type="date" placeholder="请选择支出日期" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="申请人" prop="applicant">
<el-input v-model="form.applicant" placeholder="请输入申请人" />
<el-form-item label="关联项目">
<el-select v-model="form.projectId" placeholder="请选择项目" filterable clearable style="width: 100%">
<el-option
v-for="item in projectList"
:key="item.id"
:label="item.projectName"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="支出描述" prop="description">
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入支出描述" />
<el-form-item label="支出描述" prop="purpose">
<el-input v-model="form.purpose" type="textarea" :rows="3" placeholder="请输入支出描述" />
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="form.remarks" type="textarea" :rows="2" placeholder="请输入备注" />
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="附件">
@ -286,7 +293,7 @@ import { getExpenseTypeTree } from '@/api/expense'
import { getProjectList } from '@/api/project'
//
const uploadUrl = '/file/api/v1/file/upload'
const uploadUrl = '/fund/file/upload'
const uploadHeaders = {
Authorization: `Bearer ${localStorage.getItem('token') || ''}`,
'X-Tenant-Id': localStorage.getItem('tenantId') || '1'
@ -305,7 +312,7 @@ const queryParams = reactive({
pageNum: 1,
pageSize: 10,
title: '',
expenseTypeId: null as number | null,
expenseType: null as string | null,
approvalStatus: '',
payStatus: ''
})
@ -315,32 +322,30 @@ const dialogTitle = ref('新增支出')
const formRef = ref<FormInstance>()
const form = reactive({
expenseId: null as number | null,
id: null as string | null,
title: '',
expenseTypeId: null as number | null,
projectId: null as number | null,
expenseType: null as string | null,
projectId: null as string | null,
amount: 0,
payeeName: '',
expenseDate: '',
applicant: '',
description: '',
remarks: '',
purpose: '',
remark: '',
attachments: ''
})
const rules = reactive<FormRules>({
title: [{ required: true, message: '请输入支出标题', trigger: 'blur' }],
expenseTypeId: [{ required: true, message: '请选择支出类型', trigger: 'change' }],
expenseType: [{ required: true, message: '请选择支出类型', trigger: 'change' }],
amount: [{ required: true, message: '请输入支出金额', trigger: 'blur' }],
payeeName: [{ required: true, message: '请输入收款单位', trigger: 'blur' }],
expenseDate: [{ required: true, message: '请选择支出日期', trigger: 'change' }],
applicant: [{ required: true, message: '请输入申请人', trigger: 'blur' }]
expenseDate: [{ required: true, message: '请选择支出日期', trigger: 'change' }]
})
const approvalVisible = ref(false)
const approvalData = ref<any>({})
const approvalForm = reactive({
expenseId: null as number | null,
expenseId: null as string | null,
comment: ''
})
@ -351,8 +356,8 @@ const detailData = ref<any>({})
const handleUploadSuccess = (response: any, _file: any, fileListVal: any[]) => {
if (response.code === 200 || response.code === 0) {
const urls = fileListVal
.filter(f => f.response?.data?.filePath || f.url)
.map(f => f.response?.data?.filePath || f.url)
.filter(f => f.response?.data?.fileUrl || f.url)
.map(f => f.response?.data?.fileUrl || f.url)
form.attachments = urls.join(',')
ElMessage.success('上传成功')
} else {
@ -368,8 +373,8 @@ const handleUploadError = () => {
//
const handleUploadRemove = (_file: any, fileListVal: any[]) => {
const urls = fileListVal
.filter(f => f.response?.data?.filePath || f.url)
.map(f => f.response?.data?.filePath || f.url)
.filter(f => f.response?.data?.fileUrl || f.url)
.map(f => f.response?.data?.fileUrl || f.url)
form.attachments = urls.join(',')
}
@ -377,7 +382,7 @@ const handleUploadRemove = (_file: any, fileListVal: any[]) => {
const getFileUrl = (path: string) => {
if (!path) return '#'
if (path.startsWith('http')) return path
return `/file/api/v1/file/download/${path}`
return `/fund/file/download/${path}`
}
//
@ -434,7 +439,7 @@ const handleSearch = () => {
const handleReset = () => {
queryParams.title = ''
queryParams.expenseTypeId = null
queryParams.expenseType = null
queryParams.approvalStatus = ''
queryParams.payStatus = ''
queryParams.pageNum = 1
@ -474,7 +479,7 @@ const handleSubmit = (row: any) => {
type: 'warning'
}).then(async () => {
try {
await submitExpense(row.expenseId)
await submitExpense(row.id)
ElMessage.success('提交成功')
fetchData()
} catch (e) {
@ -485,7 +490,7 @@ const handleSubmit = (row: any) => {
const handleApprove = (row: any) => {
approvalData.value = row
approvalForm.expenseId = row.expenseId
approvalForm.expenseId = row.id
approvalForm.comment = ''
approvalVisible.value = true
}
@ -523,7 +528,7 @@ const handleDelete = (row: any) => {
type: 'warning'
}).then(async () => {
try {
await deleteExpense(row.expenseId)
await deleteExpense(row.id)
ElMessage.success('删除成功')
fetchData()
} catch (e) {
@ -539,8 +544,8 @@ const handleSaveExpense = async () => {
submitLoading.value = true
try {
if (form.expenseId) {
await updateExpense(form.expenseId, form)
if (form.id) {
await updateExpense(form.id, form)
ElMessage.success('更新成功')
} else {
await createExpense(form)
@ -557,16 +562,15 @@ const handleSaveExpense = async () => {
}
const resetForm = () => {
form.expenseId = null
form.id = null
form.title = ''
form.expenseTypeId = null
form.expenseType = null
form.projectId = null
form.amount = 0
form.payeeName = ''
form.expenseDate = ''
form.applicant = ''
form.description = ''
form.remarks = ''
form.purpose = ''
form.remark = ''
form.attachments = ''
fileList.value = []
formRef.value?.clearValidate()
@ -578,7 +582,7 @@ const handleExport = async () => {
try {
const params: any = {}
if (queryParams.title) params.title = queryParams.title
if (queryParams.expenseTypeId) params.expenseType = queryParams.expenseTypeId
if (queryParams.expenseType) params.expenseType = queryParams.expenseType
if (queryParams.approvalStatus) params.approvalStatus = parseInt(queryParams.approvalStatus)
if (queryParams.payStatus) params.payStatus = parseInt(queryParams.payStatus)

View File

@ -105,8 +105,13 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同金额" prop="contractAmount">
<el-input-number v-model="form.contractAmount" :precision="2" :min="0" style="width: 100%" />
<el-form-item label="项目类型" prop="projectType">
<el-select v-model="form.projectType" placeholder="请选择" style="width: 100%">
<el-option label="开发项目" value="开发项目" />
<el-option label="运维项目" value="运维项目" />
<el-option label="咨询项目" value="咨询项目" />
<el-option label="集成项目" value="集成项目" />
</el-select>
</el-form-item>
</el-col>
</el-row>
@ -131,23 +136,14 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目状态" prop="projectStatus">
<el-select v-model="form.projectStatus" placeholder="请选择" style="width: 100%">
<el-option label="待启动" value="PENDING" />
<el-option label="进行中" value="IN_PROGRESS" />
<el-option label="已完成" value="COMPLETED" />
<el-option label="已取消" value="CANCELLED" />
</el-select>
<el-form-item label="预算金额" prop="budgetAmount">
<el-input-number v-model="form.budgetAmount" :precision="2" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="项目描述" prop="description">
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入项目描述" />
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="form.remarks" type="textarea" :rows="2" placeholder="请输入备注" />
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-form>
@ -210,26 +206,23 @@ const dialogTitle = ref('新增项目')
const formRef = ref<FormInstance>()
const form = reactive({
projectId: null as number | null,
id: null as string | null,
projectCode: '',
projectName: '',
customerId: null as number | null,
contractAmount: 0,
customerId: null as string | null,
projectType: '',
budgetAmount: 0,
startDate: '',
endDate: '',
projectManager: '',
projectStatus: 'PENDING',
description: '',
remarks: ''
remark: ''
})
const rules = reactive<FormRules>({
projectCode: [{ required: true, message: '请输入项目编号', trigger: 'blur' }],
projectName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
customerId: [{ required: true, message: '请选择客户', trigger: 'change' }],
contractAmount: [{ required: true, message: '请输入合同金额', trigger: 'blur' }],
startDate: [{ required: true, message: '请选择开始日期', trigger: 'change' }],
projectStatus: [{ required: true, message: '请选择项目状态', trigger: 'change' }]
projectType: [{ required: true, message: '请选择项目类型', trigger: 'change' }]
})
const detailVisible = ref(false)
@ -310,8 +303,8 @@ const handleSubmit = async () => {
submitLoading.value = true
try {
if (form.projectId) {
await updateProject(form.projectId, form)
if (form.id) {
await updateProject(form.id, form)
ElMessage.success('更新成功')
} else {
await createProject(form)
@ -328,17 +321,16 @@ const handleSubmit = async () => {
}
const resetForm = () => {
form.projectId = null
form.id = null
form.projectCode = ''
form.projectName = ''
form.customerId = null
form.contractAmount = 0
form.projectType = ''
form.budgetAmount = 0
form.startDate = ''
form.endDate = ''
form.projectManager = ''
form.projectStatus = 'PENDING'
form.description = ''
form.remarks = ''
form.remark = ''
formRef.value?.clearValidate()
}

View File

@ -312,7 +312,7 @@ import { getProjectList } from '@/api/project'
import { getCustomerList } from '@/api/customer'
//
const uploadUrl = '/file/api/v1/file/upload'
const uploadUrl = '/fund/file/upload'
const uploadHeaders = {
Authorization: `Bearer ${localStorage.getItem('token') || ''}`,
'X-Tenant-Id': localStorage.getItem('tenantId') || '1'
@ -380,8 +380,8 @@ const handleUploadSuccess = (response: any, _file: any, fileListVal: any[]) => {
if (response.code === 200 || response.code === 0) {
// URL
const urls = fileListVal
.filter(f => f.response?.data?.filePath || f.url)
.map(f => f.response?.data?.filePath || f.url)
.filter(f => f.response?.data?.fileUrl || f.url)
.map(f => f.response?.data?.fileUrl || f.url)
form.attachmentUrl = urls.join(',')
ElMessage.success('上传成功')
} else {
@ -397,8 +397,8 @@ const handleUploadError = () => {
//
const handleUploadRemove = (_file: any, fileListVal: any[]) => {
const urls = fileListVal
.filter(f => f.response?.data?.filePath || f.url)
.map(f => f.response?.data?.filePath || f.url)
.filter(f => f.response?.data?.fileUrl || f.url)
.map(f => f.response?.data?.fileUrl || f.url)
form.attachmentUrl = urls.join(',')
}
@ -406,7 +406,7 @@ const handleUploadRemove = (_file: any, fileListVal: any[]) => {
const getFileUrl = (path: string) => {
if (!path) return '#'
if (path.startsWith('http')) return path
return `/file/api/v1/file/download/${path}`
return `/fund/file/download/${path}`
}
const fetchCustomers = async () => {

View File

@ -92,8 +92,8 @@
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="应收款编号" prop="receivableCode">
<el-input v-model="form.receivableCode" placeholder="请输入应收款编号" />
<el-form-item label="应收金额" prop="receivableAmount">
<el-input-number v-model="form.receivableAmount" :precision="2" :min="0" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
@ -124,35 +124,19 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="应收金额" prop="totalAmount">
<el-input-number v-model="form.totalAmount" :precision="2" :min="0" style="width: 100%" />
<el-form-item label="应收日期" prop="receivableDate">
<el-date-picker v-model="form.receivableDate" type="date" placeholder="请选择应收日期" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="到期日期" prop="paymentDueDate">
<el-date-picker v-model="form.paymentDueDate" type="date" placeholder="请选择到期日期" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="应收日期" prop="dueDate">
<el-date-picker v-model="form.dueDate" type="date" placeholder="请选择应收日期" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="收款状态" prop="receiptStatus">
<el-select v-model="form.receiptStatus" placeholder="请选择" style="width: 100%">
<el-option label="未收款" value="UNPAID" />
<el-option label="部分收款" value="PARTIAL" />
<el-option label="已收款" value="PAID" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="账单描述" prop="description">
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="请输入账单描述" />
</el-form-item>
<el-form-item label="备注" prop="remarks">
<el-input v-model="form.remarks" type="textarea" :rows="2" placeholder="请输入备注" />
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-form>
@ -298,23 +282,22 @@ const dialogTitle = ref('新增应收款')
const formRef = ref<FormInstance>()
const form = reactive({
receivableId: null as number | null,
receivableCode: '',
projectId: null as number | null,
customerId: null as number | null,
totalAmount: 0,
dueDate: '',
receiptStatus: 'UNPAID',
description: '',
remarks: ''
id: null as string | null,
projectId: null as string | null,
customerId: null as string | null,
receivableAmount: 0,
receivableDate: '',
paymentDueDate: '',
paymentMethod: '',
bankAccount: '',
remark: ''
})
const rules = reactive<FormRules>({
receivableCode: [{ required: true, message: '请输入应收款编号', trigger: 'blur' }],
projectId: [{ required: true, message: '请选择关联项目', trigger: 'change' }],
customerId: [{ required: true, message: '请选择客户', trigger: 'change' }],
totalAmount: [{ required: true, message: '请输入应收金额', trigger: 'blur' }],
dueDate: [{ required: true, message: '请选择应收日期', trigger: 'change' }]
receivableAmount: [{ required: true, message: '请输入应收金额', trigger: 'blur' }],
receivableDate: [{ required: true, message: '请选择应收日期', trigger: 'change' }]
})
const receiptVisible = ref(false)
@ -473,8 +456,8 @@ const handleSubmit = async () => {
submitLoading.value = true
try {
if (form.receivableId) {
await updateReceivable(form.receivableId, form)
if (form.id) {
await updateReceivable(form.id, form)
ElMessage.success('更新成功')
} else {
await createReceivable(form)
@ -491,15 +474,15 @@ const handleSubmit = async () => {
}
const resetForm = () => {
form.receivableId = null
form.receivableCode = ''
form.id = null
form.projectId = null
form.customerId = null
form.totalAmount = 0
form.dueDate = ''
form.receiptStatus = 'UNPAID'
form.description = ''
form.remarks = ''
form.receivableAmount = 0
form.receivableDate = ''
form.paymentDueDate = ''
form.paymentMethod = ''
form.bankAccount = ''
form.remark = ''
formRef.value?.clearValidate()
}

View File

@ -20,41 +20,11 @@ export default defineConfig(({ mode }) => {
server: {
port: 3000,
proxy: {
'/auth/': {
target: 'http://localhost:8100',
changeOrigin: true
},
'/sys/': {
// 所有 /fund/ 请求代理到网关,去掉 /fund 前缀
'/fund/': {
target: 'http://localhost:8000',
changeOrigin: true
},
'/cust/': {
target: 'http://localhost:8000',
changeOrigin: true
},
'/proj/': {
target: 'http://localhost:8000',
changeOrigin: true
},
'/req/': {
target: 'http://localhost:8000',
changeOrigin: true
},
'/exp/': {
target: 'http://localhost:8000',
changeOrigin: true
},
'/receipt/': {
target: 'http://localhost:8000',
changeOrigin: true
},
'/file/': {
target: 'http://localhost:8000',
changeOrigin: true
},
'/report/': {
target: 'http://localhost:8000',
changeOrigin: true
changeOrigin: true,
rewrite: (path) => path.replace(/^\/fund/, '')
}
}
}

View File

@ -6,6 +6,7 @@ import com.fundplatform.file.data.entity.FileRecord;
import com.fundplatform.file.data.service.FileRecordService;
import com.fundplatform.file.service.CosStorageService;
import com.fundplatform.file.vo.FileRecordVO;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -151,25 +152,43 @@ public class FileController {
/**
* 下载文件
* 支持本地文件和COS文件下载
*/
@GetMapping("/download/**")
public void download(HttpServletResponse response,
HttpServletRequest httpRequest,
@RequestHeader(value = "X-Tenant-Id", required = false) String tenantId) throws IOException {
String requestUri = request.getRequestURI();
String requestUri = httpRequest.getRequestURI();
String filePath = requestUri.substring(requestUri.indexOf("/download/") + 10);
log.info("文件下载请求: filePath={}", filePath);
// 检查是否是COS路径
if (filePath.startsWith("cos:")) {
// COS文件重定向到COS URL
String cosUrl = filePath.substring(4);
response.sendRedirect(cosUrl);
return;
}
// 检查是否已经是完整URL
if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
response.sendRedirect(filePath);
return;
}
// 本地文件
String fullPath = uploadPath + "/" + filePath;
File file = new File(fullPath);
if (!file.exists()) {
log.warn("文件不存在: {}", fullPath);
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":404,\"message\":\"文件不存在\"}");
return;
}
// 查找文件记录
// 简化处理直接下载
String fileName = file.getName();
// 尝试还原原始文件名如果有扩展名的话
response.setContentType("application/octet-stream");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + URLEncoder.encode(fileName, StandardCharsets.UTF_8) + "\"");
@ -186,10 +205,6 @@ public class FileController {
}
}
// 注入HttpServletRequest
@jakarta.annotation.Resource
private jakarta.servlet.http.HttpServletRequest request;
/**
* 根据ID获取文件信息
*/

View File

@ -132,7 +132,7 @@ const onAfterRead = async (file: any) => {
const res: any = await uploadFile(file.file, 'expense', undefined, '支出附件')
//
const filePath = res.data?.filePath || res.data?.url
const filePath = res.data?.fileUrl || res.data?.url
if (filePath) {
uploadedAttachments.value.push(filePath)
console.log('文件上传成功:', filePath)

View File

@ -104,9 +104,18 @@ const getAttachmentCount = (attachments: string) => {
const previewAttachments = (item: any) => {
if (!item.attachments) return
// base64 URL
const attachmentList = item.attachments.split(',')
const imageUrls = attachmentList.map((b64: string) => `data:image/jpeg;base64,${b64}`)
// URL
const attachmentList = item.attachments.split(',').filter((s: string) => s.trim())
// URL
const imageUrls = attachmentList.map((url: string) => {
// URL使
if (url.startsWith('http')) return url
// COS
if (url.startsWith('cos:')) return url.substring(4)
// 使
return `/fund/file/download/${url}`
})
ImagePreview.show({
images: imageUrls,

View File

@ -189,7 +189,7 @@ const onSubmit = async () => {
projectId: form.value.projectId,
receivableAmount: parseFloat(form.value.receivableAmount),
receivableDate: form.value.receivableDate,
description: form.value.description
remark: form.value.description
})
showSuccessToast('提交成功')
router.back()

View File

@ -19,7 +19,7 @@ public class FundReceiptController {
}
@PostMapping
public Result<Long> create(@Valid @RequestBody FundReceiptDTO dto) {
public Result<String> create(@Valid @RequestBody FundReceiptDTO dto) {
return Result.success(receiptService.createReceipt(dto));
}
@ -29,7 +29,7 @@ public class FundReceiptController {
}
@GetMapping("/{id}")
public Result<FundReceiptVO> getById(@PathVariable Long id) {
public Result<FundReceiptVO> getById(@PathVariable String id) {
return Result.success(receiptService.getReceiptById(id));
}
@ -44,17 +44,17 @@ public class FundReceiptController {
}
@DeleteMapping("/{id}")
public Result<Boolean> delete(@PathVariable Long id) {
public Result<Boolean> delete(@PathVariable String id) {
return Result.success(receiptService.deleteReceipt(id));
}
@PutMapping("/{id}/confirm")
public Result<Boolean> confirm(@PathVariable Long id) {
public Result<Boolean> confirm(@PathVariable String id) {
return Result.success(receiptService.confirm(id));
}
@PutMapping("/{id}/write-off")
public Result<Boolean> writeOff(@PathVariable Long id) {
public Result<Boolean> writeOff(@PathVariable String id) {
return Result.success(receiptService.writeOff(id));
}
}

View File

@ -39,7 +39,7 @@ public class ReceivableController {
* 创建应收款
*/
@PostMapping
public Result<Long> create(@Valid @RequestBody ReceivableDTO dto) {
public Result<String> create(@Valid @RequestBody ReceivableDTO dto) {
return Result.success(receivableService.createReceivable(dto));
}
@ -47,7 +47,7 @@ public class ReceivableController {
* 更新应收款仅待确认状态可修改
*/
@PutMapping("/{id}")
public Result<Boolean> update(@PathVariable Long id, @Valid @RequestBody ReceivableDTO dto) {
public Result<Boolean> update(@PathVariable String id, @Valid @RequestBody ReceivableDTO dto) {
dto.setId(id);
return Result.success(receivableService.updateReceivable(dto));
}
@ -56,7 +56,7 @@ public class ReceivableController {
* 根据ID查询应收款
*/
@GetMapping("/{id}")
public Result<ReceivableVO> getById(@PathVariable Long id) {
public Result<ReceivableVO> getById(@PathVariable String id) {
return Result.success(receivableService.getReceivableById(id));
}
@ -67,8 +67,8 @@ public class ReceivableController {
public Result<Page<ReceivableVO>> page(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) Long projectId,
@RequestParam(required = false) Long customerId,
@RequestParam(required = false) String projectId,
@RequestParam(required = false) String customerId,
@RequestParam(required = false) String status,
@RequestParam(required = false) Integer confirmStatus) {
return Result.success(receivableService.pageReceivables(pageNum, pageSize, projectId, customerId, status, confirmStatus));
@ -79,8 +79,8 @@ public class ReceivableController {
*/
@PutMapping("/{id}/confirm")
public Result<Boolean> confirm(
@PathVariable Long id,
@RequestHeader(value = "X-User-Id", required = false) Long confirmBy) {
@PathVariable String id,
@RequestHeader(value = "X-User-Id", required = false) String confirmBy) {
return Result.success(receivableService.confirmReceivable(id, confirmBy));
}
@ -88,7 +88,7 @@ public class ReceivableController {
* 取消确认
*/
@PutMapping("/{id}/cancel-confirm")
public Result<Boolean> cancelConfirm(@PathVariable Long id) {
public Result<Boolean> cancelConfirm(@PathVariable String id) {
return Result.success(receivableService.cancelConfirm(id));
}
@ -96,7 +96,7 @@ public class ReceivableController {
* 记录收款
*/
@PostMapping("/{id}/receipt")
public Result<Boolean> recordReceipt(@PathVariable Long id, @RequestParam BigDecimal amount) {
public Result<Boolean> recordReceipt(@PathVariable String id, @RequestParam BigDecimal amount) {
return Result.success(receivableService.recordReceipt(id, amount));
}
@ -104,7 +104,7 @@ public class ReceivableController {
* 获取应收款的收款记录列表
*/
@GetMapping("/{id}/receipts")
public Result<List<FundReceiptVO>> getReceipts(@PathVariable Long id) {
public Result<List<FundReceiptVO>> getReceipts(@PathVariable String id) {
return Result.success(receivableService.getReceiptsByReceivableId(id));
}
@ -121,7 +121,7 @@ public class ReceivableController {
* 删除应收款仅待确认状态可删除
*/
@DeleteMapping("/{id}")
public Result<Boolean> delete(@PathVariable Long id) {
public Result<Boolean> delete(@PathVariable String id) {
return Result.success(receivableService.deleteReceivable(id));
}
@ -166,8 +166,8 @@ public class ReceivableController {
*/
@GetMapping("/export")
public void exportExcel(
@RequestParam(required = false) Long projectId,
@RequestParam(required = false) Long customerId,
@RequestParam(required = false) String projectId,
@RequestParam(required = false) String customerId,
@RequestParam(required = false) String status,
@RequestParam(required = false) Integer confirmStatus,
HttpServletResponse response) {