feat: 接入资产文件上传服务
This commit is contained in:
@@ -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
|
||||
@@ -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
48
src/api/ops/floorFile.ts
Normal 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
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user