mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-05-10 19:54:25 +08:00
remove AI3D module files
This commit is contained in:
@@ -1,498 +0,0 @@
|
||||
<template>
|
||||
<div class="page-threed">
|
||||
<!-- 左侧参数设置面板 -->
|
||||
<div class="params-panel">
|
||||
<!-- 平台选择Tab -->
|
||||
<div class="platform-tabs">
|
||||
<CustomTabs v-model="activePlatform" @tab-click="handlePlatformChange">
|
||||
<CustomTabPane name="gitee" width="48%">
|
||||
<template #label>
|
||||
<div class="flex items-center justify-center">
|
||||
<i class="iconfont icon-gitee mr-1"></i>
|
||||
<span>Gitee 模力方舟</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 参数容器 -->
|
||||
<div class="params-container">
|
||||
<!-- 图片上传区域 -->
|
||||
<div class="param-line pt">
|
||||
<span class="label"><span class="text-red-500 mr-1">*</span>上传图片:</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<ImageUpload v-model="giteeForm.image_url" :max-count="1" :multiple="false" />
|
||||
</div>
|
||||
|
||||
<!-- 模型选择 -->
|
||||
<div class="param-line pt">
|
||||
<span class="label"><span class="text-red-500 mr-1">*</span>模型选择:</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<el-select
|
||||
v-model="giteeForm.model"
|
||||
placeholder="选择模型"
|
||||
@change="handleModelChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="model in configs.gitee.models"
|
||||
:key="model.name"
|
||||
:label="model.name"
|
||||
:value="model.name"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<el-alert v-if="giteeForm.model_desc" type="info" :closable="false">
|
||||
{{ giteeForm.model_desc }}
|
||||
</el-alert>
|
||||
</div>
|
||||
|
||||
<!-- 文件格式选择 -->
|
||||
<div class="param-line">
|
||||
<span class="label mb-3"><span class="text-red-500 mr-1">*</span>输出格式:</span>
|
||||
<el-select v-model="giteeForm.file_format" style="width: 100%">
|
||||
<el-option
|
||||
v-for="format in giteeSupportedFormats"
|
||||
:key="format"
|
||||
:label="format"
|
||||
:value="format"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 纹理开关 -->
|
||||
<div class="flex justify-between param-line">
|
||||
<span class="label">生成纹理:</span>
|
||||
<el-switch v-model="giteeForm.texture" size="large" />
|
||||
</div>
|
||||
|
||||
<!-- 高级参数 -->
|
||||
<div class="param-line pt">
|
||||
<el-button
|
||||
@click="giteeAdvancedVisible = !giteeAdvancedVisible"
|
||||
class="advanced-toggle-btn"
|
||||
>
|
||||
<i
|
||||
:class="
|
||||
giteeAdvancedVisible ? 'iconfont icon-arrow-up' : 'iconfont icon-arrow-down'
|
||||
"
|
||||
></i>
|
||||
<span>高级参数设置</span>
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 高级参数内容 -->
|
||||
<div v-show="giteeAdvancedVisible" class="advanced-params">
|
||||
<!-- 随机种子 -->
|
||||
<div class="param-line">
|
||||
<span class="label mb-3">随机种子:</span>
|
||||
<el-input-number
|
||||
v-model="giteeForm.seed"
|
||||
:min="0"
|
||||
:max="10000000"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 迭代次数 -->
|
||||
<div class="param-line">
|
||||
<span class="label mb-3">迭代次数:</span>
|
||||
<el-input-number
|
||||
v-model="giteeForm.num_inference_steps"
|
||||
:min="1"
|
||||
:max="50"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 引导系数 -->
|
||||
<div class="param-line">
|
||||
<span class="label mb-3">引导系数:</span>
|
||||
<el-input-number
|
||||
v-model="giteeForm.guidance_scale"
|
||||
:min="1"
|
||||
:max="20"
|
||||
:step="0.5"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 3D渲染精度 -->
|
||||
<div class="param-line">
|
||||
<span class="label mb-3">3D渲染精度:</span>
|
||||
<el-select v-model="giteeForm.octree_resolution" style="width: 100%">
|
||||
<el-option label="64 (低精度)" :value="64" />
|
||||
<el-option label="128 (中精度)" :value="128" />
|
||||
<el-option label="256 (高精度)" :value="256" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CustomTabPane>
|
||||
<CustomTabPane name="tencent" width="48%">
|
||||
<template #label>
|
||||
<div class="flex items-center justify-center">
|
||||
<i class="iconfont icon-tencent mr-1"></i>
|
||||
<span>腾讯云混元3D</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 参数容器 -->
|
||||
<div class="params-container">
|
||||
<div class="param-line pt flex justify-between items-center">
|
||||
<span class="label">生成模式:</span>
|
||||
<custom-switch
|
||||
v-model="tencentForm.text3d"
|
||||
active-color="#9c27b0"
|
||||
inactive-color="#409eff"
|
||||
:width="120"
|
||||
size="large"
|
||||
>
|
||||
<template #active-text>
|
||||
<div class="flex items-center justify-start pl-4 text-sm">
|
||||
<i class="iconfont icon-image mr-1"></i> <span>文生3D</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #inactive-text>
|
||||
<div class="flex items-center justify-end pl-4 text-sm">
|
||||
<i class="iconfont icon-doc mr-1"></i> <span>图生3D</span>
|
||||
</div>
|
||||
</template>
|
||||
</custom-switch>
|
||||
</div>
|
||||
|
||||
<!-- 文本提示词 -->
|
||||
<div v-if="tencentForm.text3d">
|
||||
<div class="param-line pt">
|
||||
<span class="label"><span class="text-red-500 mr-1">*</span>提示词:</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<el-input
|
||||
v-model="tencentForm.prompt"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 5 }"
|
||||
placeholder="请输入3D模型描述,越详细越好"
|
||||
maxlength="2000"
|
||||
show-word-limit
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<!-- 图片上传区域 -->
|
||||
<div class="param-line pt">
|
||||
<span class="label"><span class="text-red-500 mr-1">*</span>上传图片:</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<ImageUpload v-model="tencentForm.image_url" :max-count="1" :multiple="false" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模型选择 -->
|
||||
<div class="param-line pt">
|
||||
<span class="label mb-2"><span class="text-red-500 mr-1">*</span>模型选择:</span>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<el-select
|
||||
v-model="tencentForm.model"
|
||||
@change="handleModelChange"
|
||||
placeholder="选择模型"
|
||||
>
|
||||
<el-option
|
||||
v-for="model in configs.tencent.models"
|
||||
:key="model.name"
|
||||
:label="model.name"
|
||||
:value="model.name"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<el-alert v-if="tencentForm.model_desc" type="info" :closable="false">
|
||||
{{ tencentForm.model_desc }}
|
||||
</el-alert>
|
||||
</div>
|
||||
|
||||
<!-- 文件格式选择 -->
|
||||
<div class="param-line">
|
||||
<span class="label mb-3"><span class="text-red-500 mr-1">*</span>输出格式:</span>
|
||||
<el-select v-model="tencentForm.file_format" style="width: 100%">
|
||||
<el-option
|
||||
v-for="format in tencentSupportedFormats"
|
||||
:key="format"
|
||||
:label="format"
|
||||
:value="format"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- PBR材质开关 -->
|
||||
<div class="flex justify-between param-line">
|
||||
<span class="label">启用PBR材质:</span>
|
||||
<el-switch v-model="tencentForm.enable_pbr" size="large" />
|
||||
</div>
|
||||
</div>
|
||||
</CustomTabPane>
|
||||
<!-- 生成按钮 -->
|
||||
<div class="generate-section">
|
||||
<button
|
||||
@click="generate3D"
|
||||
:disabled="loading"
|
||||
type="button"
|
||||
class="w-full py-3 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl disabled:from-gray-400 disabled:to-gray-400 disabled:cursor-not-allowed hover:from-blue-600 hover:to-purple-700 transition-all duration-200 flex items-center justify-center space-x-2"
|
||||
>
|
||||
<i v-if="loading" class="iconfont icon-loading animate-spin"></i>
|
||||
<i v-else class="iconfont icon-chuangzuo"></i>
|
||||
<span>{{ loading ? '创作中...' : `立即生成 (${currentPower}算力)` }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</CustomTabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<div class="content-panel">
|
||||
<!-- 任务列表 -->
|
||||
<div class="task-list">
|
||||
<div class="list-header">
|
||||
<h3>生成任务</h3>
|
||||
<el-button size="small" @click="refreshTasks">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<div class="task-items">
|
||||
<div
|
||||
v-for="task in taskList"
|
||||
:key="task.id"
|
||||
class="task-card"
|
||||
:class="getTaskCardClass(task.status)"
|
||||
>
|
||||
<!-- 任务卡片头部 -->
|
||||
<div class="task-card-header">
|
||||
<div class="task-info">
|
||||
<div class="task-id">
|
||||
<i class="iconfont icon-renwu mr-2"></i>
|
||||
#{{ task.id }}
|
||||
</div>
|
||||
<div class="task-platform">
|
||||
<i :class="getPlatformIcon(task.type)" class="mr-1"></i>
|
||||
{{ getPlatformName(task.type) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-status-wrapper">
|
||||
<div class="task-status">
|
||||
<el-button
|
||||
size="small"
|
||||
:type="getStatusText(task.status).type"
|
||||
class="action-btn processing-btn"
|
||||
disabled
|
||||
round
|
||||
>
|
||||
<i
|
||||
class="iconfont icon-loading animate-spin mr-1"
|
||||
v-if="task.status === 'processing'"
|
||||
></i>
|
||||
{{ getStatusText(task.status).text }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="task-power">
|
||||
<i class="iconfont icon-power mr-1"></i>
|
||||
{{ task.power }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务卡片内容 -->
|
||||
<div class="task-card-content">
|
||||
<!-- 左侧预览图 -->
|
||||
<div class="task-preview rounded-lg">
|
||||
<div v-if="task.status === 'success' && task.preview_url" class="preview-image">
|
||||
<img :src="task.preview_url" :alt="getTaskPrompt(task)" />
|
||||
<div class="preview-overlay cursor-pointer" @click="preview3D(task)">
|
||||
<i class="iconfont icon-eye-open !text-3xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="getTaskImageUrl(task)" class="input-image">
|
||||
<img :src="getTaskImageUrl(task)" :alt="getTaskPrompt(task)" />
|
||||
<div class="input-overlay">
|
||||
<i class="iconfont icon-cube !text-3xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="prompt-placeholder">
|
||||
<i class="iconfont icon-doc"></i>
|
||||
<span>文生3D任务</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧任务详情 -->
|
||||
<div class="task-details">
|
||||
<div class="task-model">
|
||||
<i class="iconfont icon-model !text-2xl mr-1"></i>
|
||||
{{ task.model }}
|
||||
</div>
|
||||
|
||||
<div class="task-prompt" v-if="getTaskPrompt(task)">
|
||||
<i class="iconfont icon-info !text-lg mr-1"></i>
|
||||
<span>{{ getTaskPrompt(task) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="task-params" v-if="getTaskParams(task)">
|
||||
<i class="iconfont icon-tag !text-lg mr-1"></i>
|
||||
<span>{{ getTaskParams(task) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="task-time">
|
||||
<i class="iconfont icon-clock !text-xl mr-1"></i>
|
||||
{{ dateFormat(task.created_at) }}
|
||||
</div>
|
||||
|
||||
<div class="task-error" v-if="task.status === 'failed' && task.err_msg">
|
||||
<i class="iconfont icon-error !text-base mr-1"></i>
|
||||
<span>{{ task.err_msg }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务卡片底部操作 -->
|
||||
<div class="task-card-footer">
|
||||
<div class="task-actions">
|
||||
<el-button
|
||||
v-if="task.status === 'success'"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="preview3D(task)"
|
||||
class="action-btn preview-btn"
|
||||
>
|
||||
<i class="iconfont icon-eye-open mr-1"></i>
|
||||
预览
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-if="task.status === 'success'"
|
||||
size="small"
|
||||
type="success"
|
||||
@click="downloadFile(task)"
|
||||
:loading="task.downloading"
|
||||
class="action-btn download-btn"
|
||||
>
|
||||
<i class="iconfont icon-download mr-1" v-if="!task.downloading"></i>
|
||||
<span v-if="task.downloading">下载中...</span>
|
||||
<span v-else>下载</span>
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="deleteTask(task.id)"
|
||||
class="action-btn delete-btn"
|
||||
>
|
||||
<i class="iconfont icon-remove mr-1"></i>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="taskList.length === 0" class="empty-state">
|
||||
<i class="iconfont icon-kong"></i>
|
||||
<p>暂无任务,开始创建你的第一个3D模型吧!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination" v-if="total > 0">
|
||||
<el-pagination
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handlePageSizeChange"
|
||||
@current-change="handleCurrentPageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3D预览弹窗 -->
|
||||
<el-dialog v-model="previewVisible" title="3D模型预览" fullscreen :before-close="closePreview">
|
||||
<div class="preview-container">
|
||||
<ThreeDPreview
|
||||
v-if="currentPreviewTask && currentPreviewTask.file_url"
|
||||
:model-url="currentPreviewTask.file_url"
|
||||
/>
|
||||
<div v-else class="preview-placeholder">
|
||||
<i class="iconfont icon-3d"></i>
|
||||
<p>暂无3D模型</p>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="closePreview">关闭</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="downloadCurrentModel"
|
||||
:loading="currentPreviewTask.downloading"
|
||||
>
|
||||
<span v-if="!currentPreviewTask.downloading">下载模型</span>
|
||||
<span v-else>下载中...</span>
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ImageUpload from '@/components/ImageUpload.vue'
|
||||
import ThreeDPreview from '@/components/ThreeDPreview.vue'
|
||||
import CustomSwitch from '@/components/ui/CustomSwitch.vue'
|
||||
import CustomTabPane from '@/components/ui/CustomTabPane.vue'
|
||||
import CustomTabs from '@/components/ui/CustomTabs.vue'
|
||||
import { useAI3DStore } from '@/store/ai3d'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { dateFormat } from '../utils/libs'
|
||||
|
||||
const ai3d = useAI3DStore()
|
||||
const {
|
||||
activePlatform,
|
||||
loading,
|
||||
previewVisible,
|
||||
currentPage,
|
||||
pageSize,
|
||||
total,
|
||||
taskList,
|
||||
currentPreviewTask,
|
||||
giteeAdvancedVisible,
|
||||
tencentForm,
|
||||
giteeForm,
|
||||
currentPower,
|
||||
tencentSupportedFormats,
|
||||
giteeSupportedFormats,
|
||||
configs,
|
||||
} = storeToRefs(ai3d)
|
||||
|
||||
const {
|
||||
handleModelChange,
|
||||
handlePlatformChange,
|
||||
generate3D,
|
||||
refreshTasks,
|
||||
handlePageSizeChange,
|
||||
handleCurrentPageChange,
|
||||
deleteTask,
|
||||
preview3D,
|
||||
closePreview,
|
||||
downloadFile,
|
||||
downloadCurrentModel,
|
||||
getStatusText,
|
||||
getTaskCardClass,
|
||||
getPlatformIcon,
|
||||
getPlatformName,
|
||||
getTaskPrompt,
|
||||
getTaskImageUrl,
|
||||
getTaskParams,
|
||||
} = ai3d
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '@/assets/css/ai3d.scss' as ai3d;
|
||||
</style>
|
||||
@@ -1,399 +0,0 @@
|
||||
<template>
|
||||
<div class="admin-threed-setting">
|
||||
<!-- 配置表单 -->
|
||||
<div class="settings-container">
|
||||
<!-- 配置选项卡 -->
|
||||
<el-card class="setting-card">
|
||||
<el-tabs v-model="activeTab" type="border-card">
|
||||
<!-- 腾讯混元3D配置 -->
|
||||
<el-tab-pane name="tencent">
|
||||
<template #label>
|
||||
<div class="tab-label">
|
||||
<i class="iconfont icon-tencent mr-2"></i>
|
||||
<span>腾讯混元3D</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="tab-content">
|
||||
<!-- 秘钥配置 -->
|
||||
<div class="config-section">
|
||||
<h4>秘钥配置</h4>
|
||||
<el-form :model="configs.tencent" label-width="140px" label-position="top">
|
||||
<el-form-item label="SecretId">
|
||||
<el-input
|
||||
v-model="configs.tencent.secret_id"
|
||||
placeholder="请输入腾讯云SecretId"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="SecretKey">
|
||||
<el-input
|
||||
v-model="configs.tencent.secret_key"
|
||||
placeholder="请输入腾讯云SecretKey"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="地域(目前仅支持广州)">
|
||||
<el-input v-model="configs.tencent.region" placeholder="请输入地域" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="configs.tencent.enabled" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 模型配置 -->
|
||||
<div class="config-section">
|
||||
<h4>模型配置</h4>
|
||||
<div class="model-config">
|
||||
<div class="model-header">
|
||||
<span>支持的3D模型格式和算力消耗</span>
|
||||
<el-button type="primary" plain @click="addTencentModel">添加模型</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="configs.tencent.models"
|
||||
border
|
||||
style="width: 100%"
|
||||
:max-height="400"
|
||||
size="small"
|
||||
>
|
||||
<el-table-column prop="name" label="模型名称" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.name" placeholder="模型名称" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="desc" label="模型描述" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.desc"
|
||||
placeholder="模型描述"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="power" label="算力消耗" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.power" :min="1" :max="1000" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="formats" label="输出格式" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.formats"
|
||||
multiple
|
||||
placeholder="选择输出格式"
|
||||
style="width: 100%"
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
>
|
||||
<el-option
|
||||
v-for="item in formatOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="100" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<el-button size="small" type="danger" @click="removeTencentModel($index)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Gitee模力方舟配置 -->
|
||||
<el-tab-pane name="gitee">
|
||||
<template #label>
|
||||
<div class="tab-label">
|
||||
<i class="iconfont icon-gitee mr-2"></i>
|
||||
<span>Gitee模力方舟</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="tab-content">
|
||||
<Alert type="info">
|
||||
如果你不知道怎么获取这些配置信息,请参考文档:
|
||||
<a href="https://ai.gitee.com/docs/organization/access-token" target="_blank"
|
||||
>模力方舟访问令牌配置</a
|
||||
>。
|
||||
</Alert>
|
||||
<!-- 秘钥配置 -->
|
||||
<div class="config-section mt-5">
|
||||
<h4>秘钥配置</h4>
|
||||
<el-form :model="configs.gitee" label-width="140px" label-position="top">
|
||||
<el-form-item label="API密钥">
|
||||
<el-input v-model="configs.gitee.api_key" placeholder="请输入Gitee API密钥" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="configs.gitee.enabled" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 模型配置 -->
|
||||
<div class="config-section">
|
||||
<h4>模型配置</h4>
|
||||
<div class="model-config">
|
||||
<div class="model-header">
|
||||
<span>支持的3D模型格式和算力消耗</span>
|
||||
<el-button type="primary" plain @click="addGiteeModel">添加模型</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="configs.gitee.models"
|
||||
border
|
||||
style="width: 100%"
|
||||
:max-height="400"
|
||||
size="small"
|
||||
>
|
||||
<el-table-column prop="name" label="模型名称" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.name" placeholder="模型名称" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="desc" label="模型描述" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.desc"
|
||||
placeholder="模型描述"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="power" label="算力消耗" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.power" :min="1" :max="1000" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="formats" label="输出格式" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.formats"
|
||||
multiple
|
||||
placeholder="选择输出格式"
|
||||
style="width: 100%"
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
>
|
||||
<el-option
|
||||
v-for="item in formatOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="100" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<el-button size="small" type="danger" @click="removeGiteeModel($index)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<div class="flex justify-center mb-5">
|
||||
<el-button type="primary" @click="saveConfig" :loading="loading">保存配置</el-button>
|
||||
</div>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Alert from '@/components/ui/Alert.vue'
|
||||
import { httpGet, httpPost } from '@/utils/http'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
// 响应式数据
|
||||
const activeTab = ref('tencent')
|
||||
const loading = ref(false)
|
||||
const configs = ref({
|
||||
tencent: { region: 'ap-guangzhou', enabled: true, models: [] },
|
||||
gitee: { models: [] },
|
||||
})
|
||||
|
||||
const formatOptions = ref([
|
||||
{ label: 'OBJ', value: 'OBJ' },
|
||||
{ label: 'GLB', value: 'GLB' },
|
||||
{ label: 'STL', value: 'STL' },
|
||||
{ label: 'USDZ', value: 'USDZ' },
|
||||
{ label: 'FBX', value: 'FBX' },
|
||||
{ label: 'MP4', value: 'MP4' },
|
||||
])
|
||||
|
||||
// 方法
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
const res = await httpGet('/api/admin/config/get?key=ai3d')
|
||||
configs.value = res.data
|
||||
const models = await httpGet('/api/admin/ai3d/models')
|
||||
if (!configs.value.tencent.models || configs.value.tencent.models.length === 0) {
|
||||
configs.value.tencent.models = models.data.tencent
|
||||
}
|
||||
if (!configs.value.gitee.models || configs.value.gitee.models.length === 0) {
|
||||
configs.value.gitee.models = models.data.gitee
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('加载配置失败:' + error.message)
|
||||
}
|
||||
}
|
||||
|
||||
const saveConfig = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await httpPost('/api/admin/ai3d/config', configs.value)
|
||||
ElMessage.success('所有配置保存成功')
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('保存失败:' + error.message)
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 模型操作 - 腾讯
|
||||
const addTencentModel = () => {
|
||||
configs.value.tencent.models.push({
|
||||
name: '',
|
||||
desc: '',
|
||||
power: 1,
|
||||
formats: [],
|
||||
})
|
||||
}
|
||||
|
||||
const removeTencentModel = async (index) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该模型吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
configs.value.tencent.models.splice(index, 1)
|
||||
ElMessage.success('删除成功')
|
||||
} catch (e) {
|
||||
// 用户取消
|
||||
}
|
||||
}
|
||||
|
||||
// 模型操作 - Gitee
|
||||
const addGiteeModel = () => {
|
||||
configs.value.gitee.models.push({
|
||||
name: '',
|
||||
desc: '',
|
||||
power: 1,
|
||||
formats: [],
|
||||
})
|
||||
}
|
||||
|
||||
const removeGiteeModel = async (index) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该模型吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
configs.value.gitee.models.splice(index, 1)
|
||||
ElMessage.success('删除成功')
|
||||
} catch (e) {
|
||||
// 用户取消
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadConfig()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.admin-threed-setting {
|
||||
padding: 20px;
|
||||
|
||||
a {
|
||||
color: #409eff;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.el-form-item__label {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.settings-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.setting-card {
|
||||
.el-card__body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.config-section {
|
||||
margin-bottom: 30px;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.section-actions {
|
||||
margin-top: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.model-config {
|
||||
.model-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.el-button {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__header) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__content) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,469 +0,0 @@
|
||||
<template>
|
||||
<div class="admin-threed-jobs">
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="search-section">
|
||||
<el-form :model="searchForm" inline>
|
||||
<el-form-item label="任务状态">
|
||||
<el-select
|
||||
v-model="searchForm.status"
|
||||
placeholder="选择状态"
|
||||
style="width: 120px"
|
||||
clearable
|
||||
>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="等待中" value="pending" />
|
||||
<el-option label="处理中" value="processing" />
|
||||
<el-option label="已完成" value="success" />
|
||||
<el-option label="失败" value="failed" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="平台类型">
|
||||
<el-select
|
||||
v-model="searchForm.type"
|
||||
placeholder="选择平台"
|
||||
style="width: 120px"
|
||||
clearable
|
||||
>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="魔力方舟" value="gitee" />
|
||||
<el-option label="腾讯混元" value="tencent" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="用户ID">
|
||||
<el-input v-model="searchForm.userId" placeholder="输入用户ID" clearable />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 数据统计 -->
|
||||
<div class="stats-section">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon pending">
|
||||
<i class="iconfont icon-clock"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.pending }}</div>
|
||||
<div class="stat-label">等待中</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon processing">
|
||||
<i class="iconfont icon-loading"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.processing }}</div>
|
||||
<div class="stat-label">处理中</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon completed">
|
||||
<i class="iconfont icon-check"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.success }}</div>
|
||||
<div class="stat-label">已完成</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon failed">
|
||||
<i class="iconfont icon-error"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ stats.failed }}</div>
|
||||
<div class="stat-label">失败</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 任务列表 -->
|
||||
<div class="table-section w-full">
|
||||
<el-table :data="taskList" v-loading="loading" border style="width: 100%">
|
||||
<el-table-column prop="user_id" label="用户ID" width="80" />
|
||||
<el-table-column prop="type" label="平台">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.type === 'gitee' ? 'success' : 'primary'">
|
||||
{{ row.type === 'gitee' ? '魔力方舟' : '腾讯混元' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="model" label="模型名称" />
|
||||
<el-table-column label="模型格式">
|
||||
<template #default="{ row }">
|
||||
{{ row.params.file_format }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="power" label="算力消耗" />
|
||||
<el-table-column prop="status" label="状态">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getStatusType(row.status)">
|
||||
{{ getStatusText(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间">
|
||||
<template #default="{ row }">
|
||||
{{ dateFormat(row.created_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="updated_at" label="更新时间">
|
||||
<template #default="{ row }">
|
||||
{{ dateFormat(row.updated_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="300" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" @click="viewTask(row)">查看</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
plain
|
||||
v-if="row.status === 'success'"
|
||||
@click="openModelPreview(row)"
|
||||
>
|
||||
预览模型
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteTask(row.id)"> 删除 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-section">
|
||||
<el-pagination
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[20, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务详情弹窗 -->
|
||||
<el-dialog
|
||||
v-model="taskDetailVisible"
|
||||
title="任务详情"
|
||||
width="60%"
|
||||
:before-close="closeTaskDetail"
|
||||
>
|
||||
<div v-if="currentTask" class="task-detail">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="任务ID">{{ currentTask.id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用户ID">{{ currentTask.user_id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="平台类型">
|
||||
<el-tag :type="currentTask.type === 'gitee' ? 'success' : 'primary'">
|
||||
{{ currentTask.type === 'gitee' ? '魔力方舟' : '腾讯混元' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="模型名称">{{ currentTask.model }}</el-descriptions-item>
|
||||
<el-descriptions-item label="算力消耗">{{ currentTask.power }}</el-descriptions-item>
|
||||
<el-descriptions-item label="任务状态">
|
||||
<el-tag :type="getStatusType(currentTask.status)">
|
||||
{{ getStatusText(currentTask.status) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{
|
||||
dateFormat(currentTask.created_at)
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">{{
|
||||
dateFormat(currentTask.updated_at)
|
||||
}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="task-params">
|
||||
<h4>任务参数</h4>
|
||||
<div class="params-content">
|
||||
<pre>{{ JSON.stringify(currentTask.params, null, 2) }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentTask.img_url || currentTask.file_url" class="task-result">
|
||||
<h4>生成结果</h4>
|
||||
<div class="result-links">
|
||||
<el-button type="primary" @click="downloadModel(currentTask)"> 下载3D模型 </el-button>
|
||||
<el-button
|
||||
v-if="currentTask.file_url"
|
||||
type="success"
|
||||
plain
|
||||
@click="openModelPreview(currentTask)"
|
||||
>
|
||||
预览模型
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentTask.err_msg" class="task-error">
|
||||
<h4>错误信息</h4>
|
||||
<el-alert :title="currentTask.err_msg" type="error" :closable="false" show-icon />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="closeTaskDetail">关闭</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 3D 模型预览弹窗 -->
|
||||
<el-dialog
|
||||
v-model="modelPreviewVisible"
|
||||
:class="['model-preview-dialog', { dark: isDarkTheme }]"
|
||||
title="模型预览"
|
||||
fullscreen
|
||||
destroy-on-close
|
||||
>
|
||||
<div class="model-preview-wrapper">
|
||||
<ThreeDPreview :model-url="modelPreviewUrl" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="downloadModel(currentTask)"
|
||||
:loading="currentTask.downloading"
|
||||
>
|
||||
下载3D模型
|
||||
</el-button>
|
||||
<el-button @click="modelPreviewVisible = false">关闭</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ThreeDPreview from '@/components/ThreeDPreview.vue'
|
||||
import { showMessageError } from '@/utils/dialog'
|
||||
import { httpDownload, httpGet } from '@/utils/http'
|
||||
import { dateFormat, replaceImg } from '@/utils/libs'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
const taskList = ref([])
|
||||
const taskDetailVisible = ref(false)
|
||||
const currentTask = ref({
|
||||
downloading: false,
|
||||
})
|
||||
const previewUrl = ref('')
|
||||
// 3D 预览
|
||||
const modelPreviewVisible = ref(false)
|
||||
const modelPreviewUrl = ref('')
|
||||
// 简单检测暗色主题(若全局有主题管理可替换)
|
||||
const isDarkTheme = ref(
|
||||
document.documentElement.classList.contains('dark') || document.body.classList.contains('dark')
|
||||
)
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
status: '',
|
||||
type: '',
|
||||
userId: '',
|
||||
})
|
||||
|
||||
// 统计数据
|
||||
const stats = reactive({
|
||||
pending: 0,
|
||||
processing: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
})
|
||||
|
||||
// 方法
|
||||
const loadData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
|
||||
const params = {
|
||||
page: currentPage.value,
|
||||
page_size: pageSize.value,
|
||||
...searchForm,
|
||||
}
|
||||
|
||||
// 移除空值
|
||||
Object.keys(params).forEach((key) => {
|
||||
if (params[key] === '') {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
const response = await httpGet('/api/admin/ai3d/jobs', params)
|
||||
|
||||
if (response.code === 0) {
|
||||
taskList.value = response.data.items
|
||||
total.value = response.data.total
|
||||
} else {
|
||||
ElMessage.error(response.message || '加载数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('加载数据失败:' + error.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadStats = async () => {
|
||||
try {
|
||||
const response = await httpGet('/api/admin/ai3d/stats')
|
||||
if (response.code === 0) {
|
||||
Object.assign(stats, response.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
currentPage.value = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const resetSearch = () => {
|
||||
Object.assign(searchForm, {
|
||||
status: '',
|
||||
type: '',
|
||||
userId: '',
|
||||
})
|
||||
currentPage.value = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleSizeChange = (size) => {
|
||||
pageSize.value = size
|
||||
currentPage.value = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (page) => {
|
||||
currentPage.value = page
|
||||
loadData()
|
||||
}
|
||||
|
||||
const refreshData = () => {
|
||||
loadData()
|
||||
loadStats()
|
||||
}
|
||||
|
||||
const viewTask = (task) => {
|
||||
currentTask.value = task
|
||||
taskDetailVisible.value = true
|
||||
}
|
||||
|
||||
const closeTaskDetail = () => {
|
||||
taskDetailVisible.value = false
|
||||
currentTask.value = null
|
||||
}
|
||||
|
||||
const deleteTask = async (taskId) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除这个任务吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
|
||||
const response = await httpGet(`/api/admin/ai3d/jobs/${taskId}/delete`)
|
||||
|
||||
if (response.code === 0) {
|
||||
ElMessage.success('删除成功')
|
||||
loadData()
|
||||
loadStats()
|
||||
} else {
|
||||
ElMessage.error(response.message || '删除失败')
|
||||
}
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error('删除失败:' + error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const downloadModel = async (task) => {
|
||||
const url = replaceImg(task.file_url)
|
||||
const downloadURL = `/api/download?url=${url}`
|
||||
const urlObj = new URL(url)
|
||||
const fileName = urlObj.pathname.split('/').pop()
|
||||
task.downloading = true
|
||||
try {
|
||||
const response = await httpDownload(downloadURL)
|
||||
const blob = new Blob([response.data])
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = fileName
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(link.href)
|
||||
task.downloading = false
|
||||
} catch (error) {
|
||||
showMessageError('下载失败:' + error.message)
|
||||
task.downloading = false
|
||||
}
|
||||
}
|
||||
|
||||
const openModelPreview = (task) => {
|
||||
// 优先使用文件直链,后端下载代理也可拼接
|
||||
const url = task.file_url
|
||||
if (!url) {
|
||||
ElMessage.warning('暂无可预览的模型文件')
|
||||
return
|
||||
}
|
||||
currentTask.value = task
|
||||
modelPreviewUrl.value = url
|
||||
modelPreviewVisible.value = true
|
||||
}
|
||||
|
||||
const getStatusType = (status) => {
|
||||
const typeMap = {
|
||||
pending: 'warning',
|
||||
processing: 'primary',
|
||||
success: 'success',
|
||||
failed: 'danger',
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
}
|
||||
|
||||
const getStatusText = (status) => {
|
||||
const textMap = {
|
||||
pending: '等待中',
|
||||
processing: '处理中',
|
||||
success: '已完成',
|
||||
failed: '失败',
|
||||
}
|
||||
return textMap[status] || status
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
loadStats()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '@/assets/css/admin/ai3d.scss' as *;
|
||||
</style>
|
||||
Reference in New Issue
Block a user