1341 lines
33 KiB
Vue
1341 lines
33 KiB
Vue
<template>
|
||
<div class="disassembly-detail">
|
||
<div class="order-info-container">
|
||
<div class="order-info-header">
|
||
<div class="header-left">
|
||
<el-button circle @click="goBack" class="back-button">
|
||
<el-icon><ArrowLeft /></el-icon>
|
||
</el-button>
|
||
<h2 class="order-title">拆件详情</h2>
|
||
</div>
|
||
<el-tag :type="getStatusType(orderDetail.status)" class="order-status">
|
||
{{ getStatusText(orderDetail.status) }}
|
||
</el-tag>
|
||
</div>
|
||
<div class="order-info-content">
|
||
<div class="info-item">
|
||
<span class="info-label">订单号:</span>
|
||
<span class="info-value">{{ orderDetail.orderNumber }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">创作者:</span>
|
||
<span class="info-value">{{ orderDetail.customerName }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">模型名称:</span>
|
||
<span class="info-value">{{ orderDetail.modelName }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">创建时间:</span>
|
||
<span class="info-value">{{ orderDetail.createTime }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="timeline-section">
|
||
<div class="timeline-header">
|
||
<div class="timeline-title-container">
|
||
<h2 class="timeline-title">处理流程</h2>
|
||
<div class="timeline-progress">
|
||
<span class="progress-text">进度</span>
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" :style="{ width: `${(currentStep / 3) * 100}%` }"></div>
|
||
</div>
|
||
<span class="progress-step">{{ currentStep }}/3</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="timeline-container">
|
||
<el-timeline>
|
||
<!-- 第一步:预览图和模型展示 -->
|
||
<el-timeline-item
|
||
:color="currentStep >= 1 ? '#6B46C1' : '#e4e7ed'"
|
||
size="large"
|
||
>
|
||
<div class="timeline-content">
|
||
<h3>{{ $t('admin.disassemblyOrders.detail.step1') }}</h3>
|
||
<div v-if="currentStep >= 1" class="step-content">
|
||
<div class="preview-container-horizontal">
|
||
<div class="preview-images-horizontal">
|
||
<div v-if="thumbnailUrl" class="image-item-horizontal" @click="previewImage(thumbnailUrl)">
|
||
<img :src="thumbnailUrl" alt="缩略图" />
|
||
<div class="image-label">{{ $t('admin.disassemblyOrders.detail.preview') }}</div>
|
||
<div class="image-actions">
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
@click.stop="changePreviewImage"
|
||
>
|
||
{{ $t('admin.disassemblyOrders.detail.changePreview') }}
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
<div class="image-item-horizontal" @click="previewModel(modelUrl)">
|
||
<div class="model-preview-horizontal">
|
||
<el-icon size="48"><View /></el-icon>
|
||
<span>{{ $t('admin.disassemblyOrders.detail.previewDialog') }}</span>
|
||
</div>
|
||
<div class="image-label">{{ $t('admin.disassemblyOrders.detail.previewDialog') }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="prompt-template-container">
|
||
<div class="prompt-template-label">拆件提示词模板</div>
|
||
<el-input
|
||
v-model="cjtsc"
|
||
type="textarea"
|
||
:rows="4"
|
||
placeholder="请输入拆件提示词模板"
|
||
class="prompt-template-textarea"
|
||
></el-input>
|
||
</div>
|
||
<div class="prompt-template-container">
|
||
<div class="prompt-template-label">合并提示词模板</div>
|
||
<el-input
|
||
v-model="hbtsc"
|
||
type="textarea"
|
||
:rows="4"
|
||
placeholder="请输入合并提示词模板"
|
||
class="prompt-template-textarea"
|
||
></el-input>
|
||
</div>
|
||
<div class="disassembly-button-container">
|
||
<el-button
|
||
type="primary"
|
||
size="large"
|
||
:loading="disassemblyLoading"
|
||
@click="startDisassembly"
|
||
>
|
||
{{ $t('admin.disassemblyOrders.detail.disassembly') }}
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-timeline-item>
|
||
|
||
<!-- 第二步:展示已拆件图片 -->
|
||
<el-timeline-item
|
||
:color="currentStep >= 2 ? '#6B46C1' : '#e4e7ed'"
|
||
size="large"
|
||
>
|
||
<div class="timeline-content" style="z-index: 3;">
|
||
<div class="step-header">
|
||
<h3>{{ $t('admin.disassemblyOrders.detail.step2') }}</h3>
|
||
<el-button
|
||
v-if="currentStep >= 2"
|
||
type="primary"
|
||
size="small"
|
||
@click="toggleMergeMode"
|
||
>
|
||
{{ mergeMode ? '取消合并' : '合并图片' }}
|
||
</el-button>
|
||
</div>
|
||
<div v-if="currentStep >= 2&&disassembledImages.length>0" class="step-content">
|
||
<div class="disassembled-images">
|
||
<div
|
||
v-for="(item, index) in disassembledImages"
|
||
:key="item.id"
|
||
class="image-item"
|
||
:class="{ 'selectable': mergeMode, 'selected': selectedImages.includes(index) }"
|
||
|
||
>
|
||
<ImageWrapper
|
||
:prompt="cjtsc"
|
||
:card-data="item"
|
||
:selected="selectedImages.includes(index)"
|
||
@preview="previewImage"
|
||
@delete="deleteImage(index)"
|
||
@generate-model="generateModelFromImage"
|
||
@show-actions="showImageActions"
|
||
@hide-actions="hideImageActions"
|
||
@save="(result)=>saveImage(index,result)"
|
||
@edit="(result)=>editImage(index,result)"
|
||
@partial-edit="(imageUrl)=>handlePartialEdit(imageUrl, index)"
|
||
/>
|
||
<!-- <div class="image-label">{{ $t('admin.disassemblyOrders.detail.preview') }} {{ index + 1 }}</div> -->
|
||
<!-- 选择框 -->
|
||
<!-- <div v-if="mergeMode" class="image-checkbox">
|
||
<el-checkbox v-model="selectedImages" :label="index" @change="handleCheckboxChange"></el-checkbox>
|
||
</div> -->
|
||
<div v-if="mergeMode" @click="mergeMode && toggleImageSelection(index)" style="position: absolute;width: 100%;height: 100%;z-index: 2;"></div>
|
||
</div>
|
||
</div>
|
||
<!-- 确定合并按钮 -->
|
||
<div v-if="showMergeConfirm" class="merge-confirm-button">
|
||
<el-button type="success" size="large" @click="confirmMerge">
|
||
确定合并
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="height: 80px;width: 100%;"></div>
|
||
</el-timeline-item>
|
||
|
||
<!-- 第三步:展示生成的模型 -->
|
||
<el-timeline-item
|
||
:color="currentStep >= 3 ? '#6B46C1' : '#e4e7ed'"
|
||
size="large"
|
||
>
|
||
<div class="timeline-content">
|
||
<h3>{{ $t('admin.disassemblyOrders.detail.step3') }}</h3>
|
||
<div class="step-content" >
|
||
<div class="model-upload-section">
|
||
<el-button
|
||
type="primary"
|
||
@click="showModelUploadModal = true"
|
||
class="upload-model-button"
|
||
>
|
||
<el-icon><Upload /></el-icon>
|
||
{{ $t('modelUpload.uploadModel') }}
|
||
</el-button>
|
||
<span class="upload-hint-text">{{ $t('modelUpload.uploadHint') }}</span>
|
||
<div class="warning-hint" style="margin-left: auto; display: flex; align-items: center; gap: 6px;">
|
||
<el-icon color="#f2ac34"><WarningFilled /></el-icon>
|
||
<span style="color: #f2ac34; font-size: 13px; line-height: 1.5;">{{ $t('modelUpload.saveToOrderHint') }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="generated-models">
|
||
<div
|
||
v-for="(item, index) in generatedModels"
|
||
:key="item.id"
|
||
class="model-item"
|
||
>
|
||
<ModelCom
|
||
@preview="previewModel"
|
||
:img-url="item.image"
|
||
:model-url="item.modelUrl"
|
||
:task-id="item.taskId"
|
||
:result-task="item.resultTask"
|
||
width="100%"
|
||
height="150px"
|
||
:project-id="orderDetail.projectId"
|
||
@save="(result,resultTask,modelUrl='')=>saveModel(index,result,resultTask,modelUrl)"
|
||
:show-delete="true"
|
||
@delete="deleteModel(index)"
|
||
/>
|
||
<div class="image-label">模型 {{ index + 1 }}</div>
|
||
</div>
|
||
</div>
|
||
<!-- <div class="step-actions">
|
||
<el-button
|
||
type="primary"
|
||
@click="handleProcessingComplete"
|
||
:loading="processingCompleteLoading"
|
||
>
|
||
加工完成
|
||
</el-button>
|
||
</div> -->
|
||
</div>
|
||
</div>
|
||
</el-timeline-item>
|
||
|
||
<!-- 第四步:工期信息 -->
|
||
<!-- <el-timeline-item
|
||
:color="currentStep >= 4 ? '#6B46C1' : '#e4e7ed'"
|
||
size="large"
|
||
>
|
||
<div class="timeline-content">
|
||
<h3>工期信息</h3>
|
||
<div v-if="currentStep >= 4" class="step-content">
|
||
<div class="work-period-info">
|
||
<div class="info-card">
|
||
<div class="card-content">
|
||
<div class="info-icon">
|
||
<el-icon><Calendar /></el-icon>
|
||
</div>
|
||
<div class="info-content">
|
||
<div class="info-label">开始工期</div>
|
||
<div class="info-value">{{ formatDate(orderDetail.createTime) }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-card">
|
||
<div class="card-content">
|
||
<div class="info-icon">
|
||
<el-icon><Check /></el-icon>
|
||
</div>
|
||
<div class="info-content">
|
||
<div class="info-label">完成工期</div>
|
||
<div class="info-value">{{ formatDate(orderDetail.processingCompleteTime) }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-card total-days-card">
|
||
<div class="card-content">
|
||
<div class="info-icon">
|
||
<el-icon><Timer /></el-icon>
|
||
</div>
|
||
<div class="info-content">
|
||
<div class="info-label">共计天数</div>
|
||
<div class="info-value">{{ calculateTotalDays() }} 天</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="step-actions">
|
||
<el-button @click="goBack">返回列表</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-timeline-item> -->
|
||
</el-timeline>
|
||
</div>
|
||
</div>
|
||
<!-- 图片预览对话框 -->
|
||
<el-dialog v-model="imagePreviewVisible" :title="$t('admin.disassemblyOrders.detail.preview')" width="50%" >
|
||
<img :src="previewImageUrl" style="width:100%;object-fit: contain;" />
|
||
</el-dialog>
|
||
<!-- 模型预览对话框 -->
|
||
<el-dialog v-model="modelPreviewVisible" :title="$t('admin.disassemblyOrders.detail.previewDialog')" width="70%">
|
||
<ModelViewer
|
||
:model-url="previewModelUrl"
|
||
:width="'100%'"
|
||
/>
|
||
<template #footer>
|
||
<el-button @click="modelPreviewVisible = false">{{ $t('admin.disassemblyOrders.detail.close') }}</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
<!-- 更换预览图对话框 -->
|
||
<el-dialog v-model="showImageUpload" :title="$t('admin.disassemblyOrders.detail.changePreview')" width="30%">
|
||
<div class="upload-container">
|
||
<el-upload
|
||
class="image-uploader"
|
||
:before-upload="handleImageUpload"
|
||
:disabled="uploadLoading"
|
||
>
|
||
<div class="upload-trigger">
|
||
<el-icon size="32" v-if="!uploadLoading"><Plus /></el-icon>
|
||
<el-icon size="32" v-else><Loading /></el-icon>
|
||
<div class="upload-text">
|
||
{{ uploadLoading ? $t('common.loading') : $t('admin.disassemblyOrders.detail.uploadImage') }}
|
||
</div>
|
||
</div>
|
||
</el-upload>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="cancelImageUpload">{{ $t('common.cancel') }}</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
<!-- 画布编辑器对话框 -->
|
||
<DtCanvasEditor
|
||
v-model:visible="canvasEditorVisible"
|
||
:image-url="canvasEditorImageUrl"
|
||
@add-prompt-card="handleCanvasSave"
|
||
/>
|
||
<!-- 模型上传弹窗 -->
|
||
<ModelUploadModal
|
||
v-model="showModelUploadModal"
|
||
@confirm="handleModelUpload"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, onMounted, onUnmounted, watch,computed } from 'vue'
|
||
import { useRouter, useRoute } from 'vue-router'
|
||
import { useI18n } from 'vue-i18n'
|
||
import {AdminOrders} from '../AdminOrders/AdminOrders.js'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import {prompt} from '@deotaland/utils'
|
||
import {
|
||
ArrowLeft,
|
||
View,
|
||
Close,
|
||
Calendar,
|
||
Check,
|
||
Timer,
|
||
Plus,
|
||
Loading,
|
||
Upload
|
||
} from '@element-plus/icons-vue'
|
||
import ModelViewer from '@/components/common/ModelViewer.vue'
|
||
import ModelCom from '@/components/modelCom/index.vue'
|
||
import ImageWrapper from '@/components/disassembly/ImageWrapper.vue'
|
||
import ModelUploadModal from '@/components/ModelUploadModal/index.vue'
|
||
import { AdminDisassemblyDetail } from './AdminDisassemblyDetail.js';
|
||
import { MeshyServer,GiminiServer,FileServer } from '@deotaland/utils';
|
||
const { t } = useI18n()
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
const Plug = new AdminDisassemblyDetail();
|
||
const adminOrders = new AdminOrders();
|
||
const fileServer = new FileServer();
|
||
const cjtsc = ref(prompt.Hairseparation+'');
|
||
const hbtsc = ref('将图中元素并排放在一张图中');
|
||
const editIndex = ref(-1);
|
||
const editContent = ref('');
|
||
const canvasEditorVisible = ref(false);
|
||
const canvasEditorImageUrl = ref('');
|
||
const currentEditImageIndex = ref(-1);
|
||
const showModelUploadModal = ref(false);
|
||
|
||
// 订单详情
|
||
const orderDetail = ref({
|
||
orderNumber: '',
|
||
customerName: '',
|
||
modelName: '',
|
||
createTime: '',
|
||
status: 'pending',
|
||
processingCompleteTime: null
|
||
})
|
||
const saveModel = (index,result,resultTask,modelUrl='')=>{
|
||
generatedModels.value[index].taskId = result;
|
||
generatedModels.value[index].resultTask = resultTask;
|
||
generatedModels.value[index].modelUrl = modelUrl;
|
||
}
|
||
const saveImage = (index,result)=>{
|
||
disassembledImages.value[index].taskId = result.taskId;
|
||
disassembledImages.value[index].taskQueue = result.taskQueue;
|
||
disassembledImages.value[index].imageUrl = result.imageUrl;
|
||
}
|
||
const editImage = (index,result)=>{
|
||
const imgurl = disassembledImages.value[index].imageUrl;
|
||
const newItem = {
|
||
id: new Date().getTime(),
|
||
thumbnailUrl:imgurl,
|
||
imageUrl: '',
|
||
taskID: '',
|
||
taskQueue: '',
|
||
prompt: result,
|
||
type:'edit',
|
||
project_id: orderDetail.value.projectId,
|
||
};
|
||
disassembledImages.value.push(newItem);
|
||
}
|
||
|
||
const handlePartialEdit = (imageUrl, index) => {
|
||
canvasEditorImageUrl.value = imageUrl;
|
||
currentEditImageIndex.value = index;
|
||
canvasEditorVisible.value = true;
|
||
}
|
||
const handleCanvasSave = (editedImageUrl,editContent) => {
|
||
fileServer.uploadFile(editedImageUrl).then((url) => {
|
||
const newItem = {
|
||
id: new Date().getTime(),
|
||
thumbnailUrl: url,
|
||
taskID: '',
|
||
taskQueue: '',
|
||
prompt: editContent,
|
||
type:'edit',
|
||
project_id: orderDetail.value.projectId,
|
||
};
|
||
disassembledImages.value.push(newItem);
|
||
})
|
||
}
|
||
canvasEditorVisible.value = false;
|
||
// 当前步骤
|
||
const currentStep = ref(3)
|
||
|
||
// 加载状态
|
||
const disassemblyLoading = ref(false)
|
||
const processingCompleteLoading = ref(false)
|
||
// 预览相关
|
||
const imagePreviewVisible = ref(false)
|
||
const modelPreviewVisible = ref(false)
|
||
const previewImageUrl = ref('')
|
||
const previewModelUrl = ref('')
|
||
|
||
// 图片删除相关
|
||
const hoveredImageIndex = ref(-1)
|
||
|
||
// 更换预览图相关
|
||
const showImageUpload = ref(false)
|
||
const uploadLoading = ref(false)
|
||
|
||
// 合并图片相关
|
||
const mergeMode = ref(false)
|
||
const selectedImages = ref([])
|
||
|
||
// 计算属性:是否显示确定合并按钮
|
||
const showMergeConfirm = computed(() => {
|
||
return mergeMode.value && selectedImages.value.length >= 2
|
||
})
|
||
// 资源路径
|
||
const thumbnailUrl = ref('');
|
||
const modelUrl = ref('')
|
||
const generatedModelUrl = ref('')
|
||
// 存储多个生成的模型
|
||
const generatedModels = ref([])
|
||
// 拆件后的图片(模拟)
|
||
const disassembledImages = ref([
|
||
])
|
||
watch(()=>[generatedModels.value,disassembledImages.value],()=>{
|
||
saveDisassemblyProgress()
|
||
}, {deep: true})
|
||
// 预览模型
|
||
const previewModel = (url) => {
|
||
previewModelUrl.value = url
|
||
modelPreviewVisible.value = true
|
||
}
|
||
|
||
// 处理模型上传
|
||
const handleModelUpload = (files) => {
|
||
if (files.length === 0) {
|
||
ElMessage.warning(t('admin.disassemblyOrders.detail.messages.uploadModelWarning') || '请选择要上传的模型文件')
|
||
return
|
||
}
|
||
const file = files[0]
|
||
fileServer.uploadFile(file).then((url) => {
|
||
generatedModels.value.push({
|
||
id: new Date().getTime(),
|
||
modelUrl: url,
|
||
taskId: '',
|
||
resultTask: ''
|
||
})
|
||
})
|
||
}
|
||
//保存拆件进度
|
||
const saveDisassemblyProgress = () => {
|
||
let project_details = {
|
||
...orderData.value.project_details,
|
||
disassembledImages: disassembledImages.value,
|
||
generatedModels: generatedModels.value
|
||
};
|
||
let params = {
|
||
id:orderData.value.id,
|
||
project_details:project_details
|
||
}
|
||
adminOrders.saveDisassemblyProgress(params);
|
||
}
|
||
|
||
// 获取状态标签类型
|
||
const getStatusType = (status) => {
|
||
const statusMap = {
|
||
pending: 'warning',
|
||
processing: 'primary',
|
||
'awaiting-shipment': 'success',
|
||
completed: 'success',
|
||
failed: 'danger'
|
||
}
|
||
return statusMap[status] || 'info'
|
||
}
|
||
|
||
// 获取状态文本
|
||
const getStatusText = (status) => {
|
||
switch (status) {
|
||
case 'pending':
|
||
return '待处理'
|
||
case 'processing':
|
||
return '处理中'
|
||
case 'awaiting-shipment':
|
||
return '待发货'
|
||
case 'completed':
|
||
return '已完成'
|
||
case 'failed':
|
||
return '失败'
|
||
default:
|
||
return '未知'
|
||
}
|
||
}
|
||
// 返回上一页
|
||
const goBack = () => {
|
||
router.back();
|
||
}
|
||
// 预览图片
|
||
const previewImage = (url) => {
|
||
previewImageUrl.value = url
|
||
imagePreviewVisible.value = true
|
||
}
|
||
|
||
// 更换预览图
|
||
const changePreviewImage = () => {
|
||
showImageUpload.value = true
|
||
}
|
||
|
||
// 处理图片上传
|
||
const handleImageUpload = (file) => {
|
||
uploadLoading.value = true
|
||
// 更新预览图URL
|
||
// thumbnailUrl.value = e.target.result
|
||
fileServer.uploadFile(file).then((url) => {
|
||
thumbnailUrl.value = url
|
||
uploadLoading.value = false
|
||
showImageUpload.value = false
|
||
ElMessage.success(t('admin.disassemblyOrders.detail.messages.uploadSuccess') || '图片上传成功')
|
||
})
|
||
}
|
||
// 取消图片上传
|
||
const cancelImageUpload = () => {
|
||
showImageUpload.value = false
|
||
}
|
||
const startDisassembly = () => {
|
||
for(let i = 0;i<1;i++){
|
||
handleDisassembly();
|
||
}
|
||
}
|
||
// 执行拆件
|
||
const handleDisassembly = () => {
|
||
const newItem = {
|
||
id: new Date().getTime(),
|
||
thumbnailUrl: thumbnailUrl.value,
|
||
imageUrl: '',
|
||
taskID: '',
|
||
taskQueue: '',
|
||
project_id: orderDetail.value.projectId,
|
||
};
|
||
// disassembledImages.value = []
|
||
// return
|
||
// 使用展开运算符创建新数组以确保响应式更新
|
||
disassembledImages.value = [...disassembledImages.value, newItem];
|
||
// if(currentStep.value<=2){
|
||
// currentStep.value = 2;
|
||
// }
|
||
}
|
||
|
||
|
||
const deleteModel = (index) => {
|
||
generatedModels.value.splice(index, 1)
|
||
}
|
||
|
||
const orderId = ref('');
|
||
const orderData = ref({});
|
||
// 组件挂载时获取订单信息
|
||
onMounted(async () => {
|
||
MeshyServer.pollingEnabled = true;
|
||
GiminiServer.pollingEnabled = true;
|
||
orderId.value = route.params.id
|
||
const result = await adminOrders.getOrderDetail({
|
||
id:orderId.value
|
||
})
|
||
let data = result.data;
|
||
orderDetail.value = {
|
||
orderNumber: data.order_no,
|
||
customerName: data.order_info.shipping.firstName+' '+data.order_info.shipping.lastName,
|
||
modelName: data.order_info.ipName,
|
||
createTime: data.created_at,
|
||
status: 'pending',
|
||
processingCompleteTime: null,
|
||
projectId:data.project_id,
|
||
}
|
||
thumbnailUrl.value = data.order_info.modelData.imageUrl;
|
||
modelUrl.value = data.order_info.modelData.modelUrl;
|
||
orderData.value = data;
|
||
disassembledImages.value = data.project_details.disassembledImages || [];
|
||
generatedModels.value = data.project_details.generatedModels || [];
|
||
|
||
// 根据数组长度自动判断当前步骤
|
||
if (generatedModels.value.length > 0) {
|
||
// 如果有生成的模型,直接进入第3步
|
||
// currentStep.value = 3;
|
||
} else if (disassembledImages.value.length > 0) {
|
||
// 如果有拆件图片但没有模型,进入第2步
|
||
// currentStep.value = 2;
|
||
} else {
|
||
// 否则保持在第1步
|
||
// currentStep.value = 1;
|
||
}
|
||
})
|
||
|
||
// 组件卸载时清理资源
|
||
onUnmounted(() => {
|
||
MeshyServer.pollingEnabled = false;
|
||
GiminiServer.pollingEnabled = false;
|
||
// 清理预览状态
|
||
imagePreviewVisible.value = false
|
||
modelPreviewVisible.value = false
|
||
})
|
||
|
||
// 删除图片
|
||
const deleteImage = (index) => {
|
||
disassembledImages.value.splice(index, 1)
|
||
}
|
||
|
||
|
||
// 显示图片操作按钮
|
||
const showImageActions = (index) => {
|
||
hoveredImageIndex.value = index
|
||
}
|
||
|
||
// 隐藏图片操作按钮
|
||
const hideImageActions = () => {
|
||
hoveredImageIndex.value = -1
|
||
}
|
||
|
||
// 切换合并模式
|
||
const toggleMergeMode = () => {
|
||
mergeMode.value = !mergeMode.value
|
||
if (!mergeMode.value) {
|
||
selectedImages.value = []
|
||
}
|
||
}
|
||
|
||
// 切换图片选择状态
|
||
const toggleImageSelection = (index) => {
|
||
const indexInSelected = selectedImages.value.indexOf(index)
|
||
if (indexInSelected > -1) {
|
||
selectedImages.value.splice(indexInSelected, 1)
|
||
} else {
|
||
selectedImages.value.push(index)
|
||
}
|
||
}
|
||
|
||
// 处理复选框变化
|
||
const handleCheckboxChange = () => {
|
||
// 复选框变化由v-model自动处理
|
||
}
|
||
|
||
// 确认合并图片
|
||
const confirmMerge = () => {
|
||
const imagesToMerge = selectedImages.value.map(index => {
|
||
return {
|
||
index,
|
||
...disassembledImages.value[index]
|
||
}
|
||
})
|
||
let imgArr = [];
|
||
// console.log('Selected images to merge:', imagesToMerge)
|
||
imagesToMerge.forEach((item,index) => {
|
||
imgArr.push(item.imageUrl)
|
||
})
|
||
let parms = {
|
||
id:new Date().getTime(),
|
||
project_id:orderDetail.value.projectId,
|
||
imgArr,
|
||
type:'merge',//合并类型
|
||
prompt:hbtsc.value//使用合并提示词模板
|
||
}
|
||
console.log(parms,'parmsparms');
|
||
disassembledImages.value.push(parms)
|
||
// 合并完成后退出合并模式
|
||
mergeMode.value = false
|
||
selectedImages.value = []
|
||
}
|
||
// 从单张图片生成模型并进入第三步
|
||
const generateModelFromImage = async (image) => {
|
||
// console.log('generateModelFromImage',image);
|
||
const newModel = {
|
||
id: new Date().getTime(),
|
||
image: image,
|
||
taskId: ''
|
||
};
|
||
|
||
// 使用展开运算符创建新数组以确保响应式更新
|
||
generatedModels.value = [...generatedModels.value, newModel];
|
||
console.log('generatedModels.value',generatedModels.value);
|
||
// 如果是第一次生成模型,进入第三步
|
||
// if (currentStep.value < 3) {
|
||
// currentStep.value = 3
|
||
// }
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
|
||
.page-header {
|
||
margin-bottom: 24px;
|
||
padding-bottom: 16px;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
}
|
||
|
||
/* 订单信息容器 */
|
||
.order-info-container {
|
||
background-color: #ffffff;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin-bottom: 24px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.order-info-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px solid #f3f4f6;
|
||
}
|
||
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.back-button {
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #f9fafb;
|
||
border: 1px solid #e5e7eb;
|
||
color: #6b7280;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.back-button:hover {
|
||
background-color: #f3f4f6;
|
||
border-color: #d1d5db;
|
||
color: #374151;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.order-title {
|
||
margin: 0;
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.order-status {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.order-info-content {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 14px;
|
||
color: #6b7280;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 15px;
|
||
color: #1f2937;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 28px;
|
||
font-weight: 600;
|
||
color: #1f2937;
|
||
margin: 0 0 8px 0;
|
||
}
|
||
|
||
.page-subtitle {
|
||
color: #6b7280;
|
||
margin: 0;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.timeline-section {
|
||
margin-top: 24px;
|
||
background-color: #ffffff;
|
||
border-radius: 8px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
border: 1px solid #e5e7eb;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.timeline-header {
|
||
padding: 20px;
|
||
border-bottom: 1px solid #f3f4f6;
|
||
background: linear-gradient(135deg, #6B46C1 0%, #8B5CF6 100%);
|
||
}
|
||
|
||
.timeline-title-container {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.timeline-title {
|
||
margin: 0;
|
||
font-size: 22px;
|
||
font-weight: 600;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.timeline-progress {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.progress-text {
|
||
font-size: 14px;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.progress-bar {
|
||
width: 120px;
|
||
height: 6px;
|
||
background-color: rgba(255, 255, 255, 0.2);
|
||
border-radius: 3px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background-color: #ffffff;
|
||
border-radius: 3px;
|
||
transition: width 0.3s ease;
|
||
}
|
||
|
||
.progress-step {
|
||
font-size: 14px;
|
||
color: #ffffff;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.timeline-container {
|
||
margin-top: 0;
|
||
padding: 20px;
|
||
}
|
||
|
||
.timeline-content {
|
||
padding-left: 16px;
|
||
}
|
||
|
||
.timeline-content h3 {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #1f2937;
|
||
margin: 0 0 16px 0;
|
||
display: flex;
|
||
align-items: center;
|
||
/* position: relative; */
|
||
/* top: -1px; */
|
||
}
|
||
|
||
/* 调整Element Plus时间轴组件的节点样式 */
|
||
:deep(.el-timeline-item__wrapper) {
|
||
padding-left: 28px;
|
||
position: relative;
|
||
}
|
||
|
||
:deep(.el-timeline-item__tail) {
|
||
left: 13px;
|
||
border-width: 2px;
|
||
}
|
||
|
||
:deep(.el-timeline-item__node) {
|
||
left: 7px;
|
||
width: 12px;
|
||
height: 12px;
|
||
background-color: #ffffff;
|
||
border: 2px solid #e4e7ed;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
:deep(.el-timeline-item__node--primary) {
|
||
border-color: #6B46C1;
|
||
background-color: #6B46C1;
|
||
}
|
||
|
||
:deep(.el-timeline-item__timestamp) {
|
||
padding-left: 28px;
|
||
}
|
||
|
||
/* 调整时间轴内容的位置 */
|
||
:deep(.el-timeline-item__content) {
|
||
padding-left: 0;
|
||
position: relative;
|
||
top: -2px;
|
||
}
|
||
|
||
.step-content {
|
||
background-color: #f9fafb;
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.model-upload-section {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 16px;
|
||
padding: 12px;
|
||
background-color: #f3f4f6;
|
||
border-radius: 8px;
|
||
border: 1px dashed #d1d5db;
|
||
}
|
||
|
||
.upload-model-button {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.upload-hint-text {
|
||
font-size: 13px;
|
||
color: #6b7280;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* 横板预览容器样式 */
|
||
.preview-container-horizontal {
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: relative;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.preview-images-horizontal {
|
||
display: flex;
|
||
gap: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.image-item-horizontal {
|
||
flex: 1;
|
||
cursor: pointer;
|
||
transition: transform 0.2s;
|
||
max-width: 300px;
|
||
}
|
||
|
||
.image-item-horizontal:hover {
|
||
transform: scale(1.02);
|
||
}
|
||
|
||
.image-item-horizontal img {
|
||
width: 100%;
|
||
height: 180px;
|
||
object-fit: cover;
|
||
border-radius: 8px;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.model-preview-horizontal {
|
||
width: 100%;
|
||
height: 180px;
|
||
background-color: #f3f4f6;
|
||
border-radius: 8px;
|
||
border: 1px solid #e5e7eb;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.disassembly-button-container {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
/* 提示词模板在预览容器中的样式调整 */
|
||
.preview-container-horizontal .prompt-template-container {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
/* 原有样式保持不变 */
|
||
.preview-container,
|
||
.disassembled-images,
|
||
.generated-models {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 16px;
|
||
margin-bottom: 16px;
|
||
position: relative;
|
||
}
|
||
|
||
.image-item,
|
||
.model-item {
|
||
position: relative;
|
||
width: 200px;
|
||
cursor: pointer;
|
||
transition: transform 0.2s;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
border: 1px dashed #d9d9d9;
|
||
border-radius: 8px;
|
||
padding: 8px;
|
||
}
|
||
|
||
.image-item:hover,
|
||
.model-item:hover {
|
||
transform: scale(1.02);
|
||
}
|
||
|
||
.model-wrapper {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 150px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.delete-model-button {
|
||
position: absolute;
|
||
top: 4px;
|
||
right: 4px;
|
||
z-index: 10;
|
||
}
|
||
|
||
.model-item:hover {
|
||
border-color: #6B46C1;
|
||
box-shadow: 0 4px 12px rgba(107, 70, 193, 0.1);
|
||
}
|
||
|
||
.model-actions {
|
||
position: absolute;
|
||
bottom: 4px;
|
||
left: 0;
|
||
right: 0;
|
||
display: flex;
|
||
justify-content: center;
|
||
z-index: 10;
|
||
}
|
||
|
||
.image-item img {
|
||
width: 100%;
|
||
height: 150px;
|
||
object-fit: cover;
|
||
border-radius: 8px;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.model-preview {
|
||
width: 100%;
|
||
height: 150px;
|
||
background-color: #f3f4f6;
|
||
border-radius: 8px;
|
||
border: 1px solid #e5e7eb;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.image-label {
|
||
text-align: center;
|
||
margin-top: 8px;
|
||
font-size: 14px;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.image-actions {
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-top: 8px;
|
||
gap: 8px;
|
||
}
|
||
|
||
.step-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.shipping-form {
|
||
max-width: 600px;
|
||
}
|
||
|
||
.work-period-info {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 24px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.info-card {
|
||
flex: 1;
|
||
min-width: 280px;
|
||
background-color: #fff;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 12px;
|
||
padding: 24px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.info-card:hover {
|
||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.card-content {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.total-days-card {
|
||
background: linear-gradient(135deg, #6B46C1 0%, #9333EA 100%);
|
||
color: white;
|
||
border: none;
|
||
}
|
||
|
||
.info-icon {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 12px;
|
||
background-color: #f3f4f6;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #6B46C1;
|
||
font-size: 20px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.total-days-card .info-icon {
|
||
background-color: rgba(255, 255, 255, 0.2);
|
||
color: white;
|
||
}
|
||
|
||
.info-content {
|
||
flex: 1;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 14px;
|
||
color: #6b7280;
|
||
margin-bottom: 4px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.total-days-card .info-label {
|
||
color: rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 16px;
|
||
color: #1f2937;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.total-days-card .info-value {
|
||
color: white;
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.model-preview-dialog {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
/* 上传容器样式 */
|
||
.upload-container {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding: 20px;
|
||
}
|
||
|
||
.image-uploader {
|
||
width: 100%;
|
||
}
|
||
|
||
.upload-trigger {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 200px;
|
||
height: 150px;
|
||
border: 2px dashed #d9d9d9;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.upload-trigger:hover {
|
||
border-color: #6B46C1;
|
||
background-color: #f9fafb;
|
||
}
|
||
|
||
.upload-text {
|
||
margin-top: 8px;
|
||
font-size: 14px;
|
||
color: #6b7280;
|
||
}
|
||
|
||
|
||
|
||
.prompt-template-container {
|
||
margin-bottom: 16px;
|
||
background-color: #ffffff;
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.prompt-template-label {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #6b7280;
|
||
margin-bottom: 8px;
|
||
display: block;
|
||
}
|
||
|
||
.prompt-template-textarea {
|
||
width: 100%;
|
||
font-size: 14px;
|
||
border: 1px solid #d1d5db;
|
||
border-radius: 6px;
|
||
padding: 8px;
|
||
resize: vertical;
|
||
}
|
||
|
||
.prompt-template-textarea:focus {
|
||
outline: none;
|
||
border-color: #6B46C1;
|
||
box-shadow: 0 0 0 3px rgba(107, 70, 193, 0.1);
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.disassembly-detail {
|
||
padding: 12px;
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.preview-container,
|
||
.disassembled-images,
|
||
.generated-model {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.preview-images-horizontal {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.image-item,
|
||
.model-item,
|
||
.image-item-horizontal {
|
||
width: 100%;
|
||
max-width: none;
|
||
}
|
||
|
||
.step-actions {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.disassembly-button-container {
|
||
justify-content: center;
|
||
}
|
||
|
||
.shipping-form {
|
||
max-width: 100%;
|
||
}
|
||
|
||
.work-period-info {
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.info-card {
|
||
min-width: auto;
|
||
}
|
||
}
|
||
</style> |