diff --git a/apps/FrontendDesigner/.env.development b/apps/FrontendDesigner/.env.development
new file mode 100644
index 0000000..5cd5fd5
--- /dev/null
+++ b/apps/FrontendDesigner/.env.development
@@ -0,0 +1,4 @@
+# 开发环境配置
+VITE_BASE_URL=/api
+VITE_DEV_MODE=true
+VITE_LOG_LEVEL=info
\ No newline at end of file
diff --git a/apps/FrontendDesigner/.env.example b/apps/FrontendDesigner/.env.example
new file mode 100644
index 0000000..7d859f1
--- /dev/null
+++ b/apps/FrontendDesigner/.env.example
@@ -0,0 +1,23 @@
+# 生产环境配置
+VITE_BASE_URL=https://api.deotaland.ai
+# Vercel 部署环境变量配置示例
+# 复制此文件为 .env.local 并填入实际值
+
+# Google AI API Key(用于 AI 功能)
+VITE_GOOGLE_API_KEY=your_google_api_key_here
+
+
+# Stripe 支付配置
+VITE_STRIPE_PUBLISHABLE_KEY=pk_test_your_stripe_publishable_key
+
+# 应用配置
+VITE_APP_TITLE=DeotalandAI
+VITE_APP_DESCRIPTION=AI-Powered Creation Platform
+
+# 开发环境配置
+VITE_DEV_MODE=false
+VITE_LOG_LEVEL=error
+
+# 生产环境配置(在 Vercel 中设置)
+# NODE_ENV=production
+# VERCEL=true
\ No newline at end of file
diff --git a/apps/FrontendDesigner/src/assets/demo/suoluetu.png b/apps/FrontendDesigner/src/assets/demo/suoluetu.png
index 5ce0f72..21c8617 100644
Binary files a/apps/FrontendDesigner/src/assets/demo/suoluetu.png and b/apps/FrontendDesigner/src/assets/demo/suoluetu.png differ
diff --git a/apps/FrontendDesigner/src/components/common/ModelViewer.vue b/apps/FrontendDesigner/src/components/common/ModelViewer.vue
index 947065a..4609fab 100644
--- a/apps/FrontendDesigner/src/components/common/ModelViewer.vue
+++ b/apps/FrontendDesigner/src/components/common/ModelViewer.vue
@@ -3,6 +3,15 @@
+
{{ t('modelViewer.modelInfo') }}: {{ modelInfo }}
{{ t('modelViewer.fileSize') }}: {{ formatFileSize(fileSize) }}
@@ -73,15 +111,27 @@ import { useI18n } from 'vue-i18n'
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
-import { Loading, Warning, Refresh, Grid, Position, Download } from '@element-plus/icons-vue'
+import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js'
+import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js'
+import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js'
+import { ElMessage } from 'element-plus'
+import { Loading, Warning, Refresh, Grid, Position, Download, ArrowDown } from '@element-plus/icons-vue'
const { t } = useI18n()
// Props
const props = defineProps({
+ showExport:{
+ type: Boolean,
+ default: true
+ },
modelUrl: {
type: String,
- default: null
+ default: ''
+ },
+ showInfo: {
+ type: Boolean,
+ default: true
},
width: {
type: [String, Number],
@@ -117,6 +167,8 @@ const loading = ref(true)
const error = ref('')
const modelInfo = ref('')
const fileSize = ref(0)
+const loadingProgress = ref(0)
+const isExporting = ref(false)
// Three.js 相关
let scene, camera, renderer, controls
@@ -211,10 +263,12 @@ const createLaboratoryBackground = () => {
// 加载模型
const loadModel = async (modelPath) => {
+ console.log(modelPath,'modelPathmodelPath');
if (!scene) return
loading.value = true
error.value = ''
+ loadingProgress.value = 0
try {
// 清除现有模型
@@ -231,7 +285,13 @@ const loadModel = async (modelPath) => {
modelPath,
resolve,
(progress) => {
- // 可以添加进度回调
+ // 更新加载进度
+ if (progress.total > 0) {
+ loadingProgress.value = Math.round((progress.loaded / progress.total) * 100)
+ } else {
+ // 如果没有总大小,模拟进度
+ loadingProgress.value = Math.min(90, loadingProgress.value + 10)
+ }
},
reject
)
@@ -277,6 +337,7 @@ const loadModel = async (modelPath) => {
modelInfo.value = gltf.scene?.name || '3D Model'
loading.value = false
+ loadingProgress.value = 100 // 确保进度显示100%
// 如果启用自动旋转
if (props.autoRotate) {
@@ -286,6 +347,7 @@ const loadModel = async (modelPath) => {
} catch (err) {
console.error('Error loading model:', err)
loading.value = false
+ loadingProgress.value = 0
error.value = t('modelViewer.loadError')
}
}
@@ -358,65 +420,227 @@ const centerModel = () => {
controls.update()
}
-// 导出模型
-const exportModel = () => {
- if (!model || !scene) {
- console.warn('No model to export')
+// 处理导出命令
+const handleExportCommand = (command) => {
+ if (isExporting.value) {
+ ElMessage.warning(t('modelViewer.exportInProgress'))
return
}
- // 创建导出配置
- const exportOptions = {
- binary: false,
- embedImages: true,
- animations: true,
- forcePowerOfTwoTextures: false
+ if (!scene || !model) {
+ ElMessage.warning(t('modelViewer.noModelLoaded'))
+ return
}
- // 这里可以实现导出逻辑
- // 由于浏览器环境限制,实际的GLTF导出功能需要额外的库支持
- // 当前实现为模拟导出功能
-
+ switch (command) {
+ case 'glb':
+ exportAsGLB()
+ break
+ case 'obj':
+ exportAsOBJ()
+ break
+ case 'stl':
+ exportAsSTL()
+ break
+ case 'fbx':
+ exportAsFBX()
+ break
+ default:
+ ElMessage.error('不支持的导出格式')
+ break
+ }
+}
+
+// 导出为GLB格式
+const exportAsGLB = () => {
+ isExporting.value = true
+
+ ElMessage({
+ type: 'info',
+ message: t('modelViewer.exportInProgress'),
+ duration: 2000
+ })
+
try {
- // 模拟导出过程
- ElMessage({
- type: 'info',
- message: t('modelViewer.exportInProgress'),
- duration: 2000
- })
-
- setTimeout(() => {
- // 创建下载链接(这里仅为演示,实际应该生成真正的文件)
- const dataStr = JSON.stringify({
- model: '3D Model Data',
- info: modelInfo.value,
- fileSize: formatFileSize(fileSize.value),
- timestamp: new Date().toISOString()
- })
-
- const dataBlob = new Blob([dataStr], { type: 'application/json' })
- const url = URL.createObjectURL(dataBlob)
-
- const link = document.createElement('a')
- link.href = url
- link.download = `${modelInfo.value || 'model'}_export.json`
- document.body.appendChild(link)
- link.click()
- document.body.removeChild(link)
- URL.revokeObjectURL(url)
-
- ElMessage({
- type: 'success',
- message: t('modelViewer.exportSuccess')
- })
- }, 1500)
+ const exporter = new GLTFExporter()
+ exporter.parse(
+ model,
+ (result) => {
+ const blob = new Blob([result], { type: 'application/octet-stream' })
+ const url = URL.createObjectURL(blob)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = `${modelInfo.value || 'model'}.glb`
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ URL.revokeObjectURL(url)
+
+ ElMessage({
+ type: 'success',
+ message: t('modelViewer.exportSuccess')
+ })
+ },
+ (error) => {
+ console.error('GLB export failed:', error)
+ ElMessage({
+ type: 'error',
+ message: t('modelViewer.exportFailed')
+ })
+ },
+ { binary: true }
+ )
} catch (error) {
- console.error('Export failed:', error)
+ console.error('GLB export error:', error)
ElMessage({
type: 'error',
message: t('modelViewer.exportFailed')
})
+ } finally {
+ isExporting.value = false
+ }
+}
+
+// 导出为OBJ格式
+const exportAsOBJ = () => {
+ isExporting.value = true
+
+ try {
+ const exporter = new OBJExporter()
+ const result = exporter.parse(model)
+
+ const blob = new Blob([result], { type: 'text/plain' })
+ const url = URL.createObjectURL(blob)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = `${modelInfo.value || 'model'}.obj`
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ URL.revokeObjectURL(url)
+
+ ElMessage.success(t('modelViewer.exportSuccess'))
+ } catch (error) {
+ console.error('OBJ export error:', error)
+ ElMessage.error(t('modelViewer.exportFailed'))
+ } finally {
+ isExporting.value = false
+ }
+}
+
+// 导出为STL格式
+const exportAsSTL = () => {
+ if (!model || !scene) {
+ ElMessage({
+ type: 'warning',
+ message: 'No model to export'
+ })
+ return
+ }
+
+ isExporting.value = true
+
+ ElMessage({
+ type: 'info',
+ message: t('modelViewer.exportInProgress'),
+ duration: 2000
+ })
+
+ try {
+ const exporter = new STLExporter()
+ const result = exporter.parse(model)
+
+ const blob = new Blob([result], { type: 'application/octet-stream' })
+ const url = URL.createObjectURL(blob)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = `${modelInfo.value || 'model'}.stl`
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ URL.revokeObjectURL(url)
+
+ ElMessage({
+ type: 'success',
+ message: t('modelViewer.exportSuccess')
+ })
+ } catch (error) {
+ console.error('STL export error:', error)
+ ElMessage({
+ type: 'error',
+ message: t('modelViewer.exportFailed')
+ })
+ } finally {
+ isExporting.value = false
+ }
+}
+
+// 导出为FBX格式(转换为GLB并提供说明)
+const exportAsFBX = () => {
+ if (!model || !scene) {
+ ElMessage({
+ type: 'warning',
+ message: 'No model to export'
+ })
+ return
+ }
+
+ isExporting.value = true
+
+ ElMessage({
+ type: 'info',
+ message: t('modelViewer.exportInProgress'),
+ duration: 2000
+ })
+
+ try {
+ const exporter = new GLTFExporter()
+
+ exporter.parse(
+ model,
+ (result) => {
+ const blob = new Blob([result], { type: 'application/octet-stream' })
+ const url = URL.createObjectURL(blob)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = `${modelInfo.value || 'model'}_for_fbx_conversion.glb`
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ URL.revokeObjectURL(url)
+
+ ElMessage({
+ type: 'success',
+ message: `${t('modelViewer.exportSuccess')} (GLB format for FBX conversion)`
+ })
+
+ // 显示提示信息
+ setTimeout(() => {
+ ElMessage({
+ type: 'info',
+ message: 'Note: Three.js does not directly support FBX export. The model has been exported as GLB format. You can use Blender or other tools to convert GLB to FBX format.',
+ duration: 5000
+ })
+ }, 2000)
+ },
+ (error) => {
+ console.error('FBX export failed:', error)
+ ElMessage({
+ type: 'error',
+ message: t('modelViewer.exportFailed')
+ })
+ },
+ { binary: true }
+ )
+ } catch (error) {
+ console.error('FBX export error:', error)
+ ElMessage({
+ type: 'error',
+ message: t('modelViewer.exportFailed')
+ })
+ } finally {
+ isExporting.value = false
}
}
@@ -469,7 +693,7 @@ const handleTouchEnd = () => {
// 重试加载
const retryLoad = () => {
- const modelToLoad = props.modelUrl || DEFAULT_MODEL
+ const modelToLoad = props.modelUrl
loadModel(modelToLoad)
}
@@ -504,7 +728,8 @@ onMounted(async () => {
initThreeJS()
// 加载模型
- const modelToLoad = props.modelUrl || DEFAULT_MODEL
+ console.log(props.modelUrl);
+ const modelToLoad = props.modelUrl
loadModel(modelToLoad)
// 监听窗口大小变化
@@ -629,6 +854,11 @@ defineExpose({
display: none;
}
+.progress-container {
+ margin-top: 16px;
+ text-align: center;
+}
+
/* 移动端优化 */
@media (max-width: 768px) {
.model-controls {
@@ -646,4 +876,88 @@ defineExpose({
font-size: 11px;
}
}
+
+/* 导出按钮样式优化 */
+.export-dropdown .el-button--primary {
+ background: linear-gradient(135deg, #6B46C1 0%, #553C9A 100%);
+ border: none;
+ box-shadow: 0 2px 8px rgba(107, 70, 193, 0.3);
+}
+
+.export-dropdown .el-button--primary:hover {
+ background: linear-gradient(135deg, #553C9A 0%, #4C1D95 100%);
+ box-shadow: 0 4px 12px rgba(107, 70, 193, 0.4);
+}
+
+.export-dropdown .el-button--primary:active {
+ transform: translateY(1px);
+ box-shadow: 0 1px 4px rgba(107, 70, 193, 0.3);
+}
+
+/* 导出菜单项样式 */
+.el-dropdown-menu__item {
+ padding: 8px 16px;
+ font-size: 14px;
+ transition: all 0.2s ease;
+}
+
+.el-dropdown-menu__item:hover {
+ background-color: #f5f3ff;
+ color: #6B46C1;
+}
+
+.el-dropdown-menu__item .format-dot {
+ margin-right: 8px;
+ font-size: 12px;
+}
+
+/* 暗色主题适配 */
+@media (prefers-color-scheme: dark) {
+ .el-dropdown-menu__item:hover {
+ background-color: #2d1b69;
+ color: #A78BFA;
+ }
+}
+
+/* 导出过程动画 */
+@keyframes export-pulse {
+ 0% {
+ transform: scale(1);
+ box-shadow: 0 0 0 0 rgba(107, 70, 193, 0.4);
+ }
+ 70% {
+ transform: scale(1.02);
+ box-shadow: 0 0 0 10px rgba(107, 70, 193, 0);
+ }
+ 100% {
+ transform: scale(1);
+ box-shadow: 0 0 0 0 rgba(107, 70, 193, 0);
+ }
+}
+
+.export-dropdown.exporting .el-button--primary {
+ animation: export-pulse 1.5s infinite;
+ pointer-events: none;
+}
+
+/* 格式按钮悬停效果 */
+.el-dropdown-menu__item {
+ position: relative;
+ overflow: hidden;
+}
+
+.el-dropdown-menu__item::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
+ transition: left 0.5s;
+}
+
+.el-dropdown-menu__item:hover::before {
+ left: 100%;
+}
\ No newline at end of file
diff --git a/apps/FrontendDesigner/src/components/modelCom/index.vue b/apps/FrontendDesigner/src/components/modelCom/index.vue
new file mode 100644
index 0000000..68e7eea
--- /dev/null
+++ b/apps/FrontendDesigner/src/components/modelCom/index.vue
@@ -0,0 +1,394 @@
+
+
+
+
+
+
+
{{ $t('model.initializing') }}
+
+
{{ initializationProgress }}%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/FrontendDesigner/src/locales/lang/en-US.js b/apps/FrontendDesigner/src/locales/lang/en-US.js
index 8a1c980..e4de0df 100644
--- a/apps/FrontendDesigner/src/locales/lang/en-US.js
+++ b/apps/FrontendDesigner/src/locales/lang/en-US.js
@@ -32,7 +32,23 @@ export default {
exportModel: 'Export Model',
exportInProgress: 'Export in progress...',
exportSuccess: 'Export successful',
- exportFailed: 'Export failed'
+ exportFailed: 'Export failed',
+ exportFormat: 'Export Format',
+ exportAsGLB: 'Export as GLB',
+ exportAsOBJ: 'Export as OBJ',
+ exportAsSTL: 'Export as STL',
+ exportAsFBX: 'Export as FBX',
+ selectFormat: 'Select Export Format',
+ preparingExport: 'Preparing export...'
+ },
+
+ // Model Component
+ model: {
+ initializing: 'Initializing Model...',
+ loading: 'Loading Model...',
+ loadError: 'Failed to load model',
+ preview: 'Preview Model',
+ delete: 'Delete Model'
},
// 应用
@@ -433,6 +449,8 @@ export default {
previewDialog: 'Model Preview',
close: 'Close',
loading: 'Loading...',
+ changePreview: 'Change Preview Image',
+ uploadImage: 'Upload Image',
previewDescription: 'This is a preview of the original 3D model. You can drag to rotate and scroll to zoom.',
disassemblyDescription: 'Model disassembly in progress, please wait...',
generateDescription: 'Generating disassembled model files...',
@@ -468,7 +486,9 @@ export default {
shipSuccess: 'Shipping successful',
disassembleConfirm: 'Are you sure to start disassembling this order?',
disassembleTitle: 'Disassembly Confirmation',
- alreadyProcessing: 'This order is already being processed, please do not repeat the operation'
+ alreadyProcessing: 'This order is already being processed, please do not repeat the operation',
+ uploadSuccess: 'Image uploaded successfully',
+ uploadFailed: 'Image upload failed'
}
}
}
diff --git a/apps/FrontendDesigner/src/locales/lang/zh-CN.js b/apps/FrontendDesigner/src/locales/lang/zh-CN.js
index bd3aaaf..93fc42f 100644
--- a/apps/FrontendDesigner/src/locales/lang/zh-CN.js
+++ b/apps/FrontendDesigner/src/locales/lang/zh-CN.js
@@ -367,6 +367,8 @@ export default {
previewDialog: '模型预览',
close: '关闭',
loading: '加载中...',
+ changePreview: '更换预览图',
+ uploadImage: '上传图片',
previewDescription: '这是原始3D模型的预览,您可以拖动鼠标旋转、滚轮缩放查看模型细节。',
disassemblyDescription: '模型拆件处理中,请稍候...',
generateDescription: '正在生成拆件后的模型文件...',
@@ -402,7 +404,9 @@ export default {
shipSuccess: '发货成功',
disassembleConfirm: '确定要开始拆件此订单吗?',
disassembleTitle: '拆件确认',
- alreadyProcessing: '该订单正在拆件中,请勿重复操作'
+ alreadyProcessing: '该订单正在拆件中,请勿重复操作',
+ uploadSuccess: '图片上传成功',
+ uploadFailed: '图片上传失败'
}
},
users: {
@@ -506,6 +510,22 @@ export default {
exportModel: '导出模型',
exportInProgress: '正在导出...',
exportSuccess: '导出成功',
- exportFailed: '导出失败'
+ exportFailed: '导出失败',
+ exportFormat: '导出格式',
+ exportAsGLB: '导出为GLB',
+ exportAsOBJ: '导出为OBJ',
+ exportAsSTL: '导出为STL',
+ exportAsFBX: '导出为FBX',
+ selectFormat: '选择导出格式',
+ preparingExport: '准备导出中...'
+ },
+
+ // 模型组件
+ model: {
+ initializing: '模型初始化中...',
+ loading: '模型加载中... {{progress}}%',
+ loadError: '模型加载失败',
+ preview: '预览',
+ delete: '删除'
}
}
\ No newline at end of file
diff --git a/apps/FrontendDesigner/src/router/index.js b/apps/FrontendDesigner/src/router/index.js
index 2e98274..53873a2 100644
--- a/apps/FrontendDesigner/src/router/index.js
+++ b/apps/FrontendDesigner/src/router/index.js
@@ -11,13 +11,13 @@ const AdminOrders = () => import('@/views/admin/AdminOrders.vue')
const AdminUsers = () => import('@/views/admin/AdminUsers.vue')
const AdminContentReview = () => import('@/views/admin/AdminContentReview.vue')
const AdminDisassemblyOrders = () => import('@/views/admin/AdminDisassemblyOrders.vue')
-const AdminDisassemblyDetail = () => import('@/views/admin/AdminDisassemblyDetail.vue')
+const AdminDisassemblyDetail = () => import('@/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.vue')
const routes = [
{
path: '/',
name: 'Home',
- redirect: '/login',
+ redirect: '/admin',
meta: {
title: '首页重定向'
}
@@ -133,6 +133,9 @@ const router = createRouter({
// 路由守卫 - 认证检查
router.beforeEach((to, from, next) => {
+ localStorage.setItem('token','123')
+ next();
+ return
// 设置页面标题
if (to.meta?.title) {
document.title = to.meta.title
diff --git a/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.js b/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.js
new file mode 100644
index 0000000..b151b2c
--- /dev/null
+++ b/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.js
@@ -0,0 +1,12 @@
+import { prompt, GiminiServer ,MeshyServer} from '@deotaland/utils';
+const gimiServer = new GiminiServer();
+export class AdminDisassemblyDetail {
+ constructor() {
+ }
+ //拆件
+ async disassemble(imgurl,callback) {
+ const result = await gimiServer.handleGenerateImage(imgurl, prompt.Hairseparation)
+ console.log('resultresult',result);
+ callback(result)
+ }
+}
\ No newline at end of file
diff --git a/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail.vue b/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.vue
similarity index 83%
rename from apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail.vue
rename to apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.vue
index 6426e94..ec877e6 100644
--- a/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail.vue
+++ b/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.vue
@@ -61,6 +61,16 @@
{{ $t('admin.disassemblyOrders.detail.preview') }}
+
+
+ {{ $t('admin.disassemblyOrders.detail.changePreview') }}
+
+
@@ -74,9 +84,8 @@
{{ $t('admin.disassemblyOrders.detail.disassembly') }}
@@ -120,7 +129,7 @@
class="generate-model-button"
type="success"
size="small"
- @click.stop="generateModelFromImage(index)"
+ @click.stop="generateModelFromImage(image)"
>
生成模型
@@ -129,14 +138,6 @@
{{ $t('admin.disassemblyOrders.detail.preview') }} {{ index + 1 }}
-
-
- 生成
-
-
@@ -151,29 +152,18 @@