feat: 接入资产文件上传服务

This commit is contained in:
zxr
2026-08-04 01:02:40 +08:00
parent 7353c4eede
commit a9c2a499ce
5 changed files with 115 additions and 108 deletions

View File

@@ -1,24 +0,0 @@
import { AxiosProgressEvent } from 'axios'
import { request } from '@/api/request'
/** 上传文件 */
const FtsUpload = (data: FormData, onUploadProgress?: (progress: number) => void) => {
data.append('provider', 'local')
data.append('bucket', 'visual')
return request.post(`/Assets/v1/fts/uploader`, data, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: onUploadProgress
? (progressEvent: AxiosProgressEvent) => {
if (progressEvent.total) {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
onUploadProgress(percentCompleted)
}
}
: undefined,
})
}
export default FtsUpload

View File

@@ -1,5 +1,28 @@
import { request } from '@/api/request'
export interface Floor {
id?: number
name?: string
datacenter_id?: number
floor_number?: number
status?: string
area?: number
height?: number
load_bearing?: number
enabled?: boolean
layout_plan?: string
layout_plan_file_id?: string
description?: string
}
export type SaveFloorPayload = Omit<Floor, 'layout_plan'>
interface ApiResponse<T> {
code: number
message: string
details: T
}
/** 获取楼层列表(分页) */
export const fetchFloorList = (data?: { page?: number; page_size?: number; keyword?: string; datacenter_id?: number; status?: string }) => {
return request.post('/Assets/v1/floor/list', data || {})
@@ -16,13 +39,13 @@ export const fetchFloorListByDatacenter = (datacenterId: number, params?: { name
}
/** 创建楼层 */
export const createFloor = (data: any) => {
return request.post('/Assets/v1/floor/create', data)
export const createFloor = (data: SaveFloorPayload) => {
return request.post<ApiResponse<Floor>>('/Assets/v1/floor/create', data)
}
/** 更新楼层 */
export const updateFloor = (data: any) => {
return request.put('/Assets/v1/floor/update', data)
export const updateFloor = (data: SaveFloorPayload) => {
return request.put<ApiResponse<Floor>>('/Assets/v1/floor/update', data)
}
/** 删除楼层 */

48
src/api/ops/floorFile.ts Normal file
View File

@@ -0,0 +1,48 @@
import { request } from '@/api/request'
export interface PendingUpload {
file_id: string
object_key: string
upload: {
method: string
url: string
headers: Record<string, string>
expires_at: string
}
}
interface ApiResponse<T> {
code: number
message: string
details: T
}
/** 初始化楼层布局文件并直传 OSS。 */
export async function uploadFloorLayout(file: File): Promise<PendingUpload> {
const response = await request.post<ApiResponse<PendingUpload>>('/Assets/v1/floor/layout/init', {
filename: file.name,
size: file.size,
content_type: file.type || 'application/octet-stream',
})
if (response.code !== 0) {
throw new Error(response.message || '初始化楼层布局文件上传失败')
}
const pendingUpload = response.details
if (!pendingUpload?.file_id || !pendingUpload.upload?.method || !pendingUpload.upload?.url) {
throw new Error('楼层布局文件上传参数不完整')
}
const uploadResponse = await fetch(pendingUpload.upload.url, {
method: pendingUpload.upload.method,
headers: pendingUpload.upload.headers,
body: file,
credentials: 'omit',
})
if (!uploadResponse.ok) {
throw new Error(`上传楼层布局文件失败: HTTP ${uploadResponse.status}`)
}
return pendingUpload
}

View File

@@ -23,17 +23,6 @@ export interface GoViewProjectData {
createUserId: string
}
export interface GoViewFile {
id: string
fileName: string
fileSize: number
fileSuffix: string
virtualKey: string
relativePath: string
absolutePath: string
createTime: string
}
export interface OssInfo {
BucketName: string
bucketURL: string
@@ -106,15 +95,6 @@ export const publishProject = (data: { identity: string; state: -1 | 1 }) => {
return request.put<GoViewResponse<{ identity: string; state: number }>>('/Visual/v1/project/publish', data)
}
/** 上传文件 */
export const uploadFile = (file: File) => {
const formData = new FormData()
formData.append('object', file)
return request.post<GoViewResponse<GoViewFile>>('/Visual/v1/project/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
}
/** 保存项目数据 */
export const saveProjectData = (projectId: string, content: string) => {
const formData = new URLSearchParams()

View File

@@ -60,12 +60,6 @@
list-type="picture-card"
accept="image/*"
>
<!-- <template #upload-button>
<div class="upload-btn">
<icon-plus />
<div class="upload-text">上传图片</div>
</div>
</template> -->
</a-upload>
</a-form-item>
@@ -77,27 +71,11 @@
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from 'vue'
import { computed } from 'vue'
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { Message } from '@arco-design/web-vue'
import { IconPlus } from '@arco-design/web-vue/es/icon'
import { createFloor, updateFloor } from '@/api/ops/floor'
import { fetchDatacenterList } from '@/api/ops/floor'
import FtsUpload from '@/api/common/fts'
interface Floor {
id?: number
name?: string
datacenter_id?: number
floor_number?: number
status?: string
area?: number
height?: number
load_bearing?: number
enabled?: boolean
layout_plan?: string
description?: string
}
import { createFloor, fetchDatacenterList, updateFloor } from '@/api/ops/floor'
import type { Floor, SaveFloorPayload } from '@/api/ops/floor'
import { uploadFloorLayout } from '@/api/ops/floorFile'
interface Props {
visible: boolean
@@ -117,6 +95,7 @@ const loadingDatacenters = ref(false)
const submitting = ref(false)
const datacenterList = ref<any[]>([])
const fileList = ref<any[]>([])
const localPreviewURL = ref('')
// 表单数据
const form = ref({
@@ -129,12 +108,20 @@ const form = ref({
load_bearing: undefined as number | undefined,
enabled: true,
layout_plan: '',
layout_plan_file_id: '',
description: '',
})
// 是否为编辑模式
const isEdit = computed(() => !!props.floor?.id)
const releaseLocalPreviewURL = () => {
if (localPreviewURL.value) {
URL.revokeObjectURL(localPreviewURL.value)
localPreviewURL.value = ''
}
}
// 加载数据中心列表
const loadDatacenterList = async () => {
loadingDatacenters.value = true
@@ -153,22 +140,16 @@ const loadDatacenterList = async () => {
// 处理文件上传
const handleUpload = async (option: any) => {
try {
const file = option.fileItem.file
const formData = new FormData()
formData.append('file', file)
const file = option.fileItem.file as File
const pendingUpload = await uploadFloorLayout(file)
// 使用fts接口上传文件
const res: any = await FtsUpload(formData)
if (res.code === 0) {
// 上传成功设置文件URL
form.value.layout_plan = res.data?.result_url || ''
option.onSuccess(res)
Message.success('上传成功')
} else {
option.onError(new Error('上传失败'))
Message.error('上传失败')
}
releaseLocalPreviewURL()
localPreviewURL.value = URL.createObjectURL(file)
option.fileItem.url = localPreviewURL.value
form.value.layout_plan = ''
form.value.layout_plan_file_id = pendingUpload.file_id
option.onSuccess(pendingUpload)
Message.success('上传成功')
} catch (error) {
console.error('文件上传失败:', error)
option.onError(error)
@@ -179,6 +160,11 @@ const handleUpload = async (option: any) => {
// 处理文件变化
const handleFileChange = (files: any[]) => {
fileList.value = files
if (files.length === 0) {
releaseLocalPreviewURL()
form.value.layout_plan = ''
form.value.layout_plan_file_id = ''
}
}
// 监听对话框显示状态
@@ -186,6 +172,7 @@ watch(
() => props.visible,
(newVal) => {
if (newVal) {
releaseLocalPreviewURL()
if (props.floor && isEdit.value) {
// 编辑模式:填充表单
form.value = {
@@ -198,6 +185,7 @@ watch(
load_bearing: props.floor.load_bearing,
enabled: props.floor.enabled !== undefined ? props.floor.enabled : true,
layout_plan: props.floor.layout_plan || '',
layout_plan_file_id: props.floor.layout_plan_file_id || '',
description: props.floor.description || '',
}
// 如果有布局图,设置文件列表
@@ -224,11 +212,12 @@ watch(
load_bearing: undefined,
enabled: true,
layout_plan: '',
layout_plan_file_id: '',
description: '',
}
fileList.value = []
}
}
} else releaseLocalPreviewURL()
}
)
@@ -239,7 +228,7 @@ const handleOk = async () => {
submitting.value = true
try {
const data: any = {
const data: SaveFloorPayload = {
name: form.value.name,
datacenter_id: form.value.datacenter_id,
floor_number: form.value.floor_number,
@@ -248,7 +237,7 @@ const handleOk = async () => {
height: form.value.height,
load_bearing: form.value.load_bearing,
enabled: form.value.enabled,
layout_plan: form.value.layout_plan,
layout_plan_file_id: form.value.layout_plan_file_id,
description: form.value.description,
}
@@ -263,6 +252,9 @@ const handleOk = async () => {
}
if (res.code === 0) {
const savedFloor = res.details
form.value.layout_plan = savedFloor?.layout_plan || ''
form.value.layout_plan_file_id = savedFloor?.layout_plan_file_id || ''
Message.success(isEdit.value ? '编辑成功' : '创建成功')
emit('success')
emit('update:visible', false)
@@ -279,11 +271,13 @@ const handleOk = async () => {
// 取消
const handleCancel = () => {
releaseLocalPreviewURL()
emit('update:visible', false)
}
// 处理对话框可见性变化
const handleVisibleChange = (visible: boolean) => {
if (!visible) releaseLocalPreviewURL()
emit('update:visible', visible)
}
@@ -291,6 +285,8 @@ const handleVisibleChange = (visible: boolean) => {
onMounted(() => {
loadDatacenterList()
})
onBeforeUnmount(releaseLocalPreviewURL)
</script>
<script lang="ts">
@@ -298,19 +294,3 @@ export default {
name: 'FloorFormDialog',
}
</script>
<style scoped lang="less">
.upload-btn {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.upload-text {
margin-top: 8px;
font-size: 12px;
color: var(--color-text-2);
}
</style>