2025-07-30 09:13:52 +08:00
|
|
|
|
<template>
|
|
|
|
|
<div class="image-import">
|
|
|
|
|
<a-modal
|
|
|
|
|
v-model:visible="visible"
|
|
|
|
|
title="导入图像"
|
|
|
|
|
width="600px"
|
|
|
|
|
:confirm-loading="importing"
|
|
|
|
|
@ok="handleImport"
|
|
|
|
|
@cancel="handleCancel"
|
|
|
|
|
>
|
|
|
|
|
<div class="import-content">
|
|
|
|
|
<!-- 选择项目和组件 -->
|
|
|
|
|
<a-form :model="form" layout="vertical">
|
|
|
|
|
<a-form-item label="图像来源" required>
|
|
|
|
|
<a-select
|
|
|
|
|
v-model="form.imageSource"
|
|
|
|
|
:options="imageSourceOptions"
|
|
|
|
|
placeholder="请选择图像来源"
|
|
|
|
|
:loading="loadingImageSources"
|
|
|
|
|
/>
|
|
|
|
|
</a-form-item>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<a-form-item label="目标项目" required>
|
|
|
|
|
<a-tree-select
|
|
|
|
|
v-model="form.projectId"
|
|
|
|
|
:data="projectTree"
|
|
|
|
|
:field-names="{ key: 'id', title: 'name', children: 'children' }"
|
|
|
|
|
placeholder="请选择项目"
|
|
|
|
|
tree-checkable
|
|
|
|
|
:tree-check-strictly="true"
|
|
|
|
|
@change="onProjectChange"
|
|
|
|
|
/>
|
|
|
|
|
</a-form-item>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<a-form-item label="目标组件">
|
|
|
|
|
<a-select
|
|
|
|
|
v-model="form.componentId"
|
|
|
|
|
:options="componentOptions"
|
|
|
|
|
placeholder="请选择组件(可选)"
|
|
|
|
|
allow-clear
|
|
|
|
|
/>
|
|
|
|
|
</a-form-item>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<a-form-item label="上传用户">
|
|
|
|
|
<a-input v-model="form.uploadUser" placeholder="请输入上传用户" />
|
|
|
|
|
</a-form-item>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<a-form-item label="位置信息">
|
|
|
|
|
<a-row :gutter="16">
|
|
|
|
|
<a-col :span="8">
|
|
|
|
|
<a-input v-model="form.altitude" placeholder="海拔" />
|
|
|
|
|
</a-col>
|
|
|
|
|
<a-col :span="8">
|
|
|
|
|
<a-input v-model="form.latitude" placeholder="纬度" />
|
|
|
|
|
</a-col>
|
|
|
|
|
<a-col :span="8">
|
|
|
|
|
<a-input v-model="form.longitude" placeholder="经度" />
|
|
|
|
|
</a-col>
|
|
|
|
|
</a-row>
|
|
|
|
|
</a-form-item>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<a-form-item label="导入设置">
|
|
|
|
|
<a-checkbox-group v-model="form.settings">
|
|
|
|
|
<a-checkbox value="autoAnnotate">导入后自动标注</a-checkbox>
|
|
|
|
|
<a-checkbox value="overwrite">覆盖同名文件</a-checkbox>
|
|
|
|
|
</a-checkbox-group>
|
|
|
|
|
</a-form-item>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
|
|
|
|
<a-form-item v-if="form.settings.includes('autoAnnotate')" label="标注类型">
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<a-select
|
|
|
|
|
v-model="form.annotationTypes"
|
|
|
|
|
:options="defectTypeOptions"
|
|
|
|
|
placeholder="选择要自动标注的缺陷类型"
|
|
|
|
|
multiple
|
|
|
|
|
/>
|
|
|
|
|
</a-form-item>
|
|
|
|
|
</a-form>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<!-- 文件上传区域 -->
|
|
|
|
|
<div class="upload-section">
|
|
|
|
|
<a-upload
|
|
|
|
|
ref="uploadRef"
|
|
|
|
|
:custom-request="() => {}"
|
|
|
|
|
:show-file-list="false"
|
|
|
|
|
accept="image/*"
|
|
|
|
|
multiple
|
|
|
|
|
@change="handleFileChange"
|
|
|
|
|
>
|
|
|
|
|
<template #upload-button>
|
|
|
|
|
<div class="upload-area">
|
2025-07-31 17:32:21 +08:00
|
|
|
|
<div class="upload-drag-icon">
|
|
|
|
|
<IconUpload size="48" />
|
|
|
|
|
</div>
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<div class="upload-text">
|
|
|
|
|
<p>点击或拖拽图像文件到此区域</p>
|
|
|
|
|
<p class="upload-hint">支持 JPG、PNG、JPEG 格式,可同时选择多个文件</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</a-upload>
|
|
|
|
|
</div>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<!-- 文件列表 -->
|
2025-07-31 17:32:21 +08:00
|
|
|
|
<div v-if="fileList.length > 0" class="file-list">
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<div class="list-header">
|
|
|
|
|
<h4>待导入文件 ({{ fileList.length }})</h4>
|
|
|
|
|
<a-button type="text" @click="clearFiles">
|
|
|
|
|
<template #icon>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
<IconDelete />
|
2025-07-30 09:13:52 +08:00
|
|
|
|
</template>
|
|
|
|
|
清空
|
|
|
|
|
</a-button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="list-content">
|
|
|
|
|
<div
|
|
|
|
|
v-for="(file, index) in fileList"
|
|
|
|
|
:key="index"
|
|
|
|
|
class="file-item"
|
|
|
|
|
>
|
|
|
|
|
<div class="file-info">
|
|
|
|
|
<div class="file-preview">
|
|
|
|
|
<img :src="file.preview" alt="preview" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="file-details">
|
|
|
|
|
<div class="file-name">{{ file.name }}</div>
|
|
|
|
|
<div class="file-size">{{ formatFileSize(file.size) }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="file-actions">
|
|
|
|
|
<a-button
|
|
|
|
|
type="text"
|
|
|
|
|
size="small"
|
|
|
|
|
@click="removeFile(index)"
|
|
|
|
|
>
|
|
|
|
|
<template #icon>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
<IconClose />
|
2025-07-30 09:13:52 +08:00
|
|
|
|
</template>
|
|
|
|
|
</a-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<!-- 导入进度 -->
|
2025-07-31 17:32:21 +08:00
|
|
|
|
<div v-if="importing" class="import-progress">
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<a-progress
|
|
|
|
|
:percent="importProgress"
|
|
|
|
|
:status="importStatus"
|
|
|
|
|
size="large"
|
|
|
|
|
/>
|
|
|
|
|
<p class="progress-text">{{ progressText }}</p>
|
|
|
|
|
</div>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<!-- 导入结果 -->
|
2025-07-31 17:32:21 +08:00
|
|
|
|
<div v-if="importResult" class="import-result">
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<a-alert
|
|
|
|
|
:type="importResult.failed.length > 0 ? 'warning' : 'success'"
|
|
|
|
|
:title="getResultTitle()"
|
|
|
|
|
:description="getResultDescription()"
|
|
|
|
|
show-icon
|
|
|
|
|
/>
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
|
|
|
|
<div v-if="importResult.failed.length > 0" class="result-details">
|
2025-07-30 09:13:52 +08:00
|
|
|
|
<h4>失败文件列表:</h4>
|
|
|
|
|
<div class="failed-list">
|
|
|
|
|
<div
|
|
|
|
|
v-for="failed in importResult.failed"
|
|
|
|
|
:key="failed.filename"
|
|
|
|
|
class="failed-item"
|
|
|
|
|
>
|
|
|
|
|
<span class="filename">{{ failed.filename }}</span>
|
|
|
|
|
<span class="error">{{ failed.error }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</a-modal>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-07-31 17:32:21 +08:00
|
|
|
|
import { computed, ref, watch } from 'vue'
|
2025-07-30 09:13:52 +08:00
|
|
|
|
import { Message } from '@arco-design/web-vue'
|
2025-07-31 17:32:21 +08:00
|
|
|
|
import {
|
|
|
|
|
IconClose,
|
|
|
|
|
IconDelete,
|
|
|
|
|
IconUpload,
|
2025-07-30 09:13:52 +08:00
|
|
|
|
} from '@arco-design/web-vue/es/icon'
|
2025-07-31 17:32:21 +08:00
|
|
|
|
import {
|
|
|
|
|
getImageSources,
|
|
|
|
|
getProjectTree,
|
|
|
|
|
importImages,
|
2025-07-30 09:13:52 +08:00
|
|
|
|
} from '@/apis/industrial-image'
|
2025-07-31 17:32:21 +08:00
|
|
|
|
import type {
|
|
|
|
|
ImageImportParams,
|
2025-07-30 09:13:52 +08:00
|
|
|
|
IndustrialImage,
|
2025-07-31 17:32:21 +08:00
|
|
|
|
ProjectTreeNode,
|
2025-07-30 09:13:52 +08:00
|
|
|
|
} from '@/apis/industrial-image/type'
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
visible: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface Emits {
|
|
|
|
|
(e: 'update:visible', visible: boolean): void
|
|
|
|
|
(e: 'importSuccess', result: { success: IndustrialImage[], failed: any[] }): void
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface FileItem {
|
|
|
|
|
file: File
|
|
|
|
|
name: string
|
|
|
|
|
size: number
|
|
|
|
|
preview: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const props = defineProps<Props>()
|
|
|
|
|
const emit = defineEmits<Emits>()
|
|
|
|
|
|
|
|
|
|
// 响应式数据
|
|
|
|
|
const importing = ref(false)
|
|
|
|
|
const importProgress = ref(0)
|
|
|
|
|
const importStatus = ref<'normal' | 'success' | 'warning' | 'danger'>('normal')
|
|
|
|
|
const progressText = ref('')
|
|
|
|
|
const importResult = ref<{ success: IndustrialImage[], failed: any[] } | null>(null)
|
|
|
|
|
|
|
|
|
|
const form = ref({
|
|
|
|
|
imageSource: '',
|
|
|
|
|
projectId: '',
|
|
|
|
|
componentId: '',
|
|
|
|
|
uploadUser: '',
|
|
|
|
|
altitude: '',
|
|
|
|
|
latitude: '',
|
|
|
|
|
longitude: '',
|
|
|
|
|
settings: [] as string[],
|
2025-07-31 17:32:21 +08:00
|
|
|
|
annotationTypes: [] as string[],
|
2025-07-30 09:13:52 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const fileList = ref<FileItem[]>([])
|
|
|
|
|
const projectTree = ref<ProjectTreeNode[]>([])
|
2025-07-31 17:32:21 +08:00
|
|
|
|
const defectTypes = ref<Array<{ id: string, name: string, description?: string, color?: string }>>([])
|
|
|
|
|
const imageSources = ref<Array<{ id: string, name: string, code: string }>>([])
|
2025-07-30 09:13:52 +08:00
|
|
|
|
const loadingImageSources = ref(false)
|
|
|
|
|
|
|
|
|
|
// 计算属性
|
|
|
|
|
const visible = computed({
|
|
|
|
|
get: () => props.visible,
|
2025-07-31 17:32:21 +08:00
|
|
|
|
set: (value) => emit('update:visible', value),
|
2025-07-30 09:13:52 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const componentOptions = computed(() => {
|
2025-07-31 17:32:21 +08:00
|
|
|
|
const findComponents = (nodes: ProjectTreeNode[]): Array<{ label: string, value: string }> => {
|
|
|
|
|
const options: Array<{ label: string, value: string }> = []
|
|
|
|
|
|
|
|
|
|
nodes.forEach((node) => {
|
2025-07-30 09:13:52 +08:00
|
|
|
|
if (node.type === 'component' || node.type === 'blade' || node.type === 'tower') {
|
|
|
|
|
options.push({
|
|
|
|
|
label: node.name,
|
2025-07-31 17:32:21 +08:00
|
|
|
|
value: node.id,
|
2025-07-30 09:13:52 +08:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if (node.children) {
|
|
|
|
|
options.push(...findComponents(node.children))
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
return options
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
return form.value.projectId ? findComponents(projectTree.value) : []
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const defectTypeOptions = computed(() => {
|
2025-07-31 17:32:21 +08:00
|
|
|
|
return defectTypes.value.map((type) => ({
|
2025-07-30 09:13:52 +08:00
|
|
|
|
label: type.name,
|
2025-07-31 17:32:21 +08:00
|
|
|
|
value: type.id,
|
2025-07-30 09:13:52 +08:00
|
|
|
|
}))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const imageSourceOptions = computed(() => {
|
2025-07-31 17:32:21 +08:00
|
|
|
|
return imageSources.value.map((source) => ({
|
2025-07-30 09:13:52 +08:00
|
|
|
|
label: source.name,
|
2025-07-31 17:32:21 +08:00
|
|
|
|
value: source.code,
|
2025-07-30 09:13:52 +08:00
|
|
|
|
}))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 方法
|
|
|
|
|
const loadProjectTree = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await getProjectTree()
|
|
|
|
|
projectTree.value = res.data
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载项目树失败:', error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// const loadDefectTypes = async () => {
|
|
|
|
|
// try {
|
|
|
|
|
// const res = await getDefectTypes()
|
|
|
|
|
// defectTypes.value = res.data
|
|
|
|
|
// } catch (error) {
|
|
|
|
|
// console.error('加载缺陷类型失败:', error)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
const loadImageSources = async () => {
|
|
|
|
|
loadingImageSources.value = true
|
|
|
|
|
try {
|
|
|
|
|
const res = await getImageSources()
|
|
|
|
|
if (res.data && res.data.length > 0 && res.data[0].data) {
|
|
|
|
|
imageSources.value = res.data[0].data
|
|
|
|
|
if (imageSources.value.length > 0) {
|
|
|
|
|
form.value.imageSource = imageSources.value[0].code
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('加载图像来源失败:', error)
|
|
|
|
|
} finally {
|
|
|
|
|
loadingImageSources.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onProjectChange = (value: string) => {
|
|
|
|
|
form.value.componentId = ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleFileChange = (fileList: any) => {
|
|
|
|
|
const files = Array.from(fileList.target?.files || []) as File[]
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
|
|
|
|
files.forEach((file) => {
|
2025-07-30 09:13:52 +08:00
|
|
|
|
if (!file.type.startsWith('image/')) {
|
|
|
|
|
Message.warning(`文件 ${file.name} 不是图像文件`)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
if (file.size > 10 * 1024 * 1024) { // 10MB
|
|
|
|
|
Message.warning(`文件 ${file.name} 大小超过10MB`)
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
const reader = new FileReader()
|
|
|
|
|
reader.onload = (e) => {
|
|
|
|
|
const fileItem: FileItem = {
|
|
|
|
|
file,
|
|
|
|
|
name: file.name,
|
|
|
|
|
size: file.size,
|
2025-07-31 17:32:21 +08:00
|
|
|
|
preview: e.target?.result as string,
|
2025-07-30 09:13:52 +08:00
|
|
|
|
}
|
|
|
|
|
fileList.value.push(fileItem)
|
|
|
|
|
}
|
|
|
|
|
reader.readAsDataURL(file)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const removeFile = (index: number) => {
|
|
|
|
|
fileList.value.splice(index, 1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const clearFiles = () => {
|
|
|
|
|
fileList.value = []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const formatFileSize = (bytes: number): string => {
|
|
|
|
|
if (bytes === 0) return '0 B'
|
|
|
|
|
const k = 1024
|
|
|
|
|
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
2025-07-31 17:32:21 +08:00
|
|
|
|
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
|
2025-07-30 09:13:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleImport = async () => {
|
|
|
|
|
if (!form.value.imageSource) {
|
|
|
|
|
Message.warning('请选择图像来源')
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
if (!form.value.projectId) {
|
|
|
|
|
Message.warning('请选择目标项目')
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
if (fileList.value.length === 0) {
|
|
|
|
|
Message.warning('请选择要导入的图像文件')
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
importing.value = true
|
|
|
|
|
importProgress.value = 0
|
|
|
|
|
importStatus.value = 'normal'
|
|
|
|
|
progressText.value = '正在导入图像...'
|
|
|
|
|
importResult.value = null
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
try {
|
2025-07-31 17:32:21 +08:00
|
|
|
|
const files = fileList.value.map((item) => item.file)
|
2025-07-30 09:13:52 +08:00
|
|
|
|
const params: ImageImportParams = {
|
|
|
|
|
imageSource: form.value.imageSource,
|
|
|
|
|
projectId: form.value.projectId,
|
|
|
|
|
componentId: form.value.componentId || undefined,
|
|
|
|
|
uploadUser: form.value.uploadUser || undefined,
|
|
|
|
|
altitude: form.value.altitude || undefined,
|
|
|
|
|
latitude: form.value.latitude || undefined,
|
|
|
|
|
longitude: form.value.longitude || undefined,
|
|
|
|
|
autoAnnotate: form.value.settings.includes('autoAnnotate'),
|
2025-07-31 17:32:21 +08:00
|
|
|
|
annotationTypes: form.value.settings.includes('autoAnnotate') ? form.value.annotationTypes : undefined,
|
2025-07-30 09:13:52 +08:00
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
// 模拟进度更新
|
|
|
|
|
const progressInterval = setInterval(() => {
|
|
|
|
|
if (importProgress.value < 90) {
|
|
|
|
|
importProgress.value += 10
|
|
|
|
|
}
|
|
|
|
|
}, 200)
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
const res = await importImages(files, params)
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
clearInterval(progressInterval)
|
|
|
|
|
importProgress.value = 100
|
|
|
|
|
importStatus.value = 'success'
|
|
|
|
|
progressText.value = '导入完成!'
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
// 注意:真实API返回的数据结构可能不同,需要根据实际情况调整
|
|
|
|
|
const mockResult = {
|
|
|
|
|
success: files.map((file, index) => ({
|
|
|
|
|
id: `img_${Date.now()}_${index}`,
|
|
|
|
|
name: file.name,
|
|
|
|
|
path: `uploaded/${file.name}`,
|
|
|
|
|
size: file.size,
|
|
|
|
|
type: file.type,
|
|
|
|
|
projectId: form.value.projectId,
|
|
|
|
|
componentId: form.value.componentId,
|
2025-07-31 17:32:21 +08:00
|
|
|
|
createTime: new Date().toISOString(),
|
2025-07-30 09:13:52 +08:00
|
|
|
|
})),
|
2025-07-31 17:32:21 +08:00
|
|
|
|
failed: [],
|
2025-07-30 09:13:52 +08:00
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
importResult.value = mockResult
|
|
|
|
|
emit('importSuccess', mockResult)
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
Message.success(`成功导入 ${files.length} 个图像文件`)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('导入失败:', error)
|
|
|
|
|
importProgress.value = 100
|
|
|
|
|
importStatus.value = 'danger'
|
|
|
|
|
progressText.value = '导入失败!'
|
|
|
|
|
Message.error('导入图像失败')
|
|
|
|
|
} finally {
|
|
|
|
|
importing.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleCancel = () => {
|
|
|
|
|
if (importing.value) {
|
|
|
|
|
Message.warning('正在导入中,请稍后再试')
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
resetForm()
|
|
|
|
|
visible.value = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
form.value = {
|
|
|
|
|
imageSource: '',
|
|
|
|
|
projectId: '',
|
|
|
|
|
componentId: '',
|
|
|
|
|
uploadUser: '',
|
|
|
|
|
altitude: '',
|
|
|
|
|
latitude: '',
|
|
|
|
|
longitude: '',
|
|
|
|
|
settings: [],
|
2025-07-31 17:32:21 +08:00
|
|
|
|
annotationTypes: [],
|
2025-07-30 09:13:52 +08:00
|
|
|
|
}
|
|
|
|
|
fileList.value = []
|
|
|
|
|
importResult.value = null
|
|
|
|
|
importProgress.value = 0
|
|
|
|
|
importStatus.value = 'normal'
|
|
|
|
|
progressText.value = ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getResultTitle = () => {
|
|
|
|
|
if (!importResult.value) return ''
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
const { success, failed } = importResult.value
|
|
|
|
|
if (failed.length === 0) {
|
|
|
|
|
return `导入成功!共导入 ${success.length} 个图像文件`
|
|
|
|
|
} else {
|
|
|
|
|
return `导入完成!成功 ${success.length} 个,失败 ${failed.length} 个`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getResultDescription = () => {
|
|
|
|
|
if (!importResult.value) return ''
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
const { success, failed } = importResult.value
|
|
|
|
|
if (failed.length === 0) {
|
|
|
|
|
return '所有图像文件都已成功导入到指定项目中'
|
|
|
|
|
} else {
|
|
|
|
|
return `部分文件导入失败,请检查失败原因并重新导入`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 监听器
|
|
|
|
|
watch(visible, (newValue) => {
|
|
|
|
|
if (newValue) {
|
|
|
|
|
loadProjectTree()
|
|
|
|
|
// loadDefectTypes()
|
|
|
|
|
loadImageSources()
|
|
|
|
|
} else {
|
|
|
|
|
resetForm()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
.image-import {
|
|
|
|
|
.import-content {
|
|
|
|
|
max-height: 70vh;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.upload-section {
|
|
|
|
|
margin: 20px 0;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.upload-area {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
min-height: 120px;
|
|
|
|
|
border: 2px dashed #d9d9d9;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
cursor: pointer;
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
&:hover {
|
|
|
|
|
border-color: #1890ff;
|
|
|
|
|
background: #f0f8ff;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.upload-drag-icon {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.upload-text {
|
|
|
|
|
text-align: center;
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
p {
|
|
|
|
|
margin: 0;
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
&.upload-hint {
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.file-list {
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
border: 1px solid #e8e8e8;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.list-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
background: #fafafa;
|
|
|
|
|
border-bottom: 1px solid #e8e8e8;
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
h4 {
|
|
|
|
|
margin: 0;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.list-content {
|
|
|
|
|
max-height: 300px;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.file-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
&:last-child {
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.file-info {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.file-preview {
|
|
|
|
|
width: 40px;
|
|
|
|
|
height: 40px;
|
|
|
|
|
margin-right: 12px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
border: 1px solid #e8e8e8;
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
img {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.file-details {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.file-name {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #333;
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.file-size {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #999;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.import-progress {
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.progress-text {
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #666;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.import-result {
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.result-details {
|
|
|
|
|
margin-top: 16px;
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
h4 {
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.failed-list {
|
|
|
|
|
max-height: 150px;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
border: 1px solid #f0f0f0;
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.failed-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
&:last-child {
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.filename {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
|
2025-07-30 09:13:52 +08:00
|
|
|
|
.error {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #ff4d4f;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-31 17:32:21 +08:00
|
|
|
|
</style>
|